Skip to content

Commit 43d2387

Browse files
djkapnerfcollman
authored andcommitted
Tps adaptive mesh refinement (#132)
* adding adaptive mesh estimate for TPS size reduction * fixing className caps in json * added some documentation header * trivial change to retrigger build * trivial change to retrigger build * separating recursion from setup, raising max_iter exception * Update thin_plate_spline.py trivial change to retrigger build * another trivial change for rebuild * added doc string, default pass self to recursion, custom exception * subclassing custom exception as EstimationError * removing custom class __str__ method * removing test of exception __str__ * reverting to simpler estimation error exception
1 parent 721ac88 commit 43d2387

File tree

2 files changed

+185
-7
lines changed

2 files changed

+185
-7
lines changed

renderapi/transform/leaf/thin_plate_spline.py

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,15 @@
33
from renderapi.utils import encodeBase64, decodeBase64
44
from .transform import Transform
55
import scipy.spatial
6+
import logging
7+
import sys
68
__all__ = ['ThinPlateSplineTransform']
79

810

11+
logger = logging.getLogger(__name__)
12+
logger.addHandler(logging.StreamHandler(sys.stdout))
13+
14+
915
class ThinPlateSplineTransform(Transform):
1016
"""
1117
render-python class that can hold a dataString for
@@ -281,3 +287,127 @@ def dataString(self):
281287
b64_2 = encodeBase64(blk2)
282288

283289
return '{} {} {}'.format(header, b64_1, b64_2)
290+
291+
@staticmethod
292+
def mesh_refine(
293+
new_src,
294+
old_src,
295+
old_dst,
296+
old_tf=None,
297+
computeAffine=True,
298+
tol=1.0,
299+
max_iter=50,
300+
nworst=10,
301+
niter=0):
302+
"""recursive kernel for adaptive_mesh_estimate()
303+
Parameters
304+
----------
305+
new_src : numpy.array
306+
Nx2 array of new control source points. Adapts during recursion.
307+
Seeded by adaptive_mesh_estimate.
308+
old_src : numpy.array
309+
Nx2 array of orignal control source points.
310+
old_dst : numpy.array
311+
Nx2 array of orignal control destination points.
312+
old_tf : ThinPlateSplineTransform
313+
transform constructed from old_src and old_dst, passed through
314+
recursion iterations. Created if None.
315+
computeAffine : boolean
316+
whether returned transform will have aMtx
317+
tol : float
318+
in units of pixels, how close should the points match
319+
max_iter: int
320+
some limit on how many recursive attempts
321+
nworst : int
322+
per iteration, the nworst matching srcPts will be added
323+
niter : int
324+
passed through the recursion for stopping criteria
325+
326+
Returns
327+
-------
328+
ThinPlateSplineTransform
329+
"""
330+
331+
if old_tf is None:
332+
old_tf = ThinPlateSplineTransform()
333+
old_tf.estimate(old_src, old_dst, computeAffine=computeAffine)
334+
335+
new_tf = ThinPlateSplineTransform()
336+
new_tf.estimate(
337+
new_src,
338+
old_tf.tform(new_src),
339+
computeAffine=computeAffine)
340+
new_dst = new_tf.tform(old_src)
341+
342+
delta = np.linalg.norm(new_dst - old_dst, axis=1)
343+
ind = np.argwhere(delta > tol).flatten()
344+
345+
if ind.size == 0:
346+
return new_tf
347+
348+
if niter == max_iter:
349+
raise EstimationError(
350+
"Max number of iterations ({}) reached in"
351+
" ThinPlateSplineTransform.mesh_refine()".format(
352+
max_iter))
353+
354+
sortind = np.argsort(delta[ind])
355+
new_src = np.vstack((new_src, old_src[ind[sortind[0: nworst]]]))
356+
357+
return ThinPlateSplineTransform.mesh_refine(
358+
new_src,
359+
old_src,
360+
old_dst,
361+
old_tf=old_tf,
362+
computeAffine=computeAffine,
363+
tol=tol,
364+
max_iter=max_iter,
365+
nworst=nworst,
366+
niter=(niter + 1))
367+
368+
def adaptive_mesh_estimate(
369+
self,
370+
starting_grid=7,
371+
computeAffine=True,
372+
tol=1.0,
373+
max_iter=50,
374+
nworst=10):
375+
"""method for creating a transform with fewer control points
376+
that matches the original transfom within some tolerance.
377+
Parameters
378+
----------
379+
starting_grid : int
380+
estimate will start with an n x n grid
381+
computeAffine : boolean
382+
whether returned transform will have aMtx
383+
tol : float
384+
in units of pixels, how close should the points match
385+
max_iter: int
386+
some limit on how many recursive attempts
387+
nworst : int
388+
per iteration, the nworst matching srcPts will be added
389+
390+
Returns
391+
-------
392+
ThinPlateSplineTransform
393+
"""
394+
395+
mn = self.srcPts.min(axis=1)
396+
mx = self.srcPts.max(axis=1)
397+
xt, yt = np.meshgrid(
398+
np.linspace(mn[0], mx[0], starting_grid),
399+
np.linspace(mn[1], mx[1], starting_grid))
400+
new_src = np.vstack((xt.flatten(), yt.flatten())).transpose()
401+
old_src = self.srcPts.transpose()
402+
old_dst = self.tform(old_src)
403+
404+
return ThinPlateSplineTransform.mesh_refine(
405+
new_src,
406+
old_src,
407+
old_dst,
408+
old_tf=self,
409+
computeAffine=computeAffine,
410+
tol=tol,
411+
max_iter=max_iter,
412+
nworst=nworst,
413+
niter=0)

test/test_transform.py

Lines changed: 55 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -170,13 +170,13 @@ def test_invert_Affine():
170170
def test_polynomial_scale():
171171
p = np.transpose(np.array([[0, 0]]))
172172
t = renderapi.transform.Polynomial2DTransform(params=p)
173-
assert(t.order==0)
174-
assert(t.scale==(1.0, 1.0))
175-
173+
assert(t.order == 0)
174+
assert(t.scale == (1.0, 1.0))
175+
176176
p = np.transpose(np.array([[0, 0], [1.1, 0], [0, 0.9]]))
177177
t = renderapi.transform.Polynomial2DTransform(params=p)
178-
assert(t.order==1)
179-
assert(t.scale==(1.1, 0.9))
178+
assert(t.order == 1)
179+
assert(t.scale == (1.1, 0.9))
180180

181181

182182
def test_Polynomial_estimation(use_numpy=False):
@@ -835,12 +835,12 @@ def test_thinplatespline():
835835
with pytest.raises(renderapi.errors.RenderError):
836836
s = t2.dataString.split(' ')
837837
s[1] = str(int(s[1])+1)
838-
t3 = renderapi.transform.ThinPlateSplineTransform(
838+
renderapi.transform.ThinPlateSplineTransform(
839839
dataString=" ".join(s))
840840
with pytest.raises(renderapi.errors.RenderError):
841841
s = t2.dataString.split(' ')
842842
s[2] = str(int(s[2])-4)
843-
t3 = renderapi.transform.ThinPlateSplineTransform(
843+
renderapi.transform.ThinPlateSplineTransform(
844844
dataString=" ".join(s))
845845

846846
x = np.linspace(0, 3840, 10)
@@ -963,3 +963,51 @@ def test_encode64():
963963
s = renderapi.utils.encodeBase64(x)
964964
y = renderapi.utils.decodeBase64(s)
965965
assert(np.all(x == y))
966+
967+
968+
def test_adaptive_estimate():
969+
with open(rendersettings.TEST_THINPLATESPLINE_FILE, 'r') as f:
970+
j = json.load(f)
971+
972+
tf = renderapi.transform.ThinPlateSplineTransform(
973+
dataString=j['dataString'])
974+
975+
tol = 1.0
976+
ntf = tf.adaptive_mesh_estimate(tol=1.0)
977+
978+
src = ntf.srcPts.transpose()
979+
dsta = tf.tform(src)
980+
dstb = ntf.tform(src)
981+
assert(np.linalg.norm(dsta - dstb, axis=1).max() <= tol)
982+
983+
src = tf.srcPts.transpose()
984+
dsta = tf.tform(src)
985+
dstb = ntf.tform(src)
986+
nover = np.argwhere(np.linalg.norm(dsta - dstb, axis=1) >= tol).size
987+
assert(nover == 0)
988+
989+
with pytest.raises(renderapi.errors.EstimationError):
990+
tf.adaptive_mesh_estimate(max_iter=1)
991+
992+
# invoke the recursion directly, without passing self
993+
mn = tf.srcPts.min(axis=1)
994+
mx = tf.srcPts.max(axis=1)
995+
xt, yt = np.meshgrid(
996+
np.linspace(mn[0], mx[0], 5),
997+
np.linspace(mn[1], mx[1], 5))
998+
new_src = np.vstack((xt.flatten(), yt.flatten())).transpose()
999+
old_src = tf.srcPts.transpose()
1000+
old_dst = tf.tform(old_src)
1001+
ntf = tf.mesh_refine(
1002+
new_src,
1003+
old_src,
1004+
old_dst,
1005+
old_tf=None,
1006+
computeAffine=True,
1007+
tol=1.0,
1008+
max_iter=50,
1009+
nworst=10,
1010+
niter=0)
1011+
dsta = tf.tform(src)
1012+
dstb = ntf.tform(src)
1013+
assert(np.linalg.norm(dsta - dstb, axis=1).max() <= tol)

0 commit comments

Comments
 (0)