Source code for affine_transform.affine_transform
"""Module containing functionality for applying affine transformations to nd images."""
import numpy as np
from . import _affine_transform
[docs]def transform(
input_image,
linear_transformation,
translation,
order="linear",
origin=None,
output_image=None,
output_image_origin=None,
background_value=0.0,
):
"""
Transform an image using an affine transformation.
This function applies an affine transformation to an image, meaning, it first
applies a linear transformation (a combination of rotation, scaling. etc)
after which additionally a translation can be included. For the linear transformation,
the origin of the coordinate system can be freely chosen. E.g. you could rotate an
image of two spheres around their common center of mass instead of rotating around
the image center or the original origin of the image data ("The lower left corner").
.. note:: The linear transformation is required to be invertible. Other properties
of the given matrix are not checked.
Furthermore, an option is available to choose how data is read from the
given image, in case the affine transformation does not perfectly map pixels
from the given image to the output image. This is the ``order`` of the interpolation.
The data types of the input image can be anything that is convertible to :c:data:`np.float64<NPY_FLOAT64>`.
If :c:data:`np.float64<NPY_FLOAT64>` or :c:data:`numpy.float32<NPY_FLOAT32>` arrays are given as input or input and output
images, no copies are created. For all other input types, a :c:data:`np.float64<NPY_FLOAT64>` array
is generated and the output image (if given) has to be of type :c:data:`np.float64<NPY_FLOAT64>`.
Arguments
---------
input_image : nd-array
The data to transform
linear_transformation : matrix
A matrix of dimension ``(dim, dim)``, with ``dim`` being the dimension of the
input data.
translation : vector
The translation part of the affine transformation
order : {'linear', 'cubic'}
The interpolation order to use for sampling the input image, default is ``'linear'``
origin : vector, optional
The origin to use for the linear transformation. By default, the center
of the image is chosen
output_image : nd-array, optional
The image used for storing the results. If not set, memory will be
allocated internally
output_image_origin : vector, optional
If the `(0,0,0)` coordinate of the `output_image` should not coinside with the `(0,0,0)`
location of the `input_image`, this parameter can be given. E.g. if you want
to only extract a slice at `[:,:,x]`, this argument could be set to `(0,0,x)`
background_value : optional
The background value to use in case points outside the input image
are sampled
Returns
-------
nd-array
The given ``output_image`` or if not given a newly created array with the
results
Raises
------
ValueError
If the dimensions of the given inputs mismatch, or the datatypes are incompatible
~numpy.linalg.LinAlgError
If the given linear transformation is singular
"""
# check dimensions
if not all(v == input_image.ndim for v in linear_transformation.shape):
raise ValueError(
f"The given linear transformation is of shape {linear_transformation.shape}"
f" but should be of shape ({input_image.ndim}, {input_image.ndim})"
" for the given input image."
)
dtype = input_image.dtype
if input_image.dtype != np.float64 and input_image.dtype != np.float32:
dtype = np.float64
if not len(translation) == input_image.ndim:
raise ValueError(
f"The given translation has wrong dimensionality {len(translation)}"
f" while the required dimensionality for the given input image is {input_image.ndim}."
)
else:
translation = np.asarray(translation, dtype=dtype)
if origin is None:
origin = np.fromiter(((x - 1) / 2 for x in input_image.shape), dtype=dtype)
elif not len(origin) == input_image.ndim:
raise ValueError(
f"The given origin has wrong dimensionality {len(origin)}"
f" while the required dimensionality for the given input image is {input_image.ndim}."
)
else:
origin = np.asarray(origin, dtype=dtype)
if output_image is None:
output_image = np.zeros(input_image.shape, dtype=dtype)
elif not input_image.ndim == output_image.ndim:
raise ValueError(
f"The given output image has dimension {output_image.ndim}, but needs to have the same"
f" dimension as the given input image with shape {input_image.ndim}."
)
elif not output_image.dtype == dtype:
raise ValueError(
f"The given output image has dtype {output_image.dtype}, which is incompatible with"
f" the input image dtype which requires the dtype {dtype}."
)
if order == "linear":
_transform = _affine_transform.transform_linear
elif order == "cubic":
_transform = _affine_transform.transform_cubic
else:
raise ValueError(
f'Order was given as "{order}". But only "cubic" and "linear" are valid options.'
)
if output_image_origin is not None:
if len(output_image_origin) != input_image.ndim:
raise ValueError(
f"Given output image origin has dimension {len(output_image_origin)}, but needs to be"
f"the same as the dimension of the given input image which is {input_image.ndim}."
)
translation -= np.asarray(output_image_origin, dtype=dtype)
# We transform the coordinate system, so we take the inverse
linear_transformation = np.linalg.inv(linear_transformation)
origin = (linear_transformation @ (-translation - origin)) + origin
_transform(
origin,
linear_transformation.T, # columns
input_image.astype(dtype, copy=False),
output_image,
background_value,
)
return output_image