Affine#

Spline-interpolated pull-back affine transforms on 2-D or 3-D spatial data. The module provides a general matrix-and-offset function, a rotation convenience function, and a reusable geometry plan. Explicit spatial axes allow remaining dimensions to act as independent batch or channel axes. One-shot execution uses bounded coordinate tiles; cached plans retain geometry behind a caller-controlled memory cap. AffinePlan.prepare_coefficients returns an immutable, compatibility-checked AffineCoefficientField for deliberate sharing when one sampled field is transformed through several geometries. The raw-array prefilter path remains available for explicit output-buffer workflows. AffineCoefficientField.save and AffinePlan.load_coefficients provide a JSON-plus-numeric NPZ round trip with atomic replacement, endian-portable schema-2 values, schema-1 reading, compatibility validation, and no NumPy object-pickle loading. Equivalent SciPy comparisons establish numerical parity, not a universal performance win.

class splineops.affine.affine.AffineCoefficientField(values, spatial_axes, signature, configuration, serialization_metadata, *, _token=None)#

Bases: object

Immutable tagged cardinal coefficients prepared by an affine plan.

Construct fields with AffinePlan.prepare_coefficients(). The tag prevents accidentally applying coefficients with a different input grid, spline degree, boundary mode, or precision. Transform matrix and output shape are intentionally not part of the tag, so one prepared field can be reused across compatible affine geometries.

property configuration: dict[str, object]#

Copy of the coefficient-space contract.

property dtype: dtype#
incompatibility_reason(plan) str | None#

Explain why plan cannot consume the field, or return None.

is_compatible(plan) bool#

Return whether plan can apply this coefficient field.

property nbytes: int#
save(path) None#

Atomically save values and their compatibility contract to NPZ.

The file contains JSON metadata and a numeric array only; loading never enables NumPy object pickles. Restore it through AffinePlan.load_coefficients(), which checks the stored contract against the receiving plan. A temporary file in the destination directory is flushed and atomically replaces the target, so a failed write cannot leave a partially updated archive.

property shape: tuple[int, ...]#
property spatial_axes: tuple[int, ...]#
property values: ndarray#

A copy of the coefficient values.

class splineops.affine.affine.AffinePlan(input_shape: tuple[int, ...], matrix: ~numpy._typing._array_like.ArrayLike, offset: ~numpy._typing._array_like.ArrayLike | None = None, *, output_shape: tuple[int, ...] | None = None, degree: int = 3, mode: str = 'zero', dtype: ~numpy._typing._dtype_like.DTypeLike = <class 'numpy.float64'>, cache_geometry: bool = True, max_retained_bytes: int = 268435456)#

Bases: object

Reusable pull-back affine transform on a fixed spatial geometry.

The plan can cache spline support indexes and weights for repeated frames. Non-spatial axes are treated independently, so explicit spatial_axes provide batch and channel support without changing TensorSpline.

Parameters:
  • input_shape (tuple of int) – Spatial input shape; two and three dimensions are supported.

  • matrix (ndarray) – Pull-back matrix mapping output coordinates to input coordinates.

  • offset (ndarray, optional) – Pull-back offset. Defaults to zero.

  • output_shape (tuple of int, optional) – Spatial output shape. Defaults to input_shape.

  • degree (int, optional) – B-spline degree from 0 through 7.

  • mode (str, optional) – TensorSpline boundary extension mode.

  • dtype (dtype, optional) – Floating execution precision.

  • cache_geometry (bool, optional) – Cache support indexes and weights. Disable for one-shot transforms.

  • max_retained_bytes (int, optional) – Hard limit for cached geometry arrays.

apply(data: NDArray, *, spatial_axes: Sequence[int] | None = None, out: NDArray | None = None) ndarray#

Apply the fixed transform to samples or independent batches/channels.

apply_coefficients(coefficients: NDArray | AffineCoefficientField, *, spatial_axes: Sequence[int] | None = None, out: NDArray | None = None) ndarray#

Apply the transform without prefiltering cardinal coefficients again.

Tagged fields from prepare_coefficients() are checked against this plan. Raw arrays from prefilter() remain supported for backward compatibility; their provenance is necessarily the caller’s responsibility.

property configuration: dict[str, object]#

Copy of the fixed numerical contract represented by this plan.

property geometry_cached: bool#

Whether spline supports and weights are retained by this plan.

property geometry_retained_bytes: int#

Bytes retained by cached spline supports and weights.

load_coefficients(path) AffineCoefficientField#

Load and validate a field saved by AffineCoefficientField.save.

prefilter(data: NDArray, *, spatial_axes: Sequence[int] | None = None, out: NDArray | None = None) ndarray#

Return cardinal coefficients for reuse across affine geometries.

prepare_coefficients(data: NDArray, *, spatial_axes: Sequence[int] | None = None) AffineCoefficientField#

Return immutable coefficients with a checked compatibility tag.

Prefer this method when coefficients will be reused. prefilter remains available for raw-array and explicit-output workflows.

property retained_bytes: int#

Bytes retained by geometry and the reusable spline template.

property template_retained_bytes: int#

Bytes retained by template coefficients and construction coordinates.

splineops.affine.affine.affine_transform(data: NDArray, matrix: ArrayLike, offset: ArrayLike | None = None, *, output_shape: tuple[int, ...] | None = None, spatial_axes: Sequence[int] | None = None, degree: int = 3, mode: str = 'zero', out: NDArray | None = None) ndarray#

Apply a one-shot two- or three-dimensional pull-back affine transform.

splineops.affine.affine.rotate(data: NDArray, angle: float, axis: Tuple[float, float, float] | None = None, center: Tuple[float, float, float] | None = None, degree: int = 3, mode: str = 'zero', *, spatial_axes: Sequence[int] | None = None, out: NDArray | None = None) NDArray#

Rotate 2D or 3D data around a specified center using spline interpolation.

Parameters:
  • data (ndarray) – 2D or 3D input data array to rotate.

  • angle (float) – Rotation angle in degrees.

  • axis (tuple of float, optional) – The axis of rotation for 3D data. Defaults to (0, 0, 1).

  • center (tuple of float, optional) – The center of rotation. Defaults to the array center.

  • degree (int, optional) – B-spline degree (0 to 7). Default is 3.

  • mode (str, optional) – Boundary handling mode (e.g., “zero”, “mirror”). Default is “zero”.

  • spatial_axes (sequence of int, optional) – Two or three axes to rotate. Required for arrays that also contain batch or channel dimensions; every non-spatial slice is transformed independently.

  • out (ndarray, optional) – Exact-shape and exact-dtype output buffer.

Returns:

rotated_data – The data array after rotation.

Return type:

ndarray

Examples

Rotate a 2D array by 45 degrees:

>>> import numpy as np
>>> from splineops.affine import rotate
>>> data = np.array([[1, 2], [3, 4]])
>>> rotated_data = rotate(data, angle=45)
>>> rotated_data.shape
(2, 2)

Rotate a 3D array around a custom axis:

>>> data_3d = np.random.rand(4, 4, 4)
>>> rotated_data_3d = rotate(data_3d, angle=30, axis=(1, 0, 0))
>>> rotated_data_3d.shape
(4, 4, 4)

See also#

TensorSpline

The independent continuous spline model used internally for interpolation.