# coding: utf-8
from __future__ import annotations
from dataclasses import dataclass
from typing import Optional, List, Tuple
from .box import Box
[docs]
@dataclass
class SpotsBoxes:
"""This class stores up to four instances of
:class:`~crappy.tool.camera_config.config_tools.Box`, defining the bounding
boxes of the spots for :class:`~crappy.blocks.VideoExtenso` or the patches
for :class:`~crappy.blocks.DICVE`.
It can also instantiate the Box objects by parsing a list of tuples
containing enough information.
.. versionadded:: 2.0.0
"""
spot_1: Optional[Box] = None
spot_2: Optional[Box] = None
spot_3: Optional[Box] = None
spot_4: Optional[Box] = None
x_l0: Optional[float] = None
y_l0: Optional[float] = None
_index = -1
def __getitem__(self, i: int) -> Optional[Box]:
if i == 0:
return self.spot_1
elif i == 1:
return self.spot_2
elif i == 2:
return self.spot_3
elif i == 3:
return self.spot_4
else:
raise IndexError
def __setitem__(self, i: int, value: Optional[Box]) -> None:
if i == 0:
self.spot_1 = value
elif i == 1:
self.spot_2 = value
elif i == 2:
self.spot_3 = value
elif i == 3:
self.spot_4 = value
else:
raise IndexError
def __iter__(self) -> SpotsBoxes:
self._index = -1
return self
def __next__(self) -> Box:
self._index += 1
try:
return self[self._index]
except IndexError:
raise StopIteration
def __len__(self) -> int:
return len([spot for spot in self if spot is not None])
[docs]
def set_spots(self,
spots: List[Tuple[int, int, int, int]]) -> None:
"""Parses a list of tuples and instantiates the corresponding
:class:`~crappy.tool.camera_config.config_tools.Box` objects."""
for i, spot in enumerate(spots):
self[i] = Box(x_start=spot[1], x_end=spot[1] + spot[3],
y_start=spot[0], y_end=spot[0] + spot[2])
def save_length(self) -> None:
"""Setting the :attr:`x_l0` and :attr:`y_l0` attributes based on the
positions of the centroids.
If only one spot is detected, setting the initial lengths to 0.
"""
# Calculating the centroids of the spots if not already known
for spot in self:
if spot is not None and spot.x_centroid is None:
min_x, max_x, min_y, max_y = spot.sorted()
spot.x_centroid = min_x + (max_x - min_x) / 2
spot.y_centroid = min_y + (max_y - min_y) / 2
# Simply taking the distance between the extrema as the initial length
if len(self) > 1:
x_centers = [spot.x_centroid for spot in self if spot is not None]
y_centers = [spot.y_centroid for spot in self if spot is not None]
self.x_l0 = max(x_centers) - min(x_centers)
self.y_l0 = max(y_centers) - min(y_centers)
# If only one spot detected, setting the initial lengths to 0
else:
self.x_l0 = 0
self.y_l0 = 0
[docs]
def empty(self) -> bool:
"""Returns :obj:`True` if all spots are :obj:`None`, else :obj:`False`."""
return all(spot is None for spot in self)
[docs]
def reset(self) -> None:
"""Resets the boxes to :obj:`None`."""
for i in range(4):
self[i] = None