|
10 | 10 | import cv2 |
11 | 11 | import numpy as np |
12 | 12 |
|
| 13 | +from .detection import DetectionResult |
13 | 14 | from .utils import array_shape_to_str |
14 | 15 |
|
15 | 16 | if TYPE_CHECKING: |
16 | 17 | from cv2.typing import RotatedRect |
17 | 18 |
|
18 | 19 |
|
19 | | -class SegmentedObject: |
20 | | - def __init__( |
21 | | - self, |
22 | | - xmin: int, |
23 | | - ymin: int, |
24 | | - xmax: int, |
25 | | - ymax: int, |
26 | | - score: float, |
27 | | - id: int, |
28 | | - str_label: str, |
29 | | - mask: np.ndarray, |
30 | | - ) -> None: |
31 | | - self.xmin = xmin |
32 | | - self.ymin = ymin |
33 | | - self.xmax = xmax |
34 | | - self.ymax = ymax |
35 | | - self.score = score |
36 | | - self.id = id |
37 | | - self.str_label = str_label |
38 | | - self.mask = mask |
39 | | - |
40 | | - def __str__(self): |
41 | | - return ( |
42 | | - f"{self.xmin}, {self.ymin}, {self.xmax}, {self.ymax}, {self.id} ({self.str_label}): {self.score:.3f}" |
43 | | - f", {(self.mask > 0.5).sum()}" |
44 | | - ) |
45 | | - |
| 20 | +class InstanceSegmentationResult(DetectionResult): |
| 21 | + """Instance segmentation result type. |
46 | 22 |
|
47 | | -class SegmentedObjectWithRects(SegmentedObject): |
48 | | - def __init__(self, segmented_object: SegmentedObject, rotated_rect: RotatedRect) -> None: |
49 | | - super().__init__( |
50 | | - segmented_object.xmin, |
51 | | - segmented_object.ymin, |
52 | | - segmented_object.xmax, |
53 | | - segmented_object.ymax, |
54 | | - segmented_object.score, |
55 | | - segmented_object.id, |
56 | | - segmented_object.str_label, |
57 | | - segmented_object.mask, |
58 | | - ) |
59 | | - self.rotated_rect = rotated_rect |
| 23 | + Args: |
| 24 | + bboxes (np.ndarray): bounding boxes in dim (N, 4) where N is the number of boxes. |
| 25 | + labels (np.ndarray): labels for each bounding box in dim (N,). |
| 26 | + masks (np.ndarray): masks for each bounding box in dim (N, H, W). |
| 27 | + scores (np.ndarray | None, optional): confidence scores for each bounding box in dim (N,). Defaults to None. |
| 28 | + label_names (list[str] | None, optional): class names for each label. Defaults to None. |
| 29 | + saliency_map (list[np.ndarray] | None, optional): saliency maps for XAI. Defaults to None. |
| 30 | + feature_vector (np.ndarray | None, optional): feature vector for XAI. Defaults to None. |
| 31 | + """ |
60 | 32 |
|
61 | | - def __str__(self): |
62 | | - res = super().__str__() |
63 | | - rect = self.rotated_rect |
64 | | - res += f", RotatedRect: {rect[0][0]:.3f} {rect[0][1]:.3f} {rect[1][0]:.3f} {rect[1][1]:.3f} {rect[2]:.3f}" |
65 | | - return res |
| 33 | + def __init__( |
| 34 | + self, |
| 35 | + bboxes: np.ndarray, |
| 36 | + labels: np.ndarray, |
| 37 | + masks: np.ndarray, |
| 38 | + scores: np.ndarray | None = None, |
| 39 | + label_names: list[str] | None = None, |
| 40 | + saliency_map: list[np.ndarray] | None = None, |
| 41 | + feature_vector: np.ndarray | None = None, |
| 42 | + ): |
| 43 | + super().__init__(bboxes, labels, scores, label_names, saliency_map, feature_vector) |
| 44 | + self._masks = masks |
| 45 | + |
| 46 | + def __str__(self) -> str: |
| 47 | + repr_str = "" |
| 48 | + for box, score, label, name, mask in zip( |
| 49 | + self.bboxes, |
| 50 | + self.scores, |
| 51 | + self.labels, |
| 52 | + self.label_names, |
| 53 | + self.masks, |
| 54 | + strict=True, |
| 55 | + ): |
| 56 | + x1, y1, x2, y2 = box |
| 57 | + repr_str += f"{x1}, {y1}, {x2}, {y2}, {label} ({name}): {score:.3f}, {(mask > 0.5).sum()}; " |
66 | 58 |
|
| 59 | + filled = 0 |
| 60 | + for cls_map in self.saliency_map: |
| 61 | + if cls_map.size: |
| 62 | + filled += 1 |
| 63 | + prefix = f"{repr_str}" if len(repr_str) else "" |
| 64 | + return prefix + f"{filled}; {array_shape_to_str(self.feature_vector)}" |
| 65 | + |
| 66 | + @property |
| 67 | + def masks(self) -> np.ndarray: |
| 68 | + return self._masks |
| 69 | + |
| 70 | + @masks.setter |
| 71 | + def masks(self, value): |
| 72 | + if not isinstance(value, np.ndarray): |
| 73 | + msg = "Masks must be numpy array." |
| 74 | + raise ValueError(msg) |
| 75 | + self._masks = value |
| 76 | + |
| 77 | + @property |
| 78 | + def saliency_map(self): |
| 79 | + return self._saliency_map |
| 80 | + |
| 81 | + @saliency_map.setter |
| 82 | + def saliency_map(self, value: list[np.ndarray]): |
| 83 | + if not isinstance(value, list): |
| 84 | + msg = "Saliency maps must be list." |
| 85 | + raise ValueError(msg) |
| 86 | + self._saliency_map = value |
| 87 | + |
| 88 | + |
| 89 | +class RotatedSegmentationResult(InstanceSegmentationResult): |
| 90 | + """Rotated instance segmentation result type. |
| 91 | +
|
| 92 | + Args: |
| 93 | + bboxes (np.ndarray): bounding boxes in dim (N, 4) where N is the number of boxes. |
| 94 | + labels (np.ndarray): labels for each bounding box in dim (N,). |
| 95 | + masks (np.ndarray): masks for each bounding box in dim (N, H, W). |
| 96 | + rotated_rects (list[RotatedRect]): rotated rectangles for each bounding box. |
| 97 | + scores (np.ndarray | None, optional): confidence scores for each bounding box in dim (N,). Defaults to None. |
| 98 | + label_names (list[str] | None, optional): class names for each label. Defaults to None. |
| 99 | + saliency_map (list[np.ndarray] | None, optional): saliency maps for XAI. Defaults to None. |
| 100 | + feature_vector (np.ndarray | None, optional): feature vector for XAI. Defaults to None. |
| 101 | + """ |
67 | 102 |
|
68 | | -class InstanceSegmentationResult: |
69 | 103 | def __init__( |
70 | 104 | self, |
71 | | - segmentedObjects: list[SegmentedObject | SegmentedObjectWithRects], |
72 | | - saliency_map: list[np.ndarray], |
73 | | - feature_vector: np.ndarray, |
| 105 | + bboxes: np.ndarray, |
| 106 | + labels: np.ndarray, |
| 107 | + masks: np.ndarray, |
| 108 | + rotated_rects: list[RotatedRect], |
| 109 | + scores: np.ndarray | None = None, |
| 110 | + label_names: list[str] | None = None, |
| 111 | + saliency_map: list[np.ndarray] | None = None, |
| 112 | + feature_vector: np.ndarray | None = None, |
74 | 113 | ): |
75 | | - self.segmentedObjects = segmentedObjects |
76 | | - # Contain per class saliency_maps and "feature_vector" model output if feature_vector exists |
77 | | - self.saliency_map = saliency_map |
78 | | - self.feature_vector = feature_vector |
| 114 | + super().__init__(bboxes, labels, masks, scores, label_names, saliency_map, feature_vector) |
| 115 | + self.rotated_rects = rotated_rects |
| 116 | + |
| 117 | + def __str__(self) -> str: |
| 118 | + repr_str = "" |
| 119 | + for box, score, label, name, mask, rotated_rect in zip( |
| 120 | + self.bboxes, |
| 121 | + self.scores, |
| 122 | + self.labels, |
| 123 | + self.label_names, |
| 124 | + self.masks, |
| 125 | + self.rotated_rects, |
| 126 | + strict=True, |
| 127 | + ): |
| 128 | + x1, y1, x2, y2 = box |
| 129 | + (cx, cy), (w, h), angle = rotated_rect |
| 130 | + repr_str += f"{x1}, {y1}, {x2}, {y2}, {label} ({name}): {score:.3f}, {(mask > 0.5).sum()}," |
| 131 | + repr_str += f" RotatedRect: {cx:.3f} {cy:.3f} {w:.3f} {h:.3f} {angle:.3f}; " |
79 | 132 |
|
80 | | - def __str__(self): |
81 | | - obj_str = "; ".join(str(obj) for obj in self.segmentedObjects) |
82 | 133 | filled = 0 |
83 | 134 | for cls_map in self.saliency_map: |
84 | 135 | if cls_map.size: |
85 | 136 | filled += 1 |
86 | | - prefix = f"{obj_str}; " if len(obj_str) else "" |
87 | | - return prefix + f"{filled}; [{','.join(str(i) for i in self.feature_vector.shape)}]" |
| 137 | + prefix = f"{repr_str}" if len(repr_str) else "" |
| 138 | + return prefix + f"{filled}; {array_shape_to_str(self.feature_vector)}" |
| 139 | + |
| 140 | + @property |
| 141 | + def rotated_rects(self) -> list[RotatedRect]: |
| 142 | + return self._rotated_rects |
| 143 | + |
| 144 | + @rotated_rects.setter |
| 145 | + def rotated_rects(self, value): |
| 146 | + if not isinstance(value, list): |
| 147 | + msg = "RotatedRects must be list." |
| 148 | + raise ValueError(msg) |
| 149 | + self._rotated_rects = value |
88 | 150 |
|
89 | 151 |
|
90 | 152 | class Contour: |
|
0 commit comments