Source code for pyxc.transform.transform_base
from abc import ABCMeta, abstractmethod
from typing import Type, Optional
from warnings import warn
import numpy as np
from ..core.processor.arrays import xy2matrix
[docs]class TransformationBase:
def __init__(self, parent: Optional["TransformationBase"] = None, *args, **kwargs):
self.parent = parent
[docs] def apply_transformation(self, x, y):
raise NotImplementedError("Transformation is not implemented.")
[docs] def apply_reverse_transformation(self, x, y):
raise NotImplementedError("Reverse transformation is not available.")
[docs]class MatrixTransformationBase(TransformationBase):
"""
A base class for matrix transformations. It provides the basic functionality for matrix transformations.
Parameters
----------
transformation_matrix : np.ndarray, optional
The transformation matrix to be used for transformation.
parent : TransformationBase, optional
The parent transformation object.
Attributes
----------
externally_set_transformation_matrix : np.ndarray
transformation_matrix : np.ndarray
reverse_transformation_matrix : np.ndarray
parent_transformation_matrix : np.ndarray
"""
def __init__(
self,
transformation_matrix: Optional[np.ndarray] = None,
parent: Optional[TransformationBase] = None,
):
super(MatrixTransformationBase, self).__init__(parent=parent)
if transformation_matrix is not None:
self._matrix_integrity_checker(transformation_matrix)
# Core variables
self._externally_set_transformation_matrix: np.ndarray | None = (
transformation_matrix
)
self.queue: list = list() # Storage of applied transformation matrices
self.is_updated = False
# Basic transformation matrix.
self.identity = np.array(
[
[1, 0, 0],
[0, 1, 0],
[0, 0, 1],
]
)
@property
def externally_set_transformation_matrix(self):
return self._identity_if_none(self._externally_set_transformation_matrix)
@property
def transformation_matrix(self):
if (
self.externally_set_transformation_matrix is not None
and len(self.queue) != 0
):
warn("Transformation matrix and queue were set. Queue will be prioritised.")
if len(self.queue) == 0:
return (
self.externally_set_transformation_matrix
@ self.parent_transformation_matrix
)
else:
return self._compile_from_queue() @ self.parent_transformation_matrix
@transformation_matrix.setter
def transformation_matrix(self, matrix):
"""
Set the transformation matrix, checks matrix integrity before setting.
Parameters
----------
matrix : ndarray
The matrix to set as the transformation matrix.
"""
self._matrix_integrity_checker(matrix)
self._externally_set_transformation_matrix = matrix
self.is_updated = True
@property
def reverse_transformation_matrix(self):
"""Return a compiled reverse transformation matrix."""
return np.linalg.pinv(self.transformation_matrix)
@property
def parent_transformation_matrix(self):
"""Return the base transformation matrix if set."""
if self.parent is None:
return self.identity
else:
return self._identity_if_none(self.parent.transformation_matrix)
def _compile_from_queue(self):
"""
Calculate the total transformation matrix based on the queue and the parent transformation matrix.
Returns
-------
transform_matrix : np.ndarray
Complex 2D image affine transformation matrix.
Notes
-----
When the `_externally_set_transformation_matrix` is None, then the total transformation matrix is calculated from the
parent and the queue. If `_externally_set_transformation_matrix` is not None, then the total transformation matrix
is calculated from the parent and the `_externally_set_transformation_matrix` value.
"""
self.queue.reverse() # Transformation order should be reversed.
transform_matrix = self.identity # Starting with the identity matrix.
for operation in self.queue:
transform_matrix = transform_matrix @ operation
return transform_matrix
[docs] def apply_transformation(self, x, y):
intermediate_matrix = self.transformation_matrix @ xy2matrix(x, y)
return (
intermediate_matrix[0] / intermediate_matrix[2],
intermediate_matrix[1] / intermediate_matrix[2],
)
def _identity_if_none(self, value):
"""
Return the identity matrix if the provided value is None, else returns the value itself.
Parameters
----------
value : ndarray or None
The value to check.
Returns
-------
ndarray
Identity matrix if value is None, else the value itself.
"""
if value is None:
return self.identity
else:
return value
@abstractmethod
def _matrix_integrity_checker(self, matrix):
"""
Check whether the provided matrix is valid.
Parameters
----------
matrix : ndarray
The matrix to check.
Returns
-------
bool
Abstract method to be implemented in subclasses. Expected to return True if matrix is valid, False otherwise.
"""
pass