"""Some basic tools for working with coordinates.
"""
from typing import Any, Tuple
import numpy as np
import cv2
from pydantic import Field
from shapeflow.core.config import BaseConfig
[docs]class Coo(BaseConfig):
"""Coordinates of a point on a 2D image.
Relative, and thus independent of the size of the image.
"""
x: float = Field(default = 0)
"""The x-coordinate.
"""
y: float = Field(default = 0)
"""The y coordinate.
"""
def __repr__(self):
return f"Coo({self.x}, {self.y})"
@property
def rel(self) -> Tuple[float, float]:
"""Get the relative coordinates.
"""
return (self.y, self.x)
@property
def list(self) -> list:
"""Get the relative coordinates as a list.
"""
return [self.x, self.y]
[docs]class ShapeCoo(Coo):
"""Coordinates of a point on a 2D image with a known size.
"""
shape: Tuple[int, int]
"""The size of the image, from ``numpy.ndarray.shape``.
"""
def __repr__(self):
return f"Coo({self.x}, {self.y}) ~ {self.shape}"
def __eq__(self, other: object) -> bool:
assert isinstance(other, ShapeCoo)
return self.abs == other.abs and self.shape == other.shape
@property
def abs(self) -> Tuple[float, float]:
"""Get the absolute coordinates.
"""
if self.shape is not None:
return (self.y * self.shape[0], self.x * self.shape[1])
else:
raise ValueError('No shape provided')
@property
def idx(self) -> Tuple[int, int]:
"""Get (approximate) array indices ~ the absolute coordinate.
"""
abs = self.abs
return (int(round(abs[0])), int(round(abs[1])))
@property
def cv2(self): # todo: the whole flip thing is confusing.
"""Get the absolute coordinates for ``OpenCV``.
.. note::
The order is flipped with respect to
:func:`~shapeflow.maths.coordinates.ShapeCoo.abs`
"""
abs = self.abs
return (abs[1], abs[0])
[docs] def value(self, image: np.ndarray) -> Any:
"""Get the value (color) of an image at this coordinate (approximately).
Parameters
----------
image: np.ndarray
An image as a ``numpy`` array. Its ``shape`` should match
:attr:`~shapeflow.maths.coordinates.ShapeCoo.shape`.
Returns
-------
Any
The value of the image array at this coordinate.
"""
assert image.shape[0:2] == self.shape
# todo: make sure x/y stuff is ok
coo_idx = self.idx
return image[
coo_idx[0], coo_idx[1]
]
[docs]class Roi(BaseConfig):
"""A rectangular region of interest, composed of four points as
:class:`~shapeflow.maths.coordinates.Coo` objects.
"""
BL: Coo = Field(default=None)
"""The bottom-left corner.
"""
TL: Coo = Field(default=None)
"""The top-left corner.
"""
TR: Coo = Field(default=None)
"""The top-right corner.
"""
BR: Coo = Field(default=None)
"""The bottom-right corner.
"""