Module transformnd.transforms
Implementations of some common transforms.
Expand source code
"""Implementations of some common transforms."""
from .affine import Affine
from .reflection import Reflect
from .simple import Identity, Scale, Translate
__all__ = ["Affine", "Identity", "Reflect", "Scale", "Translate"]
Sub-modules
transformnd.transforms.affine-
Rigid transformations implemented as affine multiplications.
transformnd.transforms.moving_least_squares-
Implementation of Moving Least Squares transformation …
transformnd.transforms.reflectiontransformnd.transforms.simple-
Simple transformations like rigid translation and scaling.
transformnd.transforms.thinplate-
Thin plate splines transformations.
Classes
class Affine (matrix: ArrayLike, *, spaces: SpaceTuple = (None, None))-
Helper class that provides a standard way to create an ABC using inheritance.
Affine transformation using an augmented matrix.
Matrix must have shape (D+1, D+1) or (D, D+1); the bottom row is assumed to be [0, 0, …, 0, 1].
Parameters
matrix:ArrayLike- Affine transformation matrix.
spaces:tuple[SpaceRef, SpaceRef]- Optional source and target spaces
Raises
ValueError- Malformed matrix.
Expand source code
class Affine(Transform): def __init__( self, matrix: ArrayLike, *, spaces: SpaceTuple = (None, None), ): """Affine transformation using an augmented matrix. Matrix must have shape (D+1, D+1) or (D, D+1); the bottom row is assumed to be [0, 0, ..., 0, 1]. Parameters ---------- matrix : ArrayLike Affine transformation matrix. spaces : tuple[SpaceRef, SpaceRef] Optional source and target spaces Raises ------ ValueError Malformed matrix. """ super().__init__(spaces=spaces) m = np.asarray(matrix) if m.ndim != 2: raise ValueError("Transformation matrix must be 2D") if m.shape[1] == m.shape[0] + 1: base = np.eye(m.shape[1]) base[:-1, :] = m m = base elif not is_square(m): raise ValueError("Transformation matrix must be square") self.matrix = m self.ndim = {len(self.matrix) - 1} def apply(self, coords: np.ndarray) -> np.ndarray: coords = self._validate_coords(coords) # todo: replace with writing into full ones? coords = np.concatenate( [coords, np.ones((coords.shape[0], 1), dtype=coords.dtype)], axis=1 ) return (self.matrix @ coords.T).T[:, :-1] def __invert__(self) -> Transform: return type(self)( np.linalg.inv(self.matrix), spaces=self.spaces[::-1], ) def __matmul__(self, rhs: Affine) -> Affine: """Compose two affine transforms by matrix multiplication. Parameters ---------- rhs : AffineTransform Returns ------- AffineTransform Raises ------ ValueError Incompatible transforms. """ if not isinstance(rhs, Affine): return NotImplemented if self.matrix.shape != rhs.matrix.shape: raise ValueError( "Cannot multiply affine matrices of different dimensionality" ) if not none_eq(self.target_space, rhs.source_space): raise ValueError("Affine transforms do not share a space") return Affine( self.matrix @ rhs.matrix, spaces=(self.source_space, rhs.target_space), ) @classmethod def from_linear_map( cls, linear_map: ArrayLike, translation=0, *, spaces: SpaceTuple = (None, None), ) -> Affine: """Create an augmented affine matrix from a linear map, with an optional translation. Parameters ---------- linear_map : ArrayLike Shape (D, D) translation : ArrayLike, optional Translation to add to the matrix, by default None spaces : tuple[SpaceRef, SpaceRef] Optional source and target spaces Returns ------- AffineTransform """ lin_map = np.asarray(linear_map) side = len(lin_map) + 1 matrix = np.eye(side, dtype=lin_map.dtype) matrix[:-1, :] = lin_map matrix[:-1, -1] = translation return cls(matrix, spaces=spaces) @classmethod def identity( cls, ndim: int, *, spaces: SpaceTuple = (None, None), ) -> Affine: """Create an identity affine transformation. Parameters ---------- ndim : int spaces : tuple[SpaceRef, SpaceRef] Optional source and target spaces Returns ------- AffineTransform """ return cls( np.eye(ndim + 1), spaces=spaces, ) @classmethod def translation( cls, translation: ArrayLike, ndim: Optional[int] = None, *, spaces: SpaceTuple = (None, None), ) -> Affine: """Create an affine translation. Parameters ---------- translation : ArrayLike If scalar, broadcast to ndim. ndim : int, optional If translation is scalar, how many dims to use. spaces : tuple[SpaceRef, SpaceRef] Optional source and target spaces Returns ------- AffineTransform """ t = arg_as_array(translation, ndim) m = np.eye(len(t) + 1, dtype=t.dtype) m[:-1, -1] = t return cls(m, spaces=spaces) @classmethod def scaling( cls, scale: ArrayLike, ndim: Optional[int] = None, *, spaces: SpaceTuple = (None, None), ) -> Affine: """Create an affine scaling. Parameters ---------- scale : ArrayLike If scalar, broadcast to ndim. ndim : Optional[int], optional If scale is scalar, how many dimensions to use spaces : tuple[SpaceRef, SpaceRef] Optional source and target spaces Returns ------- AffineTransform [description] """ s = arg_as_array(scale, ndim) m = np.eye(len(s) + 1, dtype=s.dtype) m[:-1, :-1] *= s return cls(m, spaces=spaces) @classmethod def reflection( cls, axis: Union[int, Container[int]], ndim: int, *, spaces: SpaceTuple = (None, None), ) -> Affine: """Create an affine reflection. Parameters ---------- axis : Union[int, Container[int]] A single axis or multiple to reflect in. ndim : int How many dimensions to work in. spaces : tuple[SpaceRef, SpaceRef] Optional source and target spaces Returns ------- AffineTransform """ if np.isscalar(axis): axis = [axis] # type: ignore values = [-1 if idx in axis else 1 for idx in range(ndim)] # type:ignore m = np.eye(ndim + 1) m[:-1, :-1] *= values return cls(m, spaces=spaces) @classmethod def rotation2( cls, rotation: float, degrees=True, clockwise=False, *, spaces: SpaceTuple = (None, None), ) -> Affine: """Create a 2D affine rotation. Parameters ---------- rotation : float Angle to rotate. degrees : bool, optional Whether rotation is in degrees (rather than radians), by default True clockwise : bool, optional Whether rotation is clockwise, by default False spaces : tuple[SpaceRef, SpaceRef] Optional source and target spaces Returns ------- AffineTransform """ if degrees: rotation = np.deg2rad(rotation) if clockwise: rotation *= -1 vals = [ [np.cos(rotation), -np.sin(rotation)], [np.sin(rotation), np.cos(rotation)], ] m = np.eye(3) m[:-1, :-1] = vals return cls(m, spaces=spaces) @classmethod def rotation3( cls, rotation: Union[float, Tuple[float, float, float]], degrees=True, clockwise=False, order=(0, 1, 2), *, spaces: SpaceTuple = (None, None), ) -> Affine: """Create a 3D affine rotation. Parameters ---------- rotation : Union[float, Tuple[float, float, float]] Either a single rotation for all axes, or 1 for each. degrees : bool, optional Whether rotation is in degrees (rather than radians), by default True clockwise : bool, optional Whether rotation is clockwise, by default False order : tuple, optional What order to apply the rotations, by default (0, 1, 2) spaces : tuple[SpaceRef, SpaceRef] Optional source and target spaces Returns ------- AffineTransform Raises ------ ValueError Incompatible order. """ if np.isscalar(rotation): r = np.array([rotation] * 3) else: r = np.asarray(rotation) if degrees: r = np.deg2rad(r) if clockwise: r *= -1 if len(order) != 3 or set(order) != {0, 1, 2}: raise ValueError("Order must contain only 0, 1, 2 in any order.") order = list(order) rots = [ np.array( [ [1, 0, 0], [0, np.cos(r[0]), -np.sin(r[0])], [0, np.sin(r[0]), np.cos(r[0])], ] ), np.array( [ [np.cos(r[1]), 0, np.sin(r[1])], [0, 1, 0], [-np.sin(r[1]), 0, np.cos(r[1])], ] ), np.array( [ [np.cos(r[2]), -np.sin(r[2]), 0], [np.sin(r[2]), np.cos(r[2]), 0], [0, 0, 1], ] ), ] rot = rots[order.pop(0)] rot @= rots[order.pop(0)] rot @= rots[order.pop(0)] m = np.eye(4) m[:-1, :-1] = rot return cls(m, spaces=spaces) @classmethod def shearing( cls, factor: Union[float, np.ndarray], ndim: Optional[int] = None, *, spaces: SpaceTuple = (None, None), ) -> Affine: """Create an affine shear. `factor` can be a scalar to broadcast to all dimensions, or a D-length list of D-1 lists. The first inner list contains the shear factors in the first dimension for all *but* the first dimension. The second inner list contains the shear factors in the second dimension for all the *but* the second dimension, etc. Parameters ---------- factor : Union[float, np.ndarray] Shear scale factors; see above for more details. ndim : Optional[int], optional If factor is scalar, broadcast to this many dimensions, by default None spaces : tuple[SpaceRef, SpaceRef] Optional source and target spaces Returns ------- AffineTransform Raises ------ ValueError Incompatible factor. """ if np.isscalar(factor): if ndim is None: raise ValueError("If factor is scalar, ndim must be defined") s = np.full((ndim, ndim - 1), factor) else: s = np.asarray(factor) if s.shape[0] != s.shape[1] + 1: raise ValueError("Factor must be of shape (D, D-1)") ndim = s.shape[0] m = np.eye(ndim, dtype=s.dtype) for col_idx in range(m.shape[1]): it = iter(factor[col_idx]) # type: ignore for row_idx in range(m.shape[0] - 1): if m[row_idx, col_idx] == 0: m[row_idx, col_idx] = next(it) return cls(m, spaces=spaces)Ancestors
- Transform
- abc.ABC
Static methods
def from_linear_map(linear_map: ArrayLike, translation=0, *, spaces: SpaceTuple = (None, None)) ‑> Affine-
Create an augmented affine matrix from a linear map, with an optional translation.
Parameters
linear_map:ArrayLike- Shape (D, D)
translation:ArrayLike, optional- Translation to add to the matrix, by default None
spaces:tuple[SpaceRef, SpaceRef]- Optional source and target spaces
Returns
AffineTransform
Expand source code
@classmethod def from_linear_map( cls, linear_map: ArrayLike, translation=0, *, spaces: SpaceTuple = (None, None), ) -> Affine: """Create an augmented affine matrix from a linear map, with an optional translation. Parameters ---------- linear_map : ArrayLike Shape (D, D) translation : ArrayLike, optional Translation to add to the matrix, by default None spaces : tuple[SpaceRef, SpaceRef] Optional source and target spaces Returns ------- AffineTransform """ lin_map = np.asarray(linear_map) side = len(lin_map) + 1 matrix = np.eye(side, dtype=lin_map.dtype) matrix[:-1, :] = lin_map matrix[:-1, -1] = translation return cls(matrix, spaces=spaces) def identity(ndim: int, *, spaces: SpaceTuple = (None, None)) ‑> Affine-
Create an identity affine transformation.
Parameters
ndim:intspaces:tuple[SpaceRef, SpaceRef]- Optional source and target spaces
Returns
AffineTransform
Expand source code
@classmethod def identity( cls, ndim: int, *, spaces: SpaceTuple = (None, None), ) -> Affine: """Create an identity affine transformation. Parameters ---------- ndim : int spaces : tuple[SpaceRef, SpaceRef] Optional source and target spaces Returns ------- AffineTransform """ return cls( np.eye(ndim + 1), spaces=spaces, ) def reflection(axis: Union[int, Container[int]], ndim: int, *, spaces: SpaceTuple = (None, None)) ‑> Affine-
Create an affine reflection.
Parameters
axis:Union[int, Container[int]]- A single axis or multiple to reflect in.
ndim:int- How many dimensions to work in.
spaces:tuple[SpaceRef, SpaceRef]- Optional source and target spaces
Returns
AffineTransform
Expand source code
@classmethod def reflection( cls, axis: Union[int, Container[int]], ndim: int, *, spaces: SpaceTuple = (None, None), ) -> Affine: """Create an affine reflection. Parameters ---------- axis : Union[int, Container[int]] A single axis or multiple to reflect in. ndim : int How many dimensions to work in. spaces : tuple[SpaceRef, SpaceRef] Optional source and target spaces Returns ------- AffineTransform """ if np.isscalar(axis): axis = [axis] # type: ignore values = [-1 if idx in axis else 1 for idx in range(ndim)] # type:ignore m = np.eye(ndim + 1) m[:-1, :-1] *= values return cls(m, spaces=spaces) def rotation2(rotation: float, degrees=True, clockwise=False, *, spaces: SpaceTuple = (None, None)) ‑> Affine-
Create a 2D affine rotation.
Parameters
rotation:float- Angle to rotate.
degrees:bool, optional- Whether rotation is in degrees (rather than radians), by default True
clockwise:bool, optional- Whether rotation is clockwise, by default False
spaces:tuple[SpaceRef, SpaceRef]- Optional source and target spaces
Returns
AffineTransform
Expand source code
@classmethod def rotation2( cls, rotation: float, degrees=True, clockwise=False, *, spaces: SpaceTuple = (None, None), ) -> Affine: """Create a 2D affine rotation. Parameters ---------- rotation : float Angle to rotate. degrees : bool, optional Whether rotation is in degrees (rather than radians), by default True clockwise : bool, optional Whether rotation is clockwise, by default False spaces : tuple[SpaceRef, SpaceRef] Optional source and target spaces Returns ------- AffineTransform """ if degrees: rotation = np.deg2rad(rotation) if clockwise: rotation *= -1 vals = [ [np.cos(rotation), -np.sin(rotation)], [np.sin(rotation), np.cos(rotation)], ] m = np.eye(3) m[:-1, :-1] = vals return cls(m, spaces=spaces) def rotation3(rotation: Union[float, Tuple[float, float, float]], degrees=True, clockwise=False, order=(0, 1, 2), *, spaces: SpaceTuple = (None, None)) ‑> Affine-
Create a 3D affine rotation.
Parameters
rotation:Union[float, Tuple[float, float, float]]- Either a single rotation for all axes, or 1 for each.
degrees:bool, optional- Whether rotation is in degrees (rather than radians), by default True
clockwise:bool, optional- Whether rotation is clockwise, by default False
order:tuple, optional- What order to apply the rotations, by default (0, 1, 2)
spaces:tuple[SpaceRef, SpaceRef]- Optional source and target spaces
Returns
AffineTransform
Raises
ValueError- Incompatible order.
Expand source code
@classmethod def rotation3( cls, rotation: Union[float, Tuple[float, float, float]], degrees=True, clockwise=False, order=(0, 1, 2), *, spaces: SpaceTuple = (None, None), ) -> Affine: """Create a 3D affine rotation. Parameters ---------- rotation : Union[float, Tuple[float, float, float]] Either a single rotation for all axes, or 1 for each. degrees : bool, optional Whether rotation is in degrees (rather than radians), by default True clockwise : bool, optional Whether rotation is clockwise, by default False order : tuple, optional What order to apply the rotations, by default (0, 1, 2) spaces : tuple[SpaceRef, SpaceRef] Optional source and target spaces Returns ------- AffineTransform Raises ------ ValueError Incompatible order. """ if np.isscalar(rotation): r = np.array([rotation] * 3) else: r = np.asarray(rotation) if degrees: r = np.deg2rad(r) if clockwise: r *= -1 if len(order) != 3 or set(order) != {0, 1, 2}: raise ValueError("Order must contain only 0, 1, 2 in any order.") order = list(order) rots = [ np.array( [ [1, 0, 0], [0, np.cos(r[0]), -np.sin(r[0])], [0, np.sin(r[0]), np.cos(r[0])], ] ), np.array( [ [np.cos(r[1]), 0, np.sin(r[1])], [0, 1, 0], [-np.sin(r[1]), 0, np.cos(r[1])], ] ), np.array( [ [np.cos(r[2]), -np.sin(r[2]), 0], [np.sin(r[2]), np.cos(r[2]), 0], [0, 0, 1], ] ), ] rot = rots[order.pop(0)] rot @= rots[order.pop(0)] rot @= rots[order.pop(0)] m = np.eye(4) m[:-1, :-1] = rot return cls(m, spaces=spaces) def scaling(scale: ArrayLike, ndim: Optional[int] = None, *, spaces: SpaceTuple = (None, None)) ‑> Affine-
Create an affine scaling.
Parameters
scale:ArrayLike- If scalar, broadcast to ndim.
ndim:Optional[int], optional- If scale is scalar, how many dimensions to use
spaces:tuple[SpaceRef, SpaceRef]- Optional source and target spaces
Returns
AffineTransform- [description]
Expand source code
@classmethod def scaling( cls, scale: ArrayLike, ndim: Optional[int] = None, *, spaces: SpaceTuple = (None, None), ) -> Affine: """Create an affine scaling. Parameters ---------- scale : ArrayLike If scalar, broadcast to ndim. ndim : Optional[int], optional If scale is scalar, how many dimensions to use spaces : tuple[SpaceRef, SpaceRef] Optional source and target spaces Returns ------- AffineTransform [description] """ s = arg_as_array(scale, ndim) m = np.eye(len(s) + 1, dtype=s.dtype) m[:-1, :-1] *= s return cls(m, spaces=spaces) def shearing(factor: Union[float, np.ndarray], ndim: Optional[int] = None, *, spaces: SpaceTuple = (None, None)) ‑> Affine-
Create an affine shear.
factorcan be a scalar to broadcast to all dimensions, or a D-length list of D-1 lists. The first inner list contains the shear factors in the first dimension for all but the first dimension. The second inner list contains the shear factors in the second dimension for all the but the second dimension, etc.Parameters
factor:Union[float, np.ndarray]- Shear scale factors; see above for more details.
ndim:Optional[int], optional- If factor is scalar, broadcast to this many dimensions, by default None
spaces:tuple[SpaceRef, SpaceRef]- Optional source and target spaces
Returns
AffineTransform
Raises
ValueError- Incompatible factor.
Expand source code
@classmethod def shearing( cls, factor: Union[float, np.ndarray], ndim: Optional[int] = None, *, spaces: SpaceTuple = (None, None), ) -> Affine: """Create an affine shear. `factor` can be a scalar to broadcast to all dimensions, or a D-length list of D-1 lists. The first inner list contains the shear factors in the first dimension for all *but* the first dimension. The second inner list contains the shear factors in the second dimension for all the *but* the second dimension, etc. Parameters ---------- factor : Union[float, np.ndarray] Shear scale factors; see above for more details. ndim : Optional[int], optional If factor is scalar, broadcast to this many dimensions, by default None spaces : tuple[SpaceRef, SpaceRef] Optional source and target spaces Returns ------- AffineTransform Raises ------ ValueError Incompatible factor. """ if np.isscalar(factor): if ndim is None: raise ValueError("If factor is scalar, ndim must be defined") s = np.full((ndim, ndim - 1), factor) else: s = np.asarray(factor) if s.shape[0] != s.shape[1] + 1: raise ValueError("Factor must be of shape (D, D-1)") ndim = s.shape[0] m = np.eye(ndim, dtype=s.dtype) for col_idx in range(m.shape[1]): it = iter(factor[col_idx]) # type: ignore for row_idx in range(m.shape[0] - 1): if m[row_idx, col_idx] == 0: m[row_idx, col_idx] = next(it) return cls(m, spaces=spaces) def translation(translation: ArrayLike, ndim: Optional[int] = None, *, spaces: SpaceTuple = (None, None)) ‑> Affine-
Create an affine translation.
Parameters
translation:ArrayLike- If scalar, broadcast to ndim.
ndim:int, optional- If translation is scalar, how many dims to use.
spaces:tuple[SpaceRef, SpaceRef]- Optional source and target spaces
Returns
AffineTransform
Expand source code
@classmethod def translation( cls, translation: ArrayLike, ndim: Optional[int] = None, *, spaces: SpaceTuple = (None, None), ) -> Affine: """Create an affine translation. Parameters ---------- translation : ArrayLike If scalar, broadcast to ndim. ndim : int, optional If translation is scalar, how many dims to use. spaces : tuple[SpaceRef, SpaceRef] Optional source and target spaces Returns ------- AffineTransform """ t = arg_as_array(translation, ndim) m = np.eye(len(t) + 1, dtype=t.dtype) m[:-1, -1] = t return cls(m, spaces=spaces)
Inherited members
class Identity (*, spaces: Tuple[Optional[Hashable], Optional[Hashable]] = (None, None))-
Helper class that provides a standard way to create an ABC using inheritance.
Transform which does nothing.
Parameters
spaces:tuple[SpaceRef, SpaceRef]- Optional source and target spaces
Raises
ValueError- [description]
Expand source code
class Identity(Transform): def __init__( self, *, spaces: SpaceTuple = (None, None), ): """ Transform which does nothing. Parameters ---------- spaces : tuple[SpaceRef, SpaceRef] Optional source and target spaces Raises ------ ValueError [description] """ src = chain_or(*spaces, default=None) tgt = chain_or(*spaces[::-1], default=None) if src != tgt: raise ValueError("Source and target spaces are different") super().__init__(spaces=(src, src)) def __invert__(self) -> Transform: return self def apply(self, coords: np.ndarray) -> np.ndarray: return coords.copy()Ancestors
- Transform
- abc.ABC
Inherited members
class Reflect (normals: Union[numpy.__array_like._SupportsArray[numpy.dtype], numpy.__nested_sequence._NestedSequence[numpy.__array_like._SupportsArray[numpy.dtype]], bool, int, float, complex, str, bytes, numpy.__nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]], point=0, *, spaces: Tuple[Optional[Hashable], Optional[Hashable]] = (None, None))-
Helper class that provides a standard way to create an ABC using inheritance.
Reflection about arbitrary planes.
Parameters
normals:sequenceofarrays- Normal vectors to the planes of reflection. Unitised internally.
point:floatorsequenceoffloats, optional- Intersection point of all reflection planes (can be broadcast from scalar), by default 0 (i.e. the origin)
spaces:tuple[SpaceRef, SpaceRef]- Optional source and target spaces
Raises
ValueError- Inconsistent dimensionality
Expand source code
class Reflect(Transform): def __init__( self, normals: ArrayLike, point=0, *, spaces: SpaceTuple = (None, None), ): """Reflection about arbitrary planes. Parameters ---------- normals : sequence of arrays Normal vectors to the planes of reflection. Unitised internally. point : float or sequence of floats, optional Intersection point of all reflection planes (can be broadcast from scalar), by default 0 (i.e. the origin) spaces : tuple[SpaceRef, SpaceRef] Optional source and target spaces Raises ------ ValueError Inconsistent dimensionality """ super().__init__(spaces=spaces) normals = np.asarray(normals) if normals.ndim == 1: normals = [normals] n1 = normals[0] if not np.isscalar(point) and len(n1) != len(point): raise ValueError("Point and normals are not of the same dimensionality") self.point = point self.ndim = {len(n1)} self.normals = [unitise(n) for n in normals] # todo: matmul is associative, so turn this into an affine in 2/3D? def apply(self, coords: np.ndarray) -> np.ndarray: coords = self._validate_coords(coords) out = coords - self.point for n in self.normals: # mul->sum vectorises dot product # normals are unit, avoids unnecessary division by 1 out -= 2 * np.sum(coords * n, axis=1) * n out += self.point return out @classmethod def from_points( cls, points: ArrayLike, *, spaces: SpaceTuple = (None, None), ): """Infer a single plane of reflection from a minimal number of points on it. Parameters ---------- points : NxD array of N points in D dimensions. N == D spaces : tuple[SpaceRef, SpaceRef] Optional source and target spaces Returns ------- Reflection """ point, normals = get_hyperplanes(np.asarray(points), unitise=False) return cls(normals, point, spaces=spaces) @classmethod def from_axis( cls, axis: Union[int, Sequence[int]], origin: ArrayLike, *, spaces: SpaceTuple = (None, None), ): """Reflect around hyperplane(s) parallel with axes. Parameters ---------- axis : int or sequence of int Index (or indices) of axes in which to reflect. origin : array-like Point around which to reflect. spaces : tuple[SpaceRef, SpaceRef] Optional source and target spaces Returns ------- Reflection Raises ------ ValueError Selected axis does not exist. """ origin = np.asarray(origin) axis = ensure_tuple(axis) for a in axis: if a >= len(axis): raise ValueError( "Cannot reflect in axis which does not exist (too high)" ) normals = [] for i in range(len(origin) - len(axis) + 1): if i not in axis: v = np.zeros_like(origin) v[i] += 1 normals.append(v) return cls(normals, origin, spaces=spaces) def __invert__(self): return copy(self)Ancestors
- Transform
- abc.ABC
Static methods
def from_axis(axis: Union[int, Sequence[int]], origin: Union[numpy.__array_like._SupportsArray[numpy.dtype], numpy.__nested_sequence._NestedSequence[numpy.__array_like._SupportsArray[numpy.dtype]], bool, int, float, complex, str, bytes, numpy.__nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]], *, spaces: Tuple[Optional[Hashable], Optional[Hashable]] = (None, None))-
Reflect around hyperplane(s) parallel with axes.
Parameters
axis:intorsequenceofint- Index (or indices) of axes in which to reflect.
origin:array-like- Point around which to reflect.
spaces:tuple[SpaceRef, SpaceRef]- Optional source and target spaces
Returns
Reflection
Raises
ValueError- Selected axis does not exist.
Expand source code
@classmethod def from_axis( cls, axis: Union[int, Sequence[int]], origin: ArrayLike, *, spaces: SpaceTuple = (None, None), ): """Reflect around hyperplane(s) parallel with axes. Parameters ---------- axis : int or sequence of int Index (or indices) of axes in which to reflect. origin : array-like Point around which to reflect. spaces : tuple[SpaceRef, SpaceRef] Optional source and target spaces Returns ------- Reflection Raises ------ ValueError Selected axis does not exist. """ origin = np.asarray(origin) axis = ensure_tuple(axis) for a in axis: if a >= len(axis): raise ValueError( "Cannot reflect in axis which does not exist (too high)" ) normals = [] for i in range(len(origin) - len(axis) + 1): if i not in axis: v = np.zeros_like(origin) v[i] += 1 normals.append(v) return cls(normals, origin, spaces=spaces) def from_points(points: Union[numpy.__array_like._SupportsArray[numpy.dtype], numpy.__nested_sequence._NestedSequence[numpy.__array_like._SupportsArray[numpy.dtype]], bool, int, float, complex, str, bytes, numpy.__nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]], *, spaces: Tuple[Optional[Hashable], Optional[Hashable]] = (None, None))-
Infer a single plane of reflection from a minimal number of points on it.
Parameters
- points :
- NxD array of N points in D dimensions. N == D
spaces:tuple[SpaceRef, SpaceRef]- Optional source and target spaces
Returns
Reflection
Expand source code
@classmethod def from_points( cls, points: ArrayLike, *, spaces: SpaceTuple = (None, None), ): """Infer a single plane of reflection from a minimal number of points on it. Parameters ---------- points : NxD array of N points in D dimensions. N == D spaces : tuple[SpaceRef, SpaceRef] Optional source and target spaces Returns ------- Reflection """ point, normals = get_hyperplanes(np.asarray(points), unitise=False) return cls(normals, point, spaces=spaces)
Inherited members
class Scale (scale: Union[numpy.__array_like._SupportsArray[numpy.dtype], numpy.__nested_sequence._NestedSequence[numpy.__array_like._SupportsArray[numpy.dtype]], bool, int, float, complex, str, bytes, numpy.__nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]], *, spaces: Tuple[Optional[Hashable], Optional[Hashable]] = (None, None))-
Helper class that provides a standard way to create an ABC using inheritance.
Simple scale transform.
All points are scaled, i.e. distance from the origin may also change.
Parameters
scale:scalarorD-length array-like- Scaling to apply in all dimensions, or each dimension.
spaces:tuple[SpaceRef, SpaceRef]- Optional source and target spaces
Raises
ValueError- If scale is the wrong shape.
Expand source code
class Scale(Transform): def __init__( self, scale: ArrayLike, *, spaces: SpaceTuple = (None, None), ): """Simple scale transform. All points are scaled, i.e. distance from the origin may also change. Parameters ---------- scale : scalar or D-length array-like Scaling to apply in all dimensions, or each dimension. spaces : tuple[SpaceRef, SpaceRef] Optional source and target spaces Raises ------ ValueError If scale is the wrong shape. """ super().__init__(spaces=spaces) self.scale = np.asarray(scale) if self.scale.ndim > 1: raise ValueError("Scale must be scalar or 1D") if self.scale.shape not in [(), (1,)]: self.ndim = {self.scale.shape[0]} # otherwise, can be broadcast to anything def apply(self, coords: np.ndarray) -> np.ndarray: coords = self._validate_coords(coords) return coords * self.scale def __invert__(self) -> Transform: return type(self)( 1 / self.scale, spaces=self.spaces[::-1], )Ancestors
- Transform
- abc.ABC
Inherited members
class Translate (translation: Union[numpy.__array_like._SupportsArray[numpy.dtype], numpy.__nested_sequence._NestedSequence[numpy.__array_like._SupportsArray[numpy.dtype]], bool, int, float, complex, str, bytes, numpy.__nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]], *, spaces: Tuple[Optional[Hashable], Optional[Hashable]] = (None, None))-
Helper class that provides a standard way to create an ABC using inheritance.
Simple translation.
Parameters
translation:scalarorD-length array- Translation to apply in all dimensions, or each dimension.
spaces:tuple[SpaceRef, SpaceRef]- Optional source and target spaces
Raises
ValueError- If the translation is the wrong shape
Expand source code
class Translate(Transform): def __init__( self, translation: ArrayLike, *, spaces: SpaceTuple = (None, None), ): """Simple translation. Parameters ---------- translation : scalar or D-length array Translation to apply in all dimensions, or each dimension. spaces : tuple[SpaceRef, SpaceRef] Optional source and target spaces Raises ------ ValueError If the translation is the wrong shape """ super().__init__(spaces=spaces) self.translation = np.asarray(translation) if self.translation.ndim > 1: raise ValueError("Translation must be scalar or 1D") if self.translation.shape not in [(), (1,)]: self.ndim = {self.translation.shape[0]} # otherwise, can be broadcast to anything def apply(self, coords: np.ndarray) -> np.ndarray: self._validate_coords(coords) return coords + self.translation def __invert__(self) -> Transform: return type(self)(-self.translation, spaces=self.spaces[::-1])Ancestors
- Transform
- abc.ABC
Inherited members