kwimage.transform
¶
Objects for representing and manipulating image transforms.
Module Contents¶
Classes¶
Inherit from this class and define |
|
Base class for matrix-based transform. |
|
Base class for matrix-based transform. |
|
Currently just a stub class that may be used to implement projective / |
|
Helper for making affine transform matrices. |
Functions¶
|
- class kwimage.transform.Transform[source]¶
Bases:
ubelt.NiceRepr
Inherit from this class and define
__nice__
to “nicely” print your objects.Defines
__str__
and__repr__
in terms of__nice__
function Classes that inherit fromNiceRepr
should redefine__nice__
. If the inheriting class has a__len__
, method then the default__nice__
method will return its length.Example
>>> import ubelt as ub >>> class Foo(ub.NiceRepr): ... def __nice__(self): ... return 'info' >>> foo = Foo() >>> assert str(foo) == '<Foo(info)>' >>> assert repr(foo).startswith('<Foo(info) at ')
Example
>>> import ubelt as ub >>> class Bar(ub.NiceRepr): ... pass >>> bar = Bar() >>> import pytest >>> with pytest.warns(RuntimeWarning) as record: >>> assert 'object at' in str(bar) >>> assert 'object at' in repr(bar)
Example
>>> import ubelt as ub >>> class Baz(ub.NiceRepr): ... def __len__(self): ... return 5 >>> baz = Baz() >>> assert str(baz) == '<Baz(5)>'
Example
>>> import ubelt as ub >>> # If your nice message has a bug, it shouldn't bring down the house >>> class Foo(ub.NiceRepr): ... def __nice__(self): ... assert False >>> foo = Foo() >>> import pytest >>> with pytest.warns(RuntimeWarning) as record: >>> print('foo = {!r}'.format(foo)) foo = <...Foo ...>
- class kwimage.transform.Matrix(matrix)[source]¶
Bases:
Transform
Base class for matrix-based transform.
Example
>>> from kwimage.transform import * # NOQA >>> ms = {} >>> ms['random()'] = Matrix.random() >>> ms['eye()'] = Matrix.eye() >>> ms['random(3)'] = Matrix.random(3) >>> ms['random(4, 4)'] = Matrix.random(4, 4) >>> ms['eye(3)'] = Matrix.eye(3) >>> ms['explicit'] = Matrix(np.array([[1.618]])) >>> for k, m in ms.items(): >>> print('----') >>> print(f'{k} = {m}') >>> print(f'{k}.inv() = {m.inv()}') >>> print(f'{k}.T = {m.T}') >>> print(f'{k}.det() = {m.det()}')
- classmethod coerce(cls, data=None, **kwargs)[source]¶
Example
>>> Matrix.coerce({'type': 'matrix', 'matrix': [[1, 0, 0], [0, 1, 0]]}) >>> Matrix.coerce(np.eye(3)) >>> Matrix.coerce(None)
- __matmul__(self, other)[source]¶
Example
>>> m = {} >>> # Works, and returns a Matrix >>> m[len(m)] = x = Matrix.random() @ np.eye(2) >>> assert isinstance(x, Matrix) >>> m[len(m)] = x = Matrix.random() @ None >>> assert isinstance(x, Matrix) >>> # Works, and returns an ndarray >>> m[len(m)] = x = np.eye(3) @ Matrix.random(3) >>> assert isinstance(x, np.ndarray) >>> # These do not work >>> # m[len(m)] = None @ Matrix.random() >>> # m[len(m)] = np.eye(3) @ None >>> print('m = {}'.format(ub.repr2(m)))
- class kwimage.transform.Linear(matrix)[source]¶
Bases:
Matrix
Base class for matrix-based transform.
Example
>>> from kwimage.transform import * # NOQA >>> ms = {} >>> ms['random()'] = Matrix.random() >>> ms['eye()'] = Matrix.eye() >>> ms['random(3)'] = Matrix.random(3) >>> ms['random(4, 4)'] = Matrix.random(4, 4) >>> ms['eye(3)'] = Matrix.eye(3) >>> ms['explicit'] = Matrix(np.array([[1.618]])) >>> for k, m in ms.items(): >>> print('----') >>> print(f'{k} = {m}') >>> print(f'{k}.inv() = {m.inv()}') >>> print(f'{k}.T = {m.T}') >>> print(f'{k}.det() = {m.det()}')
- class kwimage.transform.Projective(matrix)[source]¶
Bases:
Linear
Currently just a stub class that may be used to implement projective / homography transforms in the future.
- class kwimage.transform.Affine(matrix)[source]¶
Bases:
Projective
Helper for making affine transform matrices.
Example
>>> self = Affine(np.eye(3)) >>> m1 = np.eye(3) @ self >>> m2 = self @ np.eye(3)
Example
>>> from kwimage.transform import * # NOQA >>> m = {} >>> # Works, and returns a Affine >>> m[len(m)] = x = Affine.random() @ np.eye(3) >>> assert isinstance(x, Affine) >>> m[len(m)] = x = Affine.random() @ None >>> assert isinstance(x, Affine) >>> # Works, and returns an ndarray >>> m[len(m)] = x = np.eye(3) @ Affine.random(3) >>> assert isinstance(x, np.ndarray) >>> # Works, and returns an Matrix >>> m[len(m)] = x = Affine.random() @ Matrix.random(3) >>> assert isinstance(x, Matrix) >>> m[len(m)] = x = Matrix.random(3) @ Affine.random() >>> assert isinstance(x, Matrix) >>> print('m = {}'.format(ub.repr2(m)))
- concise(self)[source]¶
Return a concise coercable dictionary representation of this matrix
- Returns
- a small serializable dict that can be passed
to
Affine.coerce()
to reconstruct this object.
- Return type
- Returns
dictionary with consise parameters
- Return type
Dict
Example
>>> self = Affine.random(rng=0, scale=1) >>> params = self.concise() >>> assert np.allclose(Affine.coerce(params).matrix, self.matrix) >>> print('params = {}'.format(ub.repr2(params, nl=1, precision=2))) params = { 'offset': (0.08, 0.38), 'theta': 0.08, 'type': 'affine', }
Example
>>> self = Affine.random(rng=0, scale=2, offset=0) >>> params = self.concise() >>> assert np.allclose(Affine.coerce(params).matrix, self.matrix) >>> print('params = {}'.format(ub.repr2(params, nl=1, precision=2))) params = { 'scale': 2.00, 'theta': 0.04, 'type': 'affine', }
- classmethod coerce(cls, data=None, **kwargs)[source]¶
Attempt to coerce the data into an affine object
- Parameters
data – some data we attempt to coerce to an Affine matrix
**kwargs – some data we attempt to coerce to an Affine matrix, mutually exclusive with data.
- Returns
Affine
Example
>>> import kwimage >>> kwimage.Affine.coerce({'type': 'affine', 'matrix': [[1, 0, 0], [0, 1, 0]]}) >>> kwimage.Affine.coerce({'scale': 2}) >>> kwimage.Affine.coerce({'offset': 3}) >>> kwimage.Affine.coerce(np.eye(3)) >>> kwimage.Affine.coerce(None) >>> kwimage.Affine.coerce(skimage.transform.AffineTransform(scale=30))
- decompose(self)[source]¶
Decompose the affine matrix into its individual scale, translation, rotation, and skew parameters.
- Returns
decomposed offset, scale, theta, and shear params
- Return type
Dict
References
https://math.stackexchange.com/questions/612006/decompose-affine
Example
>>> self = Affine.random() >>> params = self.decompose() >>> recon = Affine.coerce(**params) >>> params2 = recon.decompose() >>> pt = np.vstack([np.random.rand(2, 1), [1]]) >>> result1 = self.matrix[0:2] @ pt >>> result2 = recon.matrix[0:2] @ pt >>> assert np.allclose(result1, result2)
>>> self = Affine.scale(0.001) @ Affine.random() >>> params = self.decompose() >>> self.det()
- classmethod scale(cls, scale)[source]¶
Create a scale Affine object
- Parameters
scale (float | Tuple[float, float]) – x, y scale factor
- Returns
Affine
- classmethod translate(cls, offset)[source]¶
Create a translation Affine object
- Parameters
offset (float | Tuple[float, float]) – x, y translation factor
- Returns
Affine
- classmethod rotate(cls, theta)[source]¶
Create a rotation Affine object
- Parameters
theta (float) – counter-clockwise rotation angle in radians
- Returns
Affine
- classmethod random(cls, rng=None, **kw)[source]¶
Create a random Affine object
- Parameters
rng – random number generator
**kw – passed to
Affine.random_params()
. can contain coercable random distributions for scale, offset, about, theta, and shear.
- Returns
Affine
- classmethod random_params(cls, rng=None, **kw)[source]¶
- Parameters
rng – random number generator
**kw – can contain coercable random distributions for scale, offset, about, theta, and shear.
- Returns
affine parameters suitable to be passed to Affine.affine
- Return type
Dict
Todo
[ ] improve kwargs parameterization
- classmethod affine(cls, scale=None, offset=None, theta=None, shear=None, about=None)[source]¶
Create an affine matrix from high-level parameters
- Parameters
scale (float | Tuple[float, float]) – x, y scale factor
offset (float | Tuple[float, float]) – x, y translation factor
theta (float) – counter-clockwise rotation angle in radians
shear (float) – counter-clockwise shear angle in radians
about (float | Tuple[float, float]) – x, y location of the origin
- Returns
the constructed Affine object
- Return type
Example
>>> rng = kwarray.ensure_rng(None) >>> scale = rng.randn(2) * 10 >>> offset = rng.randn(2) * 10 >>> about = rng.randn(2) * 10 >>> theta = rng.randn() * 10 >>> shear = rng.randn() * 10 >>> # Create combined matrix from all params >>> F = Affine.affine( >>> scale=scale, offset=offset, theta=theta, shear=shear, >>> about=about) >>> # Test that combining components matches >>> S = Affine.affine(scale=scale) >>> T = Affine.affine(offset=offset) >>> R = Affine.affine(theta=theta) >>> H = Affine.affine(shear=shear) >>> O = Affine.affine(offset=about) >>> # combine (note shear must be on the RHS of rotation) >>> alt = O @ T @ R @ H @ S @ O.inv() >>> print('F = {}'.format(ub.repr2(F.matrix.tolist(), nl=1))) >>> print('alt = {}'.format(ub.repr2(alt.matrix.tolist(), nl=1))) >>> assert np.all(np.isclose(alt.matrix, F.matrix)) >>> pt = np.vstack([np.random.rand(2, 1), [[1]]]) >>> warp_pt1 = (F.matrix @ pt) >>> warp_pt2 = (alt.matrix @ pt) >>> assert np.allclose(warp_pt2, warp_pt1)
- Sympy:
>>> # xdoctest: +SKIP >>> import sympy >>> # Shows the symbolic construction of the code >>> # https://groups.google.com/forum/#!topic/sympy/k1HnZK_bNNA >>> from sympy.abc import theta >>> x0, y0, sx, sy, theta, shear, tx, ty = sympy.symbols( >>> 'x0, y0, sx, sy, theta, shear, tx, ty') >>> # move the center to 0, 0 >>> tr1_ = np.array([[1, 0, -x0], >>> [0, 1, -y0], >>> [0, 0, 1]]) >>> # Define core components of the affine transform >>> S = np.array([ # scale >>> [sx, 0, 0], >>> [ 0, sy, 0], >>> [ 0, 0, 1]]) >>> H = np.array([ # shear >>> [1, -sympy.sin(shear), 0], >>> [0, sympy.cos(shear), 0], >>> [0, 0, 1]]) >>> R = np.array([ # rotation >>> [sympy.cos(theta), -sympy.sin(theta), 0], >>> [sympy.sin(theta), sympy.cos(theta), 0], >>> [ 0, 0, 1]]) >>> T = np.array([ # translation >>> [ 1, 0, tx], >>> [ 0, 1, ty], >>> [ 0, 0, 1]]) >>> # Contruct the affine 3x3 about the origin >>> aff0 = np.array(sympy.simplify(T @ R @ H @ S)) >>> # move 0, 0 back to the specified origin >>> tr2_ = np.array([[1, 0, x0], >>> [0, 1, y0], >>> [0, 0, 1]]) >>> # combine transformations >>> aff = tr2_ @ aff0 @ tr1_ >>> print('aff = {}'.format(ub.repr2(aff.tolist(), nl=1)))