From 92f86432187f8f92b81a2af9ac9fa017bbfbaaf4 Mon Sep 17 00:00:00 2001 From: Tim Sheerman-Chase Date: Fri, 31 Aug 2012 18:40:17 +0100 Subject: [PATCH 1/7] Initial work to introduce piecewise affine transform --- skimage/transform/__init__.py | 5 ++- skimage/transform/_geometric.py | 73 ++++++++++++++++++++++++++++++++- 2 files changed, 74 insertions(+), 4 deletions(-) diff --git a/skimage/transform/__init__.py b/skimage/transform/__init__.py index 0907544b..317d8e3d 100644 --- a/skimage/transform/__init__.py +++ b/skimage/transform/__init__.py @@ -4,5 +4,6 @@ from .finite_radon_transform import * from .integral import * from ._geometric import (warp, warp_coords, estimate_transform, SimilarityTransform, AffineTransform, - ProjectiveTransform, PolynomialTransform) -from ._warps import resize, rotate, swirl, homography + ProjectiveTransform, PolynomialTransform, + PiecewiseAffineTransform) +from ._warps import swirl, homography diff --git a/skimage/transform/_geometric.py b/skimage/transform/_geometric.py index 0f76b50e..0aa3d26f 100644 --- a/skimage/transform/_geometric.py +++ b/skimage/transform/_geometric.py @@ -1,6 +1,6 @@ import math import numpy as np -from scipy import ndimage +from scipy import ndimage, spatial from skimage.util import img_as_float from ._warps_cy import _warp_fast @@ -580,6 +580,76 @@ class PolynomialTransform(GeometricTransform): 'parameters by exchanging source and destination coordinates,' 'then apply the forward transformation.') +class PiecewiseAffineTransform(ProjectiveTransform): + + """2D piecewise affine transformation. + + Parameters + ---------- + TODO + + """ + + def __init__(self): + pass + + def estimate(self, src, dst): + + #Convert input to correct types + dstPoints = np.array(dst) + srcPoints = np.array(src) + + #Split input shape into mesh + self.tess = spatial.Delaunay(srcPoints) + + #Calculate ROI in source control points + xmin, xmax = srcPoints[:,0].min(), srcPoints[:,0].max() + ymin, ymax = srcPoints[:,1].min(), srcPoints[:,1].max() + + #Find affine mapping from input positions to mean shape + self.triAffines = [] + for tri in self.tess.vertices: + srcTri = np.hstack((srcPoints[tri,:], np.ones((3,1)))).transpose() + dstTri = np.hstack((dstPoints[tri,:], np.ones((3,1)))).transpose() + + affine = AffineTransform() + affine.estimate(srcTri, dstTri) + self.triAffines.append(affine) + + def __call__(self, coords): + """Apply forward transformation. + + Parameters + ---------- + coords : (N, 2) array + source coordinates + + Returns + ------- + coords : (N, 2) array + Transformed coordinates. + + """ + + out = np.ones((coords.shape[0], 2)) * -1 + + for ptNum, pt in enumerate(coords): + #Determine which triangle contains the point + simplexIndex = self.tess.find_simplex(pt) + + if simplexIndex == -1: + #This point is outside the hull of the control points + out[ptNum,0] = 0 + out[ptNum,1] = 0 + continue + + #Calculate position in the input image + affine = self.triAffines[simplexIndex] + destPos = affine(pt) + out[ptNum,0] = destPos[0][0] + out[ptNum,1] = destPos[0][1] + + return out TRANSFORMS = { 'similarity': SimilarityTransform, @@ -593,7 +663,6 @@ HOMOGRAPHY_TRANSFORMS = ( ProjectiveTransform ) - def estimate_transform(ttype, src, dst, **kwargs): """Estimate 2D geometric transformation parameters. From be1143046bb7589fb8f61b1a8a5f2ba5fcf1745b Mon Sep 17 00:00:00 2001 From: Tim Sheerman-Chase Date: Fri, 31 Aug 2012 19:06:26 +0100 Subject: [PATCH 2/7] Achieved functional piecewise affine transform --- skimage/transform/_geometric.py | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/skimage/transform/_geometric.py b/skimage/transform/_geometric.py index 0aa3d26f..8321ba1e 100644 --- a/skimage/transform/_geometric.py +++ b/skimage/transform/_geometric.py @@ -591,29 +591,19 @@ class PiecewiseAffineTransform(ProjectiveTransform): """ def __init__(self): - pass + self.tess = None + self.triAffines = [] def estimate(self, src, dst): - #Convert input to correct types - dstPoints = np.array(dst) - srcPoints = np.array(src) - #Split input shape into mesh - self.tess = spatial.Delaunay(srcPoints) - - #Calculate ROI in source control points - xmin, xmax = srcPoints[:,0].min(), srcPoints[:,0].max() - ymin, ymax = srcPoints[:,1].min(), srcPoints[:,1].max() + self.tess = spatial.Delaunay(src) #Find affine mapping from input positions to mean shape self.triAffines = [] for tri in self.tess.vertices: - srcTri = np.hstack((srcPoints[tri,:], np.ones((3,1)))).transpose() - dstTri = np.hstack((dstPoints[tri,:], np.ones((3,1)))).transpose() - affine = AffineTransform() - affine.estimate(srcTri, dstTri) + affine.estimate(src[tri,:], dst[tri,:]) self.triAffines.append(affine) def __call__(self, coords): @@ -639,8 +629,8 @@ class PiecewiseAffineTransform(ProjectiveTransform): if simplexIndex == -1: #This point is outside the hull of the control points - out[ptNum,0] = 0 - out[ptNum,1] = 0 + out[ptNum,0] = -1 + out[ptNum,1] = -1 continue #Calculate position in the input image @@ -656,6 +646,7 @@ TRANSFORMS = { 'affine': AffineTransform, 'projective': ProjectiveTransform, 'polynomial': PolynomialTransform, + 'piecewiseaffine': PiecewiseAffineTransform, } HOMOGRAPHY_TRANSFORMS = ( SimilarityTransform, @@ -673,7 +664,7 @@ def estimate_transform(ttype, src, dst, **kwargs): Parameters ---------- - ttype : {'similarity', 'affine', 'projective', 'polynomial'} + ttype : {'similarity', 'affine', 'piecewiseaffine', 'projective', 'polynomial'} Type of transform. kwargs : array or int Function parameters (src, dst, n, angle):: @@ -681,6 +672,7 @@ def estimate_transform(ttype, src, dst, **kwargs): NAME / TTYPE FUNCTION PARAMETERS 'similarity' `src, `dst` 'affine' `src, `dst` + 'piecewiseaffine' `src, `dst` 'projective' `src, `dst` 'polynomial' `src, `dst`, `order` (polynomial order) From 07d4e333f03a89c7559e61a3c3dba92d153c31aa Mon Sep 17 00:00:00 2001 From: Tim Sheerman-Chase Date: Fri, 31 Aug 2012 19:12:57 +0100 Subject: [PATCH 3/7] Update doc strings with piecewise affine --- skimage/transform/_geometric.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/skimage/transform/_geometric.py b/skimage/transform/_geometric.py index 8321ba1e..8771542b 100644 --- a/skimage/transform/_geometric.py +++ b/skimage/transform/_geometric.py @@ -584,6 +584,10 @@ class PiecewiseAffineTransform(ProjectiveTransform): """2D piecewise affine transformation. + Control points are used to define the mapping. The transform is based on + a Delaunay triangulation of the points to form a mesh. Each triangle is + used to find a local affine transform. + Parameters ---------- TODO From 5617bc563fcd1bd4d75001c3063f7c1dfc1380a4 Mon Sep 17 00:00:00 2001 From: Tim Sheerman-Chase Date: Fri, 31 Aug 2012 22:47:59 +0100 Subject: [PATCH 4/7] Improve comments --- skimage/transform/_geometric.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/skimage/transform/_geometric.py b/skimage/transform/_geometric.py index 8771542b..785765d6 100644 --- a/skimage/transform/_geometric.py +++ b/skimage/transform/_geometric.py @@ -600,10 +600,10 @@ class PiecewiseAffineTransform(ProjectiveTransform): def estimate(self, src, dst): - #Split input shape into mesh + #Triangulate input positions into mesh self.tess = spatial.Delaunay(src) - #Find affine mapping from input positions to mean shape + #Find affine mapping from source positions to destination self.triAffines = [] for tri in self.tess.vertices: affine = AffineTransform() @@ -637,7 +637,7 @@ class PiecewiseAffineTransform(ProjectiveTransform): out[ptNum,1] = -1 continue - #Calculate position in the input image + #Calculate affine transformed position affine = self.triAffines[simplexIndex] destPos = affine(pt) out[ptNum,0] = destPos[0][0] @@ -650,7 +650,7 @@ TRANSFORMS = { 'affine': AffineTransform, 'projective': ProjectiveTransform, 'polynomial': PolynomialTransform, - 'piecewiseaffine': PiecewiseAffineTransform, + 'piecewiseaffine': PiecewiseAffineTransform, } HOMOGRAPHY_TRANSFORMS = ( SimilarityTransform, From 839442aad2e7efdfcb8766a86eaf570035eab8e0 Mon Sep 17 00:00:00 2001 From: Tim Sheerman-Chase Date: Fri, 31 Aug 2012 22:53:39 +0100 Subject: [PATCH 5/7] Improve comments --- skimage/transform/_geometric.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/skimage/transform/_geometric.py b/skimage/transform/_geometric.py index 785765d6..9869fd87 100644 --- a/skimage/transform/_geometric.py +++ b/skimage/transform/_geometric.py @@ -599,6 +599,18 @@ class PiecewiseAffineTransform(ProjectiveTransform): self.triAffines = [] def estimate(self, src, dst): + """Set the control points with which to perform the piecewise affine mapping. + + Number of source and destination coordinates must match. + + Parameters + ---------- + src : (N, 2) array + Source coordinates. + dst : (N, 2) array + Destination coordinates. + + """ #Triangulate input positions into mesh self.tess = spatial.Delaunay(src) From ea5fe5ff9cf941ea13fdf4f5d7578fea64d6a335 Mon Sep 17 00:00:00 2001 From: Tim Sheerman-Chase Date: Sat, 1 Sep 2012 12:38:25 +0100 Subject: [PATCH 6/7] Setting _matrix to None, because the transform is not of this form --- skimage/transform/_geometric.py | 1 + 1 file changed, 1 insertion(+) diff --git a/skimage/transform/_geometric.py b/skimage/transform/_geometric.py index 9869fd87..030197da 100644 --- a/skimage/transform/_geometric.py +++ b/skimage/transform/_geometric.py @@ -597,6 +597,7 @@ class PiecewiseAffineTransform(ProjectiveTransform): def __init__(self): self.tess = None self.triAffines = [] + self._matrix = None def estimate(self, src, dst): """Set the control points with which to perform the piecewise affine mapping. From 3e3e0928d72c9add5e3b36163e75a7a564d4b65e Mon Sep 17 00:00:00 2001 From: Tim Sheerman-Chase Date: Sat, 1 Sep 2012 12:41:23 +0100 Subject: [PATCH 7/7] Wrap long line --- skimage/transform/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/transform/__init__.py b/skimage/transform/__init__.py index 317d8e3d..48a252b2 100644 --- a/skimage/transform/__init__.py +++ b/skimage/transform/__init__.py @@ -4,6 +4,6 @@ from .finite_radon_transform import * from .integral import * from ._geometric import (warp, warp_coords, estimate_transform, SimilarityTransform, AffineTransform, - ProjectiveTransform, PolynomialTransform, + ProjectiveTransform, PolynomialTransform, PiecewiseAffineTransform) from ._warps import swirl, homography