Source code for menpo.transform.homogeneous.scale
import numpy as np
from .base import HomogFamilyAlignment
from .affine import DiscreteAffine, Affine
from .similarity import Similarity
[docs]def Scale(scale_factor, n_dims=None):
r"""
Factory function for producing Scale transforms. Zero scale factors are not
permitted.
A :class:`UniformScale` will be produced if:
- A float `scale_factor` and a `n_dims` kwarg are provided
- A ndarray scale_factor with shape (`n_dims`, ) is provided with all
elements being the same
A :class:`NonUniformScale` will be provided if:
- A ndarray `scale_factor` with shape (`n_dims`, ) is provided with
at least two differing scale factors.
Parameters
----------
scale_factor: double or (D,) ndarray
Scale for each axis.
n_dims: int
The dimensionality of the output transform.
Returns
-------
scale : :class:`UniformScale` or :class:`NonUniformScale`
The correct type of scale
Raises
-------
ValueError
If any of the scale factors is zero
"""
from numbers import Number
if not isinstance(scale_factor, Number):
# some array like thing - make it a numpy array for sure
scale_factor = np.asarray(scale_factor)
if not np.all(scale_factor):
raise ValueError('Having a zero in one of the scales is invalid')
if n_dims is None:
# scale_factor better be a numpy array then
if np.allclose(scale_factor, scale_factor[0]):
return UniformScale(scale_factor[0], scale_factor.shape[0])
else:
return NonUniformScale(scale_factor)
else:
# interpret as a scalar then
return UniformScale(scale_factor, n_dims)
[docs]class NonUniformScale(DiscreteAffine, Affine):
r"""
An `n_dims` scale transform, with a scale component for each dimension.
Parameters
----------
scale : ``(n_dims,)`` `ndarray`
A scale for each axis.
"""
def __init__(self, scale, skip_checks=False):
scale = np.asarray(scale)
if not skip_checks:
if scale.size > 3 or scale.size < 2:
raise ValueError("NonUniformScale can only be 2D or 3D"
", not {}".format(scale.size))
h_matrix = np.eye(scale.size + 1)
np.fill_diagonal(h_matrix, scale)
h_matrix[-1, -1] = 1
Affine.__init__(self, h_matrix, skip_checks=True, copy=False)
@classmethod
def identity(cls, n_dims):
return NonUniformScale(np.ones(n_dims))
@property
def h_matrix_is_mutable(self):
return False
@property
def scale(self):
r"""
The scale vector.
:type: (D,) ndarray
"""
# Copy the vector as Numpy 1.10 will return a writeable view
return self.h_matrix.diagonal()[:-1].copy()
def _transform_str(self):
message = 'NonUniformScale by {}'.format(self.scale)
return message
@property
def n_parameters(self):
"""
The number of parameters: `n_dims`.
:type: int
`n_dims` parameters - `[scale_x, scale_y, ....]` - The scalar values
representing the scale across each axis.
"""
return self.scale.size
def _as_vector(self):
r"""
Return the parameters of the transform as a 1D array. These parameters
are parametrised as deltas from the identity warp. The parameters
are output in the order [s0, s1, ...].
+----------+--------------------------------------------+
|parameter | definition |
+==========+============================================+
|s0 | The scale across the first axis |
+----------+--------------------------------------------+
|s1 | The scale across the second axis |
+----------+--------------------------------------------+
|... | ... |
+----------+--------------------------------------------+
|sn | The scale across the nth axis |
+----------+--------------------------------------------+
Returns
-------
s : (D,) ndarray
The scale across each axis.
"""
return self.scale
[docs] def from_vector_inplace(self, vector):
r"""
Updates the NonUniformScale inplace.
Parameters
----------
vector : (D,) ndarray
The array of parameters.
"""
np.fill_diagonal(self.h_matrix, vector)
self.h_matrix[-1, -1] = 1
@property
def composes_inplace_with(self):
return NonUniformScale, UniformScale
[docs] def pseudoinverse(self):
"""
The inverse scale.
:type: :class:`NonUniformScale`
"""
return NonUniformScale(1.0 / self.scale, skip_checks=True)
[docs]class UniformScale(DiscreteAffine, Similarity):
r"""
An abstract similarity scale transform, with a single scale component
applied to all dimensions. This is abstracted out to remove unnecessary
code duplication.
"""
def __init__(self, scale, n_dims, skip_checks=False):
if not skip_checks:
if n_dims > 3 or n_dims < 2:
raise ValueError("UniformScale can only be 2D or 3D"
", not {}".format(n_dims))
h_matrix = np.eye(n_dims + 1)
np.fill_diagonal(h_matrix, scale)
h_matrix[-1, -1] = 1
Similarity.__init__(self, h_matrix, copy=False,
skip_checks=True)
@classmethod
def identity(cls, n_dims):
return UniformScale(1, n_dims)
@property
def scale(self):
r"""
The single scale value.
:type: double
"""
return self.h_matrix[0, 0]
def _transform_str(self):
message = 'UniformScale by {}'.format(self.scale)
return message
@property
def n_parameters(self):
r"""
The number of parameters: 1
:type: int
"""
return 1
def _as_vector(self):
r"""
Return the parameters of the transform as a 1D array. These parameters
are parametrised as deltas from the identity warp. The parameters
are output in the order [s].
+----------+--------------------------------+
|parameter | definition |
+==========+================================+
|s | The scale across each axis |
+----------+--------------------------------+
Returns
-------
s : double
The scale across each axis.
"""
return np.asarray(self.scale)
def from_vector_inplace(self, p):
np.fill_diagonal(self.h_matrix, p)
self.h_matrix[-1, -1] = 1
@property
def composes_inplace_with(self):
return UniformScale
[docs] def pseudoinverse(self):
r"""
The inverse scale.
:type: type(self)
"""
return UniformScale(1.0 / self.scale, self.n_dims, skip_checks=True)
[docs]class AlignmentUniformScale(HomogFamilyAlignment, UniformScale):
def __init__(self, source, target):
HomogFamilyAlignment.__init__(self, source, target)
UniformScale.__init__(self, target.norm() / source.norm(),
source.n_dims)
def from_vector_inplace(self, p):
UniformScale.from_vector_inplace(self, p)
self._sync_target_from_state()
def _sync_state_from_target(self):
new_scale = self.target.norm() / self.source.norm()
np.fill_diagonal(self.h_matrix, new_scale)
self.h_matrix[-1, -1] = 1
[docs] def as_non_alignment(self):
r"""Returns a copy of this uniform scale without it's alignment nature.
Returns
-------
transform : :map:`UniformScale`
A version of this scale with the same transform behavior but
without the alignment logic.
"""
return UniformScale(self.scale, self.n_dims)