"""
Created on Oct 18, 2012
@author: Jamesan
"""
import abc
from abc import ABC, abstractmethod
from typing import Any, Callable
import numpy as np
from numpy.typing import NDArray, ArrayLike
import scipy.spatial
import nornir_imageregistration
from nornir_imageregistration.transforms.transform_type import TransformType
class ITransformSourceRotation(ABC):
@abstractmethod
def RotateSourcePoints(self, rangle: float, rotation_center: NDArray[np.floating] | None):
"""Rotate all warped points by the specified amount
If rotation center is not specified the transform chooses"""
raise NotImplementedError()
class ITransformTargetRotation(ABC):
@abstractmethod
def RotateTargetPoints(self, rangle: float, rotation_center: NDArray[np.floating] | None):
"""Rotate all fixed points by the specified amount
If rotation center is not specified the transform chooses"""
raise NotImplementedError()
class ITransfomFlip(ABC):
"""Supports flipping the target and source space independently of each other"""
def Flip(self):
"""Flip the target and source space independently of each other"""
raise NotImplementedError()
# Correct spelling; ITransfomFlip is deprecated.
ITransformFlip = ITransfomFlip
class ITransformRelativeScaling(ABC):
"""Supports scaling of target space or source space independently of each other"""
@abstractmethod
def ScaleFixed(self, scalar: float) -> None:
"""Scale all fixed points by the specified amount"""
raise NotImplementedError()
@abstractmethod
def ScaleWarped(self, scalar: float) -> None:
"""Scale all warped points by the specified amount"""
raise NotImplementedError()
class IGridTransform(ITransform, ABC):
@property
@abc.abstractmethod
def grid(self) -> nornir_imageregistration.IGrid:
raise NotImplementedError()
[docs]
class IControlPoints(ABC):
"""Interface for transforms that use control points"""
@property
@abc.abstractmethod
def SourcePoints(self) -> NDArray:
"""The source points of the transform. Order matches the results of SourcePoints and points"""
raise NotImplementedError()
@property
@abc.abstractmethod
def TargetPoints(self) -> NDArray:
"""The target points of the transform. Order matches the results of SourcePoints and points"""
raise NotImplementedError()
@property
@abc.abstractmethod
def points(self) -> NDArray:
"""Points is a 4xN array of corresponding control points in this format [[TargetY, TargetX, SourceY, SourceX],].
Order matches the results of SourcePoints and TargetPoints"""
raise NotImplementedError()
[docs]
@abc.abstractmethod
def NearestFixedPoint(self, points: NDArray) -> tuple[float | NDArray[np.floating], int | NDArray[np.integer]]:
"""
Return the fixed points nearest to the query points
:return: Distance, Index
"""
raise NotImplementedError()
[docs]
@abc.abstractmethod
def NearestWarpedPoint(self, points: NDArray) -> tuple[float | NDArray[np.floating], int | NDArray[np.integer]]:
"""
Return the warped points nearest to the query points
:return: Distance, Index
"""
raise NotImplementedError()
[docs]
@abc.abstractmethod
def GetPointPairsInTargetRect(self, bounds: nornir_imageregistration.Rectangle) -> NDArray[np.floating]:
"""Return the point pairs inside the rectangle defined in target space"""
raise NotImplementedError()
[docs]
@abc.abstractmethod
def GetPointPairsInSourceRect(self, bounds: nornir_imageregistration.Rectangle) -> NDArray[np.floating]:
"""Return the point pairs inside the rectangle defined in source space"""
raise NotImplementedError()
[docs]
@abc.abstractmethod
def PointPairsToWarpedPoints(self, points: NDArray[np.floating]) -> NDArray[np.floating]:
"""Return the warped points from a set of target-source point pairs"""
raise NotImplementedError()
[docs]
@abc.abstractmethod
def PointPairsToTargetPoints(self, points: NDArray[np.floating]) -> NDArray[np.floating]:
"""Return the target points from a set of target-source point pairs"""
raise NotImplementedError()
@property
@abc.abstractmethod
def NumControlPoints(self) -> int:
raise NotImplementedError()
@property
@abc.abstractmethod
def TargetBoundingBox(self) -> nornir_imageregistration.Rectangle:
"""Bounding box of target space points"""
raise NotImplementedError()
@property
@abc.abstractmethod
def SourceBoundingBox(self) -> nornir_imageregistration.Rectangle:
"""Bounding box of source space points"""
raise NotImplementedError()
class ITriangulatedTargetSpace(ABC):
@property
@abc.abstractmethod
def target_space_trianglulation(self) -> scipy.spatial.Delaunay:
raise NotImplementedError()
class ITriangulatedSourceSpace(ABC):
@property
@abc.abstractmethod
def source_space_trianglulation(self) -> scipy.spatial.Delaunay:
raise NotImplementedError()
class IControlPointAddRemove(ABC):
"""Interface for control point based transforms that can add/remove control points"""
@abc.abstractmethod
def AddPoint(self, pointpair: NDArray[np.floating]) -> int:
raise NotImplementedError()
@abc.abstractmethod
def AddPoints(self, new_points: NDArray[np.floating]):
raise NotImplementedError()
@abc.abstractmethod
def RemovePoint(self, index: int | NDArray[np.integer]):
raise NotImplementedError()
class ITargetSpaceControlPointEdit(ABC):
"""Transforms where the source space side of control points can be moved.
Originally added for grid transforms where source points are unmovable"""
@abc.abstractmethod
def UpdateTargetPointsByIndex(
self,
index: int | NDArray[np.integer],
points: NDArray[np.floating],
*,
remove_duplicates: bool = True,
) -> int | NDArray[np.integer]:
"""Move target-space control points at *index*.
:param remove_duplicates: When True (default), collapse coincident control
points after the write and return the nearest remaining index. When
False, skip uniqueness: coincident points can remain; Delaunay/RBF
can later fail or remap rows. Use False only for a bounded interactive
sequence (drag, registration apply) where the caller keeps indices
stable and remeshes when idle.
:return: The index of the edited points after any collapse.
"""
raise NotImplementedError()
@abc.abstractmethod
def UpdateTargetPointsByPosition(
self,
old_points: NDArray[np.floating],
new_points: NDArray[np.floating],
*,
remove_duplicates: bool = True,
) -> int | NDArray[np.integer]:
"""Move the points closest to *old_points* to positions at *new_points*.
:param remove_duplicates: See :meth:`UpdateTargetPointsByIndex`.
:return: The index of the edited points after any collapse.
"""
raise NotImplementedError()
class ISourceSpaceControlPointEdit(ABC):
"""Transforms where the source space side of control points can be moved"""
@abc.abstractmethod
def UpdateSourcePointsByIndex(
self,
index: int | NDArray[np.integer],
points: NDArray[np.floating],
*,
remove_duplicates: bool = True,
) -> int | NDArray[np.integer]:
"""Move source-space control points at *index*.
:param remove_duplicates: When True (default), collapse coincident control
points after the write and return the nearest remaining index. When
False, skip uniqueness: coincident points can remain; Delaunay/RBF
can later fail or remap rows. Use False only for a bounded interactive
sequence (drag, registration apply) where the caller keeps indices
stable and remeshes when idle.
:return: The index of the edited points after any collapse.
"""
raise NotImplementedError()
@abc.abstractmethod
def UpdateSourcePointsByPosition(
self,
old_points: NDArray[np.floating],
new_points: NDArray[np.floating],
*,
remove_duplicates: bool = True,
) -> int | NDArray[np.integer]:
"""Move the points closest to *old_points* to positions at *new_points*.
:param remove_duplicates: See :meth:`UpdateSourcePointsByIndex`.
:return: The index of the edited points after any collapse.
"""
raise NotImplementedError()
class IControlPointEdit(ITargetSpaceControlPointEdit, ISourceSpaceControlPointEdit, ABC):
"""Control point transforms where source and target control points can be edited"""
@abc.abstractmethod
def UpdatePointPair(self, index: int, pointpair: NDArray[np.floating]):
raise NotImplementedError()
@abc.abstractmethod
def RemovePoint(self, index: int | NDArray[np.integer]):
raise NotImplementedError()
class Base(ITransform, ITransformTranslation, ABC):
"""Base class of all transforms"""
pass