Skip to content

Commit 05719f1

Browse files
djkapnerfcollman
authored andcommitted
vectorization improvements for TPS transform(), apply(), and inverse() (#129)
* vectorization improvements for TPS transform(), apply(), and inverse() * remove scipy.special * slightly faster, avoids divide by zero warning * adding scipy as dependency * removing scipy from test_requirements
1 parent 7904f8a commit 05719f1

File tree

3 files changed

+30
-41
lines changed

3 files changed

+30
-41
lines changed

renderapi/transform/leaf/thin_plate_spline.py

Lines changed: 29 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from renderapi.errors import RenderError, EstimationError
33
from renderapi.utils import encodeBase64, decodeBase64
44
from .transform import Transform
5+
import scipy.spatial
56
__all__ = ['ThinPlateSplineTransform']
67

78

@@ -81,47 +82,38 @@ def tform(self, points):
8182
numpy.array
8283
a Nx2 array of x,y points after transformation
8384
"""
84-
result = []
85-
for pt in points:
86-
result.append(self.apply(pt))
85+
return self.apply(points)
8786

88-
return np.array(result)
89-
90-
def apply(self, pt):
87+
def apply(self, points):
9188
if not hasattr(self, 'dMtxDat'):
92-
result = pt
93-
return result
89+
return points
9490

95-
result = pt + self.computeDeformationContribution(pt)
91+
result = points + self.computeDeformationContribution(points)
9692
if self.aMtx is not None:
97-
result += self.aMtx.dot(pt)
93+
result += self.aMtx.dot(points.transpose()).transpose()
9894
if self.bVec is not None:
9995
result += self.bVec
10096

10197
return result
10298

103-
def computeDeformationContribution(self, pt):
104-
disp = np.linalg.norm(
105-
self.srcPts -
106-
pt.reshape(self.ndims, 1),
107-
axis=0)
108-
nrm = np.zeros_like(disp)
109-
ind = disp > 1e-8
110-
nrm[ind] = disp[ind] * disp[ind] * np.log(disp[ind])
111-
result = (nrm * self.dMtxDat).sum(1)
112-
return result
99+
def computeDeformationContribution(self, points):
100+
disp = scipy.spatial.distance.cdist(
101+
points,
102+
self.srcPts.transpose())
103+
disp = np.power(disp, 2.0) * np.ma.log(disp).filled(0.0)
104+
return disp.dot(self.dMtxDat.transpose())
113105

114106
def gradient_descent(
115107
self,
116-
pt,
108+
pts,
117109
gamma=1.0,
118110
precision=0.0001,
119111
max_iters=1000):
120112
"""based on https://en.wikipedia.org/wiki/Gradient_descent#Python
121113
Parameters
122114
----------
123-
pt : numpy array
124-
[x,y] point for estimating inverse
115+
pts : numpy array
116+
a Nx2 array of x,y points
125117
gamma : float
126118
step size is gamma fraction of current gradient
127119
precision : float
@@ -130,23 +122,23 @@ def gradient_descent(
130122
limit for iterations, error if reached
131123
Returns
132124
-------
133-
cur_pt : numpy array
134-
[x,y] point, estimated inverse of pt
125+
cur_pts : numpy array
126+
a Nx2 array of x,y points, estimated inverse of pt
135127
"""
136-
cur_pt = np.copy(pt)
137-
prev_pt = np.copy(pt)
128+
cur_pts = np.copy(pts)
129+
prev_pts = np.copy(pts)
138130
step_size = 1
139131
iters = 0
140132
while (step_size > precision) & (iters < max_iters):
141-
prev_pt[:] = cur_pt[:]
142-
cur_pt -= gamma*(self.apply(prev_pt) - pt)
143-
step_size = np.linalg.norm(cur_pt - prev_pt)
133+
prev_pts[:, :] = cur_pts[:, :]
134+
cur_pts -= gamma*(self.apply(prev_pts) - pts)
135+
step_size = np.linalg.norm(cur_pts - prev_pts, axis=1).max()
144136
iters += 1
145137
if iters == max_iters:
146138
raise EstimationError(
147139
'gradient descent for inversion of ThinPlateSpline '
148140
'reached maximum iterations: %d' % max_iters)
149-
return cur_pt
141+
return cur_pts
150142

151143
def inverse_tform(
152144
self,
@@ -170,15 +162,12 @@ def inverse_tform(
170162
numpy.array
171163
a Nx2 array of x,y points after inverse transformation
172164
"""
173-
newpts = []
174-
for p in points:
175-
npt = self.gradient_descent(
176-
p,
177-
gamma=gamma,
178-
precision=precision,
179-
max_iters=max_iters)
180-
newpts.append(npt)
181-
return np.array(newpts)
165+
newpts = self.gradient_descent(
166+
points,
167+
gamma=gamma,
168+
precision=precision,
169+
max_iters=max_iters)
170+
return newpts
182171

183172
@staticmethod
184173
def fit(A, B, computeAffine=True):

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
requests
2+
scipy
23
numpy
34
pillow
45
sphinxcontrib-napoleon

test_requirements.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,5 @@ pytest-pep8>=1.0.6
77
pytest-xdist>=1.14
88
flake8>=3.0.4
99
pylint>=1.5.4
10-
scipy
1110
ujson
1211
jinja2

0 commit comments

Comments
 (0)