Source code for shapeflow.core.interface
import abc
from typing import Type, Tuple, Optional, Dict, Mapping
import numpy as np
from shapeflow import get_logger
from shapeflow.core import Described
from shapeflow.core.config import BaseConfig, Configurable, Factory
from shapeflow.maths.colors import Color
from shapeflow.maths.coordinates import ShapeCoo, Roi
from pydantic import validator
log = get_logger(__name__)
[docs]class InterfaceType(Factory):
"""Interface type factory.
"""
_type: Type[Configurable]
_mapping: Mapping[str, Type[Configurable]] = {}
_config_type: Type[BaseConfig] = BaseConfig
[docs] def get(self) -> Type[Configurable]:
"""Get the interface type.
"""
interface = super().get()
assert issubclass(interface, Configurable)
return interface
[docs] def config_schema(self) -> dict:
"""Get the config schema for this interface type.
"""
return self.get().config_schema()
[docs] @classmethod
def __modify_schema__(cls, field_schema):
"""Modify ``pydantic`` schema to include the interface.
"""
super().__modify_schema__(field_schema)
field_schema.update(interface=cls._config_type.__name__)
[docs]class Handler(abc.ABC):
"""Abstract class to wrap an interface implementation and its configuration
"""
_implementation: Configurable
_implementation_factory: Type[InterfaceType]
_implementation_class: Type[Configurable]
[docs] def set_implementation(self, implementation: str) -> str:
"""Set the implementation.
"""
impl_type: type = self._implementation_factory(implementation).get()
assert issubclass(impl_type, self._implementation_class)
self._implementation = impl_type()
return self._implementation_factory.get_str( # todo: this is not necessary when using @extend(<Factory>)
self._implementation.__class__
)
[docs] def get_implementation(self) -> str:
"""Get the current implementation.
"""
return self._implementation.__class__.__qualname__
def implementation_config(self) -> BaseConfig: # todo: wait what?
pass
[docs]class HandlerConfig(BaseConfig, abc.ABC):
"""Abstract handler configuration.
"""
type: InterfaceType
"""The handler's interface implementation.
"""
data: BaseConfig
"""The implementation's configuration.
"""
@validator('type', pre=True)
def _validate_type(cls, value, values: dict):
if isinstance(value, cls.__fields__['type'].type_):
return value
else:
return cls.__fields__['type'].type_(value)
@validator('data', pre=True)
def _validate_data(cls, value, values: dict):
if isinstance(value, values['type'].get().config_class()):
return value
elif isinstance(value, dict):
return values['type'].get().config_class()(**value)
else:
raise NotImplementedError
[docs]class TransformInterface(Configurable, metaclass=abc.ABCMeta):
"""Transform interface.
Handles transforming frames based on ROI coordinates.
All transform plugins should derive from this class
and implement its methods.
"""
[docs] @abc.abstractmethod
def validate(self, matrix: Optional[np.ndarray]) -> bool:
"""Check whether a transformation matrix is valid.
Parameters
----------
matrix : Optional[np.ndarray]
A transformation matrix or ``None`` if not set yet.
"""
[docs] @abc.abstractmethod
def from_coordinates(self, roi: Roi, from_shape: tuple) -> np.ndarray:
"""Make relative video-space coordinates absolute
and compatible with ``numpy`` / ``OpenCV``.
Parameters
----------
roi : Roi
A region of interest in the video (in relative coordinates)
from_shape : tuple
The dimensions of the video (in pixels)
Returns
-------
np.ndarray
An array of absolute video-space coordinates
"""
[docs] @abc.abstractmethod
def to_coordinates(self, to_shape: tuple) -> np.ndarray:
"""Get the edges of the design in design-space coordinates
Parameters
----------
to_shape : tuple
The dimensions of the design (in pixels)
Returns
-------
np.ndarray
An array of absolute design-space coordinates
"""
[docs] @abc.abstractmethod
def estimate(self, roi: Roi, from_shape: tuple, to_shape: tuple) -> Optional[np.ndarray]:
"""Estimate the transformation matrix from
:func:`~shapeflow.core.interface.TransformInterface.from_coordinates`
to :func:`~shapeflow.core.interface.TransformInterface.to_coordinates`
Parameters
----------
roi : Roi
A region of interest in the video (in relative coordinates)
from_shape : tuple
The dimensions of the video (in pixels)
to_shape : tuple
The dimensions of the design (in pixels)
Returns
-------
Optional[np.ndarry]
The estimated transformation matrix. Returns ``None`` if one of the
arguments is invalid or if some other exception occurs.
"""
[docs] def invert(self, matrix: Optional[np.ndarray]) -> Optional[np.ndarray]:
"""Invert a transformation matrix
Parameters
----------
matrix : Optional[np.ndarray]
The transformation matrix to invert. Can be ``None`` if not set yet.
Returns
-------
Optional[np.ndarray]
The inverse transformation matrix. Returns ``None`` if ``matrix``
is ``None`` or if some exception occurs during inversion.
"""
if matrix is not None:
return np.linalg.inv(matrix)
else:
return None
[docs] @abc.abstractmethod
def transform(self, matrix: np.ndarray, img: np.ndarray, shape: Tuple[int, int]) -> np.ndarray:
"""Transform a frame from video-space to design-space.
Parameters
----------
matrix : np.ndarray
The transformation matrix
img : np.ndarray
The frame to transform
shape : tuple
The shape of the design to transform to
Returns
-------
np.ndarray
The transformed frame
"""
# todo: naming convention fail transform method vs. transform matrix
[docs] @abc.abstractmethod
def coordinate(self, matrix: np.ndarray, coordinate: ShapeCoo, shape: Tuple[int, int]) -> ShapeCoo:
"""Transform an (x,y) coordinate from video-space to design-space.
Parameters
----------
matrix : np.ndarray
The transformation matrix
coordinate : ShapeCoo
The coordinate to transform
shape : tuple
The shape of the design to transform to
Returns
-------
ShapeCoo
The transformed coordinate
"""
# todo: naming convention fail transform method vs. transform matrix
[docs]class FilterConfig(BaseConfig, metaclass=abc.ABCMeta):
"""Abstract filter configuration"""
@property
def ready(self):
"""Return true if filter can be applied ~ this configuration.
Override for specific filter implementations
"""
return False
[docs]class FilterInterface(Configurable, metaclass=abc.ABCMeta):
"""Filter interface.
Handles filtering color images to binary.
All filter plugins should derive from this class
and implement its methods.
"""
[docs] @abc.abstractmethod
def set_filter(self, filter, color: Color) -> FilterConfig:
"""Set the filter to a specific color.
Parameters
----------
filter : FilterConfig
The filter configuration
color : Color
The color to set the filter to
Returns
-------
FilterConfig
The updated filter configuration
"""
[docs] @abc.abstractmethod
def mean_color(self, filter) -> Color:
"""Get the mean color from a filter configuration
Parameters
----------
filter : FilterConfig
The filter configuration
Returns
-------
Color
The configuration's mean color
"""
[docs] @abc.abstractmethod
def filter(self, filter, image: np.ndarray, mask: np.ndarray = None) -> np.ndarray:
"""Filter a frame.
Parameters
----------
filter : FilterConfig
The filter configuration
image : np.ndarray
The frame to filter
mask : Optional[np.ndarray]
The mask to apply to the frame
Returns
-------
np.ndarray
The filtered frame
"""
[docs]class FilterType(InterfaceType):
"""Filter type factory.
"""
_type = FilterInterface
_mapping: Mapping[str, Type[FilterInterface]] = {}
_config_type = FilterConfig
[docs] def get(self) -> Type[FilterInterface]:
"""Get the filter type.
"""
interface = super().get()
assert issubclass(interface, FilterInterface)
return interface
[docs]class TransformType(InterfaceType):
"""Transform type factory.
"""
_type = TransformInterface
_mapping: Mapping[str, Type[TransformInterface]] = {}
_config_type = TransformConfig
[docs] def get(self) -> Type[TransformInterface]:
"""Get the transform type.
"""
interface = super().get()
assert issubclass(interface, TransformInterface)
return interface