reimplement implicit parameter functionality of transformations

This commit is contained in:
Johannes Schönberger
2012-07-22 16:24:58 +02:00
parent 0f5d6153d2
commit 87c57770ba
2 changed files with 134 additions and 47 deletions
+99 -43
View File
@@ -30,11 +30,6 @@ def _stackcopy(a, b):
class GeometricTransform(object):
"""Perform geometric transformations on a set of coordinates.
Parameters
----------
matrix : 3x3 array, optional
Homogeneous transformation matrix.
"""
def __call__(self, coords):
"""Apply forward transformation.
@@ -99,6 +94,11 @@ class ProjectiveTransform(GeometricTransform):
[0 1 20]
[0 0 1 ]].
Parameters
----------
matrix : 3x3 array, optional
Homogeneous transformation matrix.
"""
_coefs = range(8)
@@ -201,22 +201,30 @@ class AffineTransform(ProjectiveTransform):
Parameters
----------
scale : (sx, sy), floats
Scale factors.
rotation : float
Rotation angle in radians, counter-clockwise direction.
shear : float
Shear angle in radians, counter-clockwise direction.
translation : (tx, ty), floats
Translation in x and y.
matrix : 3x3 array, optional
Homogeneous transformation matrix.
"""
_coefs = range(6)
def __init__(self, scale=None, rotation=None, shear=None, translation=None):
ProjectiveTransform.__init__(self)
def compose_implicit(self, scale=None, rotation=None, shear=None,
translation=None):
"""Set the transformation matrix with the implicit transformation
parameters.
Parameters
----------
scale : (sx, sy) as array, list or tuple
scale factors
rotation : float
rotation angle in counter-clockwise direction
shear : float
shear angle in counter-clockwise direction
translation : (tx, ty) as array, list or tuple
translation parameters
"""
if scale is None:
scale = (1, 1)
if rotation is None:
@@ -226,18 +234,35 @@ class AffineTransform(ProjectiveTransform):
if translation is None:
translation = (0, 0)
a = rotation
sx, sy = scale
tx, ty = translation
self._matrix = np.array([
[sx * math.cos(a), - sy * math.sin(a + shear), tx],
[sx * math.sin(a), sy * math.cos(a + shear), ty],
[0, 0, 1]
[sx * math.cos(rotation), - sy * math.sin(rotation + shear), 0],
[sx * math.sin(rotation), sy * math.cos(rotation + shear), 0],
[ 0, 0, 1]
])
self._matrix[0:2, 2] = translation
@property
def scale(self):
sx = math.sqrt(self._matrix[0, 0] ** 2 + self._matrix[1, 0] ** 2)
sy = math.sqrt(self._matrix[0, 1] ** 2 + self._matrix[1, 1] ** 2)
return sx, sy
@property
def rotation(self):
return math.atan2(self._matrix[1, 0], self._matrix[0, 0])
@property
def shear(self):
beta = math.atan2(- self._matrix[0, 1], self._matrix[1, 1])
return beta - self.rotation
@property
def translation(self):
return self._matrix[0:2, 2]
class SimilarityTransform(AffineTransform):
class SimilarityTransform(ProjectiveTransform):
"""2D similarity transformation of the form::
X = a0*x + b0*y + a1 =
@@ -254,23 +279,11 @@ class SimilarityTransform(AffineTransform):
Parameters
----------
scale : float, optional
Scale / zoom factor.
rotation : float, optional
Rotation angle, counter-clockwise, in radians.
translation : (tx, ty) of float
x, y translation parameters
matrix : 3x3 array, optional
Homogeneous transformation matrix.
"""
def __init__(self, scale=None, rotation=None, translation=None):
if scale is not None:
scale = (scale, scale)
AffineTransform.__init__(self, scale=scale,
rotation=rotation,
shear=0,
translation=translation)
def estimate(self, src, dst):
"""Set the transformation matrix with the explicit transformation
parameters.
@@ -305,6 +318,52 @@ class SimilarityTransform(AffineTransform):
[b0, a0, b1],
[ 0, 0, 1]])
def compose_implicit(self, scale=None, rotation=None, translation=None):
"""Set the transformation matrix with the implicit transformation
parameters.
Parameters
----------
scale : float, optional
scale factor
rotation : float, optional
rotation angle in counter-clockwise direction
translation : (tx, ty) as array, list or tuple, optional
x, y translation parameters
"""
if scale is None:
scale = (1, 1)
if rotation is None:
rotation = 0
if translation is None:
translation = (0, 0)
self._matrix = np.array([
[math.cos(rotation), - math.sin(rotation), 0],
[math.sin(rotation), math.cos(rotation), 0],
[ 0, 0, 1]
])
self._matrix *= scale
self._matrix[0:2, 2] = translation
@property
def scale(self):
if math.cos(self.rotation) == 0:
# sin(self.rotation) == 1
scale = self._matrix[0, 1]
else:
scale = self._matrix[0, 0] / math.cos(self.rotation)
return scale
@property
def rotation(self):
return math.atan2(self._matrix[1, 0], self._matrix[1, 1])
@property
def translation(self):
return self._matrix[0:2, 2]
class PolynomialTransform(GeometricTransform):
"""2D transformation of the form::
@@ -449,7 +508,7 @@ def estimate_transform(ttype, src, dst, **kwargs):
>>> tform = tf.estimate_transform('similarity', src, dst)
>>> tform.inverse(tform.forward(src)) # == src
>>> tform.inverse(tform(src)) # == src
>>> # warp image using the estimated transformation
>>> from skimage import data
@@ -458,15 +517,12 @@ def estimate_transform(ttype, src, dst, **kwargs):
>>> warp(image, inverse_map=tform.inverse)
>>> # create transformation with explicit parameters
>>> scale = 1.1
>>> rotation = 1
>>> translation = (10, 20)
>>>
>>> tform2 = tf.SimilarityTransform(scale, rotation, translation)
>>> tform2 = tf.SimilarityTransform()
>>> tform2.compose_implicit(scale=1.1, rotation=1, translation=(10, 20))
>>> # unite transformations, applied in order from left to right
>>> tform3 = tform + tform2
>>> tform3.forward(src) # == tform2.forward(tform.forward(src))
>>> tform3(src) # == tform2(tform(src))
"""
ttype = ttype.lower()
+35 -4
View File
@@ -1,5 +1,5 @@
import numpy as np
from numpy.testing import assert_array_almost_equal
from numpy.testing import assert_equal, assert_array_almost_equal
from skimage.transform._geometric import _stackcopy
from skimage.transform import (estimate_transform, SimilarityTransform,
@@ -43,12 +43,26 @@ def test_similarity_estimation():
tform = estimate_transform('similarity', SRC[:2, :], DST[:2, :])
assert_array_almost_equal(tform(SRC[:2, :]), DST[:2, :])
assert_array_almost_equal(tform.inverse(tform(SRC)), SRC)
assert_equal(tform._matrix[0, 0], tform._matrix[1, 1])
assert_equal(tform._matrix[0, 1], - tform._matrix[1, 0])
#: over-determined
tform = estimate_transform('similarity', SRC, DST)
assert_array_almost_equal(tform.inverse(tform(SRC)), SRC)
assert_equal(tform._matrix[0, 0], tform._matrix[1, 1])
assert_equal(tform._matrix[0, 1], - tform._matrix[1, 0])
def test_similarity_implicit():
tform = SimilarityTransform()
scale = 0.1
rotation = 1
translation = (1, 1)
tform.compose_implicit(scale, rotation, translation)
assert_array_almost_equal(tform.scale, scale)
assert_array_almost_equal(tform.rotation, rotation)
assert_array_almost_equal(tform.translation, translation)
def test_affine_estimation():
#: exact solution
@@ -61,6 +75,19 @@ def test_affine_estimation():
assert_array_almost_equal(tform.inverse(tform(SRC)), SRC)
def test_affine_implicit():
tform = AffineTransform()
scale = (0.1, 0.13)
rotation = 1
shear = 0.1
translation = (1, 1)
tform.compose_implicit(scale, rotation, shear, translation)
assert_array_almost_equal(tform.scale, scale)
assert_array_almost_equal(tform.rotation, rotation)
assert_array_almost_equal(tform.shear, shear)
assert_array_almost_equal(tform.translation, translation)
def test_projective():
#: exact solution
tform = estimate_transform('projective', SRC[:4, :], DST[:4, :])
@@ -77,9 +104,13 @@ def test_polynomial():
def test_union():
tform1 = SimilarityTransform(1, 0.3)
tform2 = SimilarityTransform(1, 0.6)
tform3 = SimilarityTransform(1, 0.9)
tform1 = SimilarityTransform()
tform1.compose_implicit(scale=0.1, rotation=0.3)
tform2 = SimilarityTransform()
tform2.compose_implicit(scale=0.1, rotation=0.9)
tform3 = SimilarityTransform()
tform3.compose_implicit(scale=0.1**2, rotation=0.3+0.9)
tform = tform1 + tform2