valor-lite 0.33.7__py3-none-any.whl → 0.33.9__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,27 @@
1
+ from .annotation import Bitmask, Segmentation
2
+ from .manager import DataLoader, Evaluator
3
+ from .metric import (
4
+ F1,
5
+ Accuracy,
6
+ ConfusionMatrix,
7
+ IoU,
8
+ MetricType,
9
+ Precision,
10
+ Recall,
11
+ mIoU,
12
+ )
13
+
14
+ __all__ = [
15
+ "DataLoader",
16
+ "Evaluator",
17
+ "Segmentation",
18
+ "Bitmask",
19
+ "MetricType",
20
+ "Precision",
21
+ "Recall",
22
+ "Accuracy",
23
+ "F1",
24
+ "IoU",
25
+ "mIoU",
26
+ "ConfusionMatrix",
27
+ ]
@@ -0,0 +1,96 @@
1
+ from dataclasses import dataclass, field
2
+
3
+ import numpy as np
4
+ from numpy.typing import NDArray
5
+
6
+
7
+ @dataclass
8
+ class Bitmask:
9
+ """
10
+ Represents a binary mask with an associated semantic label.
11
+
12
+ Parameters
13
+ ----------
14
+ mask : NDArray[np.bool_]
15
+ A NumPy array of boolean values representing the mask.
16
+ label : str
17
+ The semantic label associated with the mask.
18
+
19
+ Examples
20
+ --------
21
+ >>> import numpy as np
22
+ >>> mask = np.array([[True, False], [False, True]], dtype=np.bool_)
23
+ >>> bitmask = Bitmask(mask=mask, label='ocean')
24
+ """
25
+
26
+ mask: NDArray[np.bool_]
27
+ label: str
28
+
29
+ def __post_init__(self):
30
+ if self.mask.dtype != np.bool_:
31
+ raise ValueError(
32
+ f"Bitmask recieved mask with dtype `{self.mask.dtype}`."
33
+ )
34
+
35
+
36
+ @dataclass
37
+ class Segmentation:
38
+ """
39
+ Segmentation data structure holding ground truth and prediction bitmasks for semantic segmentation tasks.
40
+
41
+ Parameters
42
+ ----------
43
+ uid : str
44
+ Unique identifier for the image or sample.
45
+ groundtruths : List[Bitmask]
46
+ List of ground truth bitmasks.
47
+ predictions : List[Bitmask]
48
+ List of predicted bitmasks.
49
+ shape : tuple of int, optional
50
+ The shape of the segmentation masks. This is set automatically after initialization.
51
+ size : int, optional
52
+ The total number of pixels in the masks. This is set automatically after initialization.
53
+
54
+ Examples
55
+ --------
56
+ >>> import numpy as np
57
+ >>> mask1 = np.array([[True, False], [False, True]], dtype=np.bool_)
58
+ >>> groundtruth = Bitmask(mask=mask1, label='object')
59
+ >>> mask2 = np.array([[False, True], [True, False]], dtype=np.bool_)
60
+ >>> prediction = Bitmask(mask=mask2, label='object')
61
+ >>> segmentation = Segmentation(
62
+ ... uid='123',
63
+ ... groundtruths=[groundtruth],
64
+ ... predictions=[prediction]
65
+ ... )
66
+ """
67
+
68
+ uid: str
69
+ groundtruths: list[Bitmask]
70
+ predictions: list[Bitmask]
71
+ shape: tuple[int, ...] = field(default_factory=lambda: (0, 0))
72
+ size: int = field(default=0)
73
+
74
+ def __post_init__(self):
75
+
76
+ groundtruth_shape = {
77
+ groundtruth.mask.shape for groundtruth in self.groundtruths
78
+ }
79
+ prediction_shape = {
80
+ prediction.mask.shape for prediction in self.predictions
81
+ }
82
+ if len(groundtruth_shape) == 0:
83
+ raise ValueError("The segmenation is missing ground truths.")
84
+ elif len(prediction_shape) == 0:
85
+ raise ValueError("The segmenation is missing predictions.")
86
+ elif (
87
+ len(groundtruth_shape) != 1
88
+ or len(prediction_shape) != 1
89
+ or groundtruth_shape != prediction_shape
90
+ ):
91
+ raise ValueError(
92
+ "A shape mismatch exists within the segmentation."
93
+ )
94
+
95
+ self.shape = groundtruth_shape.pop()
96
+ self.size = int(np.prod(np.array(self.shape)))
@@ -0,0 +1,186 @@
1
+ import numpy as np
2
+ from numpy.typing import NDArray
3
+
4
+
5
+ def compute_intermediate_confusion_matrices(
6
+ groundtruths: NDArray[np.bool_],
7
+ predictions: NDArray[np.bool_],
8
+ groundtruth_labels: NDArray[np.int32],
9
+ prediction_labels: NDArray[np.int32],
10
+ n_labels: int,
11
+ ) -> NDArray[np.int32]:
12
+ """
13
+ Computes an intermediate confusion matrix containing label counts.
14
+
15
+ Parameters
16
+ ----------
17
+ groundtruths : NDArray[np.bool_]
18
+ A 2-D array containing flattened bitmasks for each label.
19
+ predictions : NDArray[np.bool_]
20
+ A 2-D array containing flattened bitmasks for each label.
21
+ groundtruth_labels : NDArray[np.int32]
22
+ A 1-D array containing label indices.
23
+ groundtruth_labels : NDArray[np.int32]
24
+ A 1-D array containing label indices.
25
+ n_labels : int
26
+ The number of unique labels.
27
+
28
+ Returns
29
+ -------
30
+ NDArray[np.int32]
31
+ A 2-D confusion matrix with shape (n_labels + 1, n_labels + 1).
32
+ """
33
+
34
+ n_gt_labels = groundtruth_labels.size
35
+ n_pd_labels = prediction_labels.size
36
+
37
+ groundtruth_counts = groundtruths.sum(axis=1)
38
+ prediction_counts = predictions.sum(axis=1)
39
+
40
+ background_counts = np.logical_not(
41
+ groundtruths.any(axis=0) | predictions.any(axis=0)
42
+ ).sum()
43
+
44
+ intersection_counts = np.logical_and(
45
+ groundtruths.reshape(n_gt_labels, 1, -1),
46
+ predictions.reshape(1, n_pd_labels, -1),
47
+ ).sum(axis=2)
48
+
49
+ intersected_groundtruth_counts = intersection_counts.sum(axis=0)
50
+ intersected_prediction_counts = intersection_counts.sum(axis=1)
51
+
52
+ confusion_matrix = np.zeros((n_labels + 1, n_labels + 1), dtype=np.int32)
53
+ confusion_matrix[0, 0] = background_counts
54
+ for gidx in range(n_gt_labels):
55
+ gt_label_idx = groundtruth_labels[gidx]
56
+ for pidx in range(n_pd_labels):
57
+ pd_label_idx = prediction_labels[pidx]
58
+ confusion_matrix[
59
+ gt_label_idx + 1,
60
+ pd_label_idx + 1,
61
+ ] = intersection_counts[gidx, pidx]
62
+
63
+ if gidx == 0:
64
+ confusion_matrix[0, pd_label_idx + 1] = (
65
+ prediction_counts[pidx]
66
+ - intersected_prediction_counts[pidx]
67
+ )
68
+
69
+ confusion_matrix[gt_label_idx + 1, 0] = (
70
+ groundtruth_counts[gidx] - intersected_groundtruth_counts[gidx]
71
+ )
72
+
73
+ return confusion_matrix
74
+
75
+
76
+ def compute_metrics(
77
+ data: NDArray[np.float64],
78
+ label_metadata: NDArray[np.int32],
79
+ n_pixels: int,
80
+ ) -> tuple[
81
+ NDArray[np.float64],
82
+ NDArray[np.float64],
83
+ NDArray[np.float64],
84
+ float,
85
+ NDArray[np.float64],
86
+ NDArray[np.float64],
87
+ NDArray[np.float64],
88
+ ]:
89
+ """
90
+ Computes semantic segmentation metrics.
91
+
92
+ Takes data with shape (3, N).
93
+
94
+ Parameters
95
+ ----------
96
+ data : NDArray[np.float64]
97
+ A 3-D array containing confusion matrices for each datum.
98
+ label_metadata : NDArray[np.int32]
99
+ A 2-D array containing label metadata.
100
+
101
+ Returns
102
+ -------
103
+ NDArray[np.float64]
104
+ Precision.
105
+ NDArray[np.float64]
106
+ Recall.
107
+ NDArray[np.float64]
108
+ F1 Score.
109
+ float
110
+ Accuracy
111
+ NDArray[np.float64]
112
+ Confusion matrix containing IoU values.
113
+ NDArray[np.float64]
114
+ Hallucination ratios.
115
+ NDArray[np.float64]
116
+ Missing prediction ratios.
117
+ """
118
+ n_labels = label_metadata.shape[0]
119
+ gt_counts = label_metadata[:, 0]
120
+ pd_counts = label_metadata[:, 1]
121
+
122
+ counts = data.sum(axis=0)
123
+
124
+ # compute iou, missing_predictions and hallucinations
125
+ intersection_ = counts[1:, 1:]
126
+ union_ = (
127
+ gt_counts[:, np.newaxis] + pd_counts[np.newaxis, :] - intersection_
128
+ )
129
+
130
+ ious = np.zeros((n_labels, n_labels), dtype=np.float64)
131
+ np.divide(
132
+ intersection_,
133
+ union_,
134
+ where=union_ > 1e-9,
135
+ out=ious,
136
+ )
137
+
138
+ hallucination_ratio = np.zeros((n_labels), dtype=np.float64)
139
+ np.divide(
140
+ counts[0, 1:],
141
+ pd_counts,
142
+ where=pd_counts > 1e-9,
143
+ out=hallucination_ratio,
144
+ )
145
+
146
+ missing_prediction_ratio = np.zeros((n_labels), dtype=np.float64)
147
+ np.divide(
148
+ counts[1:, 0],
149
+ gt_counts,
150
+ where=gt_counts > 1e-9,
151
+ out=missing_prediction_ratio,
152
+ )
153
+
154
+ # compute precision, recall, f1
155
+ tp_counts = counts.diagonal()[1:]
156
+
157
+ precision = np.zeros(n_labels, dtype=np.float64)
158
+ np.divide(tp_counts, pd_counts, where=pd_counts > 1e-9, out=precision)
159
+
160
+ recall = np.zeros_like(precision)
161
+ np.divide(tp_counts, gt_counts, where=gt_counts > 1e-9, out=recall)
162
+
163
+ f1_score = np.zeros_like(precision)
164
+ np.divide(
165
+ 2 * (precision * recall),
166
+ (precision + recall),
167
+ where=(precision + recall) > 0,
168
+ out=f1_score,
169
+ )
170
+
171
+ # compute accuracy
172
+ tp_count = counts[1:, 1:].diagonal().sum()
173
+ background_count = counts[0, 0]
174
+ accuracy = (
175
+ (tp_count + background_count) / n_pixels if n_pixels > 0 else 0.0
176
+ )
177
+
178
+ return (
179
+ precision,
180
+ recall,
181
+ f1_score,
182
+ accuracy,
183
+ ious,
184
+ hallucination_ratio,
185
+ missing_prediction_ratio,
186
+ )