Source code for ewoksndreg.transformation.numpy_backend

import itertools
from typing import Dict
from typing import Optional
from typing import Sequence
from typing import Tuple

import numpy

from .base import Transformation
from .homography import Homography


[docs] class NumpyHomography( Homography, registry_id=Homography.RegistryId("Homography", "Numpy") ): def __init__(self, *args, warp_options: Optional[Dict] = None, **kw) -> None: if warp_options is None: warp_options = dict() self._warp_options = warp_options super().__init__(*args, **kw)
[docs] def apply_coordinates(self, coord: Sequence[numpy.ndarray]) -> numpy.ndarray: """ :param coord: shape `(N, M)` :returns: shape `(N, M)` """ return homography_transform_coordinates(self.active_matrix, coord)
[docs] def apply_data( self, data: numpy.ndarray, offset: Optional[numpy.ndarray] = None, shape: Optional[numpy.ndarray] = None, cval=numpy.nan, interpolation_order: int = 1, ) -> numpy.ndarray: """ :param data: shape `(N1, N2, ..., M1, M2, ...)` with `len((N1, N2, ...)) = N` :param offset: shape `(N,)` :param shape: shape `(N,) = [N1', N2', ...]` :param cval: missing value :param interpolation_order: order of interpolation: 0 is nearest neighbor, 1 is bilinear,... :returns: shape `(N1', N2', ..., M1, M2, ...)` """ kw = dict(self._warp_options) if shape is not None: kw["shape"] = shape if offset is not None: kw["offset"] = offset if cval is not None: kw["cval"] = cval return homography_transform_data(self.passive_matrix, data, **kw)
def __matmul__(self, other: Transformation): if isinstance(other, NumpyHomography): return NumpyHomography( other.passive_matrix @ self.passive_matrix, warp_options=self._warp_options, ) else: ValueError("Only concatenation of same types allowed")
[docs] def homography_transform_coordinates( active_matrix: numpy.ndarray, coord: numpy.ndarray ) -> numpy.ndarray: """ :param active_matrix: shape `(N+1, N+1)` or `(K, N+1, N+1)` :param coord: shape `(N, M)` :returns: shape `(N, M)` or `(K, N, M)` """ N, M = coord.shape if active_matrix.ndim not in (2, 3) or active_matrix.shape[-2:] != (N + 1, N + 1): raise ValueError("matrix and coordinates dimensions do not match") coord = numpy.vstack(list(coord) + [numpy.ones(M)]) hcoord = active_matrix.dot(coord) return hcoord[..., :-1, :] / hcoord[..., -1, :]
[docs] def homography_transform_bounding_box( active_matrix: numpy.ndarray, shape: Tuple[int] ) -> Tuple[numpy.ndarray, numpy.ndarray]: """Calculate the bounding box after transforming an image with shape `shape`. Returns the minimum and maximum coordinates of the bounding box. :param active_matrix: shape `(N+1, N+1)` or `(K, N+1, N+1)` :param shape: shape `(N,)` :returns: 2-tuple of arrays with shape `(N,)` or `(K, N)` """ from_bounding_box = numpy.array(list(itertools.product(*[[0, n] for n in shape]))).T to_bounding_box = homography_transform_coordinates(active_matrix, from_bounding_box) return to_bounding_box.min(axis=-1), to_bounding_box.max(axis=-1)
[docs] def homography_transform_data( passive_matrix: numpy.ndarray, data: numpy.ndarray, offset: Optional[numpy.ndarray] = None, shape: Optional[numpy.ndarray] = None, cval=numpy.nan, ) -> numpy.ndarray: """The `offset` and `shape` determine the coordinates in the output frame at which to interpolate the data. By default these are the coordinates of the `data` in the input frame (i.e. the pixel indices). Only nearest-neighbor interpolation is supported. :param passive_matrix: shape `(N+1, N+1)` :param data: shape `(N1, N2, ..., M1, M2, ...)` with `len((N1, N2, ...)) = N` :param offset: shape `(N,)` :param shape: shape `(N,) = [N1', N2', ...]` :param cval: missing value :returns: shape `(N1', N2', ..., M1, M2, ...)` """ N = passive_matrix.shape[0] - 1 if passive_matrix.shape != (N + 1, N + 1): raise ValueError("requires a square matrix") if data.ndim < N: raise ValueError("passive_matrix and data dimensions do not match") if offset is not None: offset = numpy.asarray(offset) if offset.shape != (N,): raise ValueError("passive_matrix and offset dimensions do not match") passive_matrix = passive_matrix.copy() passive_matrix[:N, N] = offset shape_data = numpy.asarray(data.shape[:N]) if shape is None: shape_out = shape_data.copy() else: shape_out = numpy.asarray(shape) if shape_out.shape != (N,): raise ValueError("passive_matrix and shape dimensions do not match") # Output coordinates xout = [numpy.arange(n, dtype=int) for n in shape_out] xout = numpy.meshgrid(*xout, indexing="ij") xout = numpy.array([x.flatten() for x in xout]) # (N, M) with M=N1*N2*... # Corresponding input coordinates xdata = numpy.round(homography_transform_coordinates(passive_matrix, xout)).astype( int ) valid = (xdata >= 0) & (xdata < shape_data[:, None]) valid = numpy.bitwise_and.reduce(valid, axis=0) # Nearest-neighbor interpolation out = numpy.full(shape_out, cval, dtype=data.dtype) if valid.size == 0: return out out[tuple(xout[:, valid])] = data[tuple(xdata[:, valid])] return out