|
1
|
|
|
import typing |
|
|
|
|
|
|
2
|
|
|
from dataclasses import dataclass |
|
3
|
|
|
from enum import Enum |
|
4
|
|
|
from typing import Dict |
|
5
|
|
|
|
|
6
|
|
|
from pocketutils.core.exceptions import OutOfRangeError, ErrorUtils |
|
7
|
|
|
|
|
8
|
|
|
|
|
9
|
|
|
class Edge(Enum): |
|
|
|
|
|
|
10
|
|
|
left = 1 |
|
11
|
|
|
right = 2 |
|
12
|
|
|
top = 3 |
|
13
|
|
|
bottom = 4 |
|
14
|
|
|
|
|
15
|
|
|
|
|
16
|
|
|
class Axis(Enum): |
|
|
|
|
|
|
17
|
|
|
horizontal = 1 |
|
18
|
|
|
vertical = 2 |
|
19
|
|
|
|
|
20
|
|
|
|
|
21
|
|
|
@ErrorUtils.args(edge=Edge, axis=Axis) |
|
|
|
|
|
|
22
|
|
|
class RoiError(OutOfRangeError): |
|
23
|
|
|
"""The ROI is invalid.""" |
|
24
|
|
|
|
|
25
|
|
|
|
|
26
|
|
|
@dataclass(frozen=True, order=True) |
|
|
|
|
|
|
27
|
|
|
class WellRoi: |
|
28
|
|
|
row: int |
|
29
|
|
|
column: int |
|
30
|
|
|
x0: int |
|
|
|
|
|
|
31
|
|
|
y0: int |
|
|
|
|
|
|
32
|
|
|
x1: int |
|
|
|
|
|
|
33
|
|
|
y1: int |
|
|
|
|
|
|
34
|
|
|
|
|
35
|
|
|
def __post_init__(self): |
|
36
|
|
|
if self.row < 0: |
|
37
|
|
|
raise OutOfRangeError(f"Row ({self.row}) < 0", value=self.row) |
|
38
|
|
|
if self.column < 0: |
|
39
|
|
|
raise OutOfRangeError(f"Column ({self.column}) < 0", value=self.column) |
|
40
|
|
|
if self.x0 < 0: |
|
41
|
|
|
raise RoiError(f"x0 ({self.x0}) < 0", edge=Edge.left) |
|
42
|
|
|
if self.y0 < 0: |
|
43
|
|
|
raise RoiError(f"y0 ({self.y0}) < 0", edge=Edge.top) |
|
44
|
|
|
if self.x0 >= self.x1: |
|
45
|
|
|
raise RoiError(f"x0 ({self.x0}) >= x1 ({self.x1})", axis=Axis.horizontal) |
|
46
|
|
|
if self.y0 >= self.y1: |
|
47
|
|
|
raise RoiError(f"y0 ({self.y0}) >= y1 ({self.y1})", axis=Axis.vertical) |
|
48
|
|
|
|
|
49
|
|
|
def __repr__(self) -> str: |
|
50
|
|
|
return f"{self.row},{self.column}=({self.x0},{self.y0})→({self.x1},{self.y1})" |
|
51
|
|
|
|
|
52
|
|
|
def __str__(self): |
|
53
|
|
|
return repr(self) |
|
54
|
|
|
|
|
55
|
|
|
|
|
56
|
|
|
@dataclass(frozen=True, repr=True, order=True) |
|
|
|
|
|
|
57
|
|
|
class PlateRois: |
|
58
|
|
|
n_rows: int |
|
59
|
|
|
n_columns: int |
|
60
|
|
|
image_roi: WellRoi |
|
61
|
|
|
top_left_roi: WellRoi |
|
62
|
|
|
padx: float |
|
63
|
|
|
pady: float |
|
64
|
|
|
|
|
65
|
|
|
@property |
|
66
|
|
|
def well_rois(self): |
|
|
|
|
|
|
67
|
|
|
return self._get_roi_coordinates(self.top_left_roi, self.padx, self.pady) |
|
68
|
|
|
|
|
69
|
|
|
def __iter__(self): |
|
70
|
|
|
return iter(self.well_rois.keys()) |
|
71
|
|
|
|
|
72
|
|
|
def __len__(self): |
|
73
|
|
|
return len(self.well_rois) |
|
74
|
|
|
|
|
75
|
|
|
def __getitem__(self, item): |
|
76
|
|
|
try: |
|
77
|
|
|
return self.well_rois[item[0], item[1]] |
|
78
|
|
|
except (IndexError, AttributeError): |
|
79
|
|
|
raise TypeError("Must look up well ROIs by (row, column) tuple indices.") |
|
80
|
|
|
|
|
81
|
|
|
def _get_roi_coordinates( |
|
82
|
|
|
self, top_left_roi: WellRoi, padx: float, pady: float |
|
|
|
|
|
|
83
|
|
|
) -> Dict[typing.Tuple[int, int], WellRoi]: |
|
84
|
|
|
tl = top_left_roi |
|
|
|
|
|
|
85
|
|
|
width = top_left_roi.x1 - top_left_roi.x0 |
|
86
|
|
|
height = top_left_roi.y1 - top_left_roi.y0 |
|
87
|
|
|
wells_x_edge = tl.x0 + self.n_columns * width + (self.n_columns - 1) * padx |
|
88
|
|
|
wells_y_edge = tl.y0 + self.n_rows * height + (self.n_rows - 1) * pady |
|
89
|
|
|
# make sure the wells don't extend outside the image bounds |
|
90
|
|
|
if tl.x0 < self.image_roi.x0: |
|
91
|
|
|
raise RoiError(f"{tl.x0} < {self.image_roi.x0}", edge=Edge.left) |
|
92
|
|
|
if wells_x_edge > self.image_roi.x1: |
|
93
|
|
|
raise RoiError(f"{wells_x_edge} < {self.image_roi.x1}", edge=Edge.right) |
|
94
|
|
|
if tl.y0 < self.image_roi.y0: |
|
95
|
|
|
raise RoiError(f"{tl.y0} < {self.image_roi.y0}", edge=Edge.top) |
|
96
|
|
|
if wells_y_edge > self.image_roi.y1: |
|
97
|
|
|
raise RoiError(f"{wells_y_edge} > {self.image_roi.y1}", edge=Edge.bottom) |
|
98
|
|
|
# now build |
|
99
|
|
|
rois = {} |
|
100
|
|
|
x = tl.x0 |
|
|
|
|
|
|
101
|
|
|
y = tl.y0 |
|
|
|
|
|
|
102
|
|
|
for row in range(0, self.n_rows): |
|
103
|
|
|
for column in range(0, self.n_columns): |
|
104
|
|
|
rois[(row, column)] = WellRoi(row, column, x, y, x + width, y + height) |
|
105
|
|
|
x += width + padx |
|
|
|
|
|
|
106
|
|
|
y += height + pady |
|
|
|
|
|
|
107
|
|
|
x = tl.x0 |
|
|
|
|
|
|
108
|
|
|
return rois |
|
109
|
|
|
|
|
110
|
|
|
|
|
111
|
|
|
__all__ = ["WellRoi", "PlateRois"] |
|
112
|
|
|
|