valor-lite 0.35.0__py3-none-any.whl → 0.36.0__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.
Potentially problematic release.
This version of valor-lite might be problematic. Click here for more details.
- valor_lite/classification/computation.py +147 -38
- valor_lite/classification/manager.py +229 -236
- valor_lite/classification/metric.py +5 -8
- valor_lite/classification/utilities.py +18 -14
- valor_lite/object_detection/__init__.py +2 -1
- valor_lite/object_detection/computation.py +91 -0
- valor_lite/object_detection/manager.py +313 -304
- valor_lite/semantic_segmentation/__init__.py +3 -3
- valor_lite/semantic_segmentation/annotation.py +32 -103
- valor_lite/semantic_segmentation/benchmark.py +87 -1
- valor_lite/semantic_segmentation/computation.py +96 -14
- valor_lite/semantic_segmentation/manager.py +199 -222
- valor_lite/semantic_segmentation/utilities.py +3 -3
- {valor_lite-0.35.0.dist-info → valor_lite-0.36.0.dist-info}/METADATA +2 -2
- {valor_lite-0.35.0.dist-info → valor_lite-0.36.0.dist-info}/RECORD +17 -17
- {valor_lite-0.35.0.dist-info → valor_lite-0.36.0.dist-info}/WHEEL +1 -1
- {valor_lite-0.35.0.dist-info → valor_lite-0.36.0.dist-info}/top_level.txt +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
from .annotation import Bitmask, Segmentation
|
|
2
|
-
from .manager import DataLoader, Evaluator
|
|
1
|
+
from .annotation import Bitmask, Segmentation
|
|
2
|
+
from .manager import DataLoader, Evaluator, Filter
|
|
3
3
|
from .metric import Metric, MetricType
|
|
4
4
|
|
|
5
5
|
__all__ = [
|
|
@@ -9,5 +9,5 @@ __all__ = [
|
|
|
9
9
|
"Bitmask",
|
|
10
10
|
"Metric",
|
|
11
11
|
"MetricType",
|
|
12
|
-
"
|
|
12
|
+
"Filter",
|
|
13
13
|
]
|
|
@@ -68,112 +68,41 @@ class Segmentation:
|
|
|
68
68
|
uid: str
|
|
69
69
|
groundtruths: list[Bitmask]
|
|
70
70
|
predictions: list[Bitmask]
|
|
71
|
-
shape: tuple[int, ...]
|
|
71
|
+
shape: tuple[int, ...]
|
|
72
72
|
size: int = field(default=0)
|
|
73
73
|
|
|
74
74
|
def __post_init__(self):
|
|
75
75
|
|
|
76
|
-
|
|
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
|
-
):
|
|
76
|
+
if len(self.shape) != 2 or self.shape[0] <= 0 or self.shape[1] <= 0:
|
|
91
77
|
raise ValueError(
|
|
92
|
-
"
|
|
78
|
+
f"segmentations must be 2-dimensional and have non-zero dimensions. Recieved shape '{self.shape}'"
|
|
93
79
|
)
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
"""
|
|
124
|
-
|
|
125
|
-
if number_of_unique_labels > 1:
|
|
126
|
-
common_proba = 0.4 / (number_of_unique_labels - 1)
|
|
127
|
-
min_proba = min(common_proba, 0.1)
|
|
128
|
-
labels = [str(i) for i in range(number_of_unique_labels)] + [None]
|
|
129
|
-
proba = (
|
|
130
|
-
[0.5]
|
|
131
|
-
+ [common_proba for _ in range(number_of_unique_labels - 1)]
|
|
132
|
-
+ [0.1]
|
|
133
|
-
)
|
|
134
|
-
elif number_of_unique_labels == 1:
|
|
135
|
-
labels = ["0", None]
|
|
136
|
-
proba = [0.9, 0.1]
|
|
137
|
-
min_proba = 0.1
|
|
138
|
-
else:
|
|
139
|
-
raise ValueError(
|
|
140
|
-
"The number of unique labels should be greater than zero."
|
|
141
|
-
)
|
|
142
|
-
|
|
143
|
-
probabilities = np.array(proba, dtype=np.float64)
|
|
144
|
-
weights = (probabilities / min_proba).astype(np.int32)
|
|
145
|
-
|
|
146
|
-
indices = np.random.choice(
|
|
147
|
-
np.arange(len(weights)),
|
|
148
|
-
size=(mask_height * 2, mask_width),
|
|
149
|
-
p=probabilities,
|
|
150
|
-
)
|
|
151
|
-
|
|
152
|
-
N = len(labels)
|
|
153
|
-
|
|
154
|
-
masks = np.arange(N)[:, None, None] == indices
|
|
155
|
-
|
|
156
|
-
gts = []
|
|
157
|
-
pds = []
|
|
158
|
-
for lidx in range(N):
|
|
159
|
-
label = labels[lidx]
|
|
160
|
-
if label is None:
|
|
161
|
-
continue
|
|
162
|
-
gts.append(
|
|
163
|
-
Bitmask(
|
|
164
|
-
mask=masks[lidx, :mask_height, :],
|
|
165
|
-
label=label,
|
|
166
|
-
)
|
|
167
|
-
)
|
|
168
|
-
pds.append(
|
|
169
|
-
Bitmask(
|
|
170
|
-
mask=masks[lidx, mask_height:, :],
|
|
171
|
-
label=label,
|
|
172
|
-
)
|
|
173
|
-
)
|
|
174
|
-
|
|
175
|
-
return Segmentation(
|
|
176
|
-
uid=datum_uid,
|
|
177
|
-
groundtruths=gts,
|
|
178
|
-
predictions=pds,
|
|
179
|
-
)
|
|
80
|
+
self.size = self.shape[0] * self.shape[1]
|
|
81
|
+
|
|
82
|
+
mask_accumulation = None
|
|
83
|
+
for groundtruth in self.groundtruths:
|
|
84
|
+
if self.shape != groundtruth.mask.shape:
|
|
85
|
+
raise ValueError(
|
|
86
|
+
f"ground truth masks for datum '{self.uid}' should have shape '{self.shape}'. Received mask with shape '{groundtruth.mask.shape}'"
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
if mask_accumulation is None:
|
|
90
|
+
mask_accumulation = groundtruth.mask.copy()
|
|
91
|
+
elif np.logical_and(mask_accumulation, groundtruth.mask).any():
|
|
92
|
+
raise ValueError("ground truth masks cannot overlap")
|
|
93
|
+
else:
|
|
94
|
+
mask_accumulation = mask_accumulation | groundtruth.mask
|
|
95
|
+
|
|
96
|
+
mask_accumulation = None
|
|
97
|
+
for prediction in self.predictions:
|
|
98
|
+
if self.shape != prediction.mask.shape:
|
|
99
|
+
raise ValueError(
|
|
100
|
+
f"prediction masks for datum '{self.uid}' should have shape '{self.shape}'. Received mask with shape '{prediction.mask.shape}'"
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
if mask_accumulation is None:
|
|
104
|
+
mask_accumulation = prediction.mask.copy()
|
|
105
|
+
elif np.logical_and(mask_accumulation, prediction.mask).any():
|
|
106
|
+
raise ValueError("prediction masks cannot overlap")
|
|
107
|
+
else:
|
|
108
|
+
mask_accumulation = mask_accumulation | prediction.mask
|
|
@@ -1,5 +1,91 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
1
3
|
from valor_lite.profiling import create_runtime_profiler
|
|
2
|
-
from valor_lite.semantic_segmentation import DataLoader,
|
|
4
|
+
from valor_lite.semantic_segmentation import Bitmask, DataLoader, Segmentation
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def generate_segmentation(
|
|
8
|
+
datum_uid: str,
|
|
9
|
+
number_of_unique_labels: int,
|
|
10
|
+
mask_height: int,
|
|
11
|
+
mask_width: int,
|
|
12
|
+
) -> Segmentation:
|
|
13
|
+
"""
|
|
14
|
+
Generates a semantic segmentation annotation.
|
|
15
|
+
|
|
16
|
+
Parameters
|
|
17
|
+
----------
|
|
18
|
+
datum_uid : str
|
|
19
|
+
The datum UID for the generated segmentation.
|
|
20
|
+
number_of_unique_labels : int
|
|
21
|
+
The number of unique labels.
|
|
22
|
+
mask_height : int
|
|
23
|
+
The height of the mask in pixels.
|
|
24
|
+
mask_width : int
|
|
25
|
+
The width of the mask in pixels.
|
|
26
|
+
|
|
27
|
+
Returns
|
|
28
|
+
-------
|
|
29
|
+
Segmentation
|
|
30
|
+
A generated semantic segmenatation annotation.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
if number_of_unique_labels > 1:
|
|
34
|
+
common_proba = 0.4 / (number_of_unique_labels - 1)
|
|
35
|
+
min_proba = min(common_proba, 0.1)
|
|
36
|
+
labels = [str(i) for i in range(number_of_unique_labels)] + [None]
|
|
37
|
+
proba = (
|
|
38
|
+
[0.5]
|
|
39
|
+
+ [common_proba for _ in range(number_of_unique_labels - 1)]
|
|
40
|
+
+ [0.1]
|
|
41
|
+
)
|
|
42
|
+
elif number_of_unique_labels == 1:
|
|
43
|
+
labels = ["0", None]
|
|
44
|
+
proba = [0.9, 0.1]
|
|
45
|
+
min_proba = 0.1
|
|
46
|
+
else:
|
|
47
|
+
raise ValueError(
|
|
48
|
+
"The number of unique labels should be greater than zero."
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
probabilities = np.array(proba, dtype=np.float64)
|
|
52
|
+
weights = (probabilities / min_proba).astype(np.int32)
|
|
53
|
+
|
|
54
|
+
indices = np.random.choice(
|
|
55
|
+
np.arange(len(weights)),
|
|
56
|
+
size=(mask_height * 2, mask_width),
|
|
57
|
+
p=probabilities,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
N = len(labels)
|
|
61
|
+
|
|
62
|
+
masks = np.arange(N)[:, None, None] == indices
|
|
63
|
+
|
|
64
|
+
gts = []
|
|
65
|
+
pds = []
|
|
66
|
+
for lidx in range(N):
|
|
67
|
+
label = labels[lidx]
|
|
68
|
+
if label is None:
|
|
69
|
+
continue
|
|
70
|
+
gts.append(
|
|
71
|
+
Bitmask(
|
|
72
|
+
mask=masks[lidx, :mask_height, :],
|
|
73
|
+
label=label,
|
|
74
|
+
)
|
|
75
|
+
)
|
|
76
|
+
pds.append(
|
|
77
|
+
Bitmask(
|
|
78
|
+
mask=masks[lidx, mask_height:, :],
|
|
79
|
+
label=label,
|
|
80
|
+
)
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
return Segmentation(
|
|
84
|
+
uid=datum_uid,
|
|
85
|
+
groundtruths=gts,
|
|
86
|
+
predictions=pds,
|
|
87
|
+
shape=(mask_height, mask_width),
|
|
88
|
+
)
|
|
3
89
|
|
|
4
90
|
|
|
5
91
|
def benchmark_add_data(
|
|
@@ -2,13 +2,93 @@ import numpy as np
|
|
|
2
2
|
from numpy.typing import NDArray
|
|
3
3
|
|
|
4
4
|
|
|
5
|
+
def compute_label_metadata(
|
|
6
|
+
confusion_matrices: NDArray[np.int64],
|
|
7
|
+
n_labels: int,
|
|
8
|
+
) -> NDArray[np.int64]:
|
|
9
|
+
"""
|
|
10
|
+
Computes label metadata returning a count of annotations per label.
|
|
11
|
+
|
|
12
|
+
Parameters
|
|
13
|
+
----------
|
|
14
|
+
confusion_matrices : NDArray[np.int64]
|
|
15
|
+
Confusion matrices per datum with shape (n_datums, n_labels + 1, n_labels + 1).
|
|
16
|
+
n_labels : int
|
|
17
|
+
The total number of unique labels.
|
|
18
|
+
|
|
19
|
+
Returns
|
|
20
|
+
-------
|
|
21
|
+
NDArray[np.int64]
|
|
22
|
+
The label metadata array with shape (n_labels, 2).
|
|
23
|
+
Index 0 - Ground truth label count
|
|
24
|
+
Index 1 - Prediction label count
|
|
25
|
+
"""
|
|
26
|
+
label_metadata = np.zeros((n_labels, 2), dtype=np.int64)
|
|
27
|
+
label_metadata[:, 0] = confusion_matrices[:, 1:, :].sum(axis=(0, 2))
|
|
28
|
+
label_metadata[:, 1] = confusion_matrices[:, :, 1:].sum(axis=(0, 1))
|
|
29
|
+
return label_metadata
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def filter_cache(
|
|
33
|
+
confusion_matrices: NDArray[np.int64],
|
|
34
|
+
datum_mask: NDArray[np.bool_],
|
|
35
|
+
label_mask: NDArray[np.bool_],
|
|
36
|
+
number_of_labels: int,
|
|
37
|
+
) -> tuple[NDArray[np.int64], NDArray[np.int64]]:
|
|
38
|
+
"""
|
|
39
|
+
Performs the filter operation over the internal cache.
|
|
40
|
+
|
|
41
|
+
Parameters
|
|
42
|
+
----------
|
|
43
|
+
confusion_matrices : NDArray[int64]
|
|
44
|
+
The internal evaluator cache.
|
|
45
|
+
datum_mask : NDArray[bool]
|
|
46
|
+
A mask that filters out datums.
|
|
47
|
+
datum_mask : NDArray[bool]
|
|
48
|
+
A mask that filters out labels.
|
|
49
|
+
|
|
50
|
+
Returns
|
|
51
|
+
-------
|
|
52
|
+
NDArray[int64]
|
|
53
|
+
Filtered confusion matrices.
|
|
54
|
+
NDArray[int64]
|
|
55
|
+
Filtered label metadata.
|
|
56
|
+
"""
|
|
57
|
+
if label_mask.any():
|
|
58
|
+
# add filtered labels to background
|
|
59
|
+
null_predictions = confusion_matrices[:, label_mask, :].sum(
|
|
60
|
+
axis=(1, 2)
|
|
61
|
+
)
|
|
62
|
+
null_groundtruths = confusion_matrices[:, :, label_mask].sum(
|
|
63
|
+
axis=(1, 2)
|
|
64
|
+
)
|
|
65
|
+
null_intersection = (
|
|
66
|
+
confusion_matrices[:, label_mask, label_mask]
|
|
67
|
+
.reshape(confusion_matrices.shape[0], -1)
|
|
68
|
+
.sum(axis=1)
|
|
69
|
+
)
|
|
70
|
+
confusion_matrices[:, 0, 0] += (
|
|
71
|
+
null_groundtruths + null_predictions - null_intersection
|
|
72
|
+
)
|
|
73
|
+
confusion_matrices[:, label_mask, :] = 0
|
|
74
|
+
confusion_matrices[:, :, label_mask] = 0
|
|
75
|
+
|
|
76
|
+
confusion_matrices = confusion_matrices[datum_mask]
|
|
77
|
+
|
|
78
|
+
label_metadata = compute_label_metadata(
|
|
79
|
+
confusion_matrices=confusion_matrices,
|
|
80
|
+
n_labels=number_of_labels,
|
|
81
|
+
)
|
|
82
|
+
return confusion_matrices, label_metadata
|
|
83
|
+
|
|
84
|
+
|
|
5
85
|
def compute_intermediate_confusion_matrices(
|
|
6
86
|
groundtruths: NDArray[np.bool_],
|
|
7
87
|
predictions: NDArray[np.bool_],
|
|
8
|
-
groundtruth_labels: NDArray[np.
|
|
9
|
-
prediction_labels: NDArray[np.
|
|
88
|
+
groundtruth_labels: NDArray[np.int64],
|
|
89
|
+
prediction_labels: NDArray[np.int64],
|
|
10
90
|
n_labels: int,
|
|
11
|
-
) -> NDArray[np.
|
|
91
|
+
) -> NDArray[np.int64]:
|
|
12
92
|
"""
|
|
13
93
|
Computes an intermediate confusion matrix containing label counts.
|
|
14
94
|
|
|
@@ -18,16 +98,16 @@ def compute_intermediate_confusion_matrices(
|
|
|
18
98
|
A 2-D array containing flattened bitmasks for each label.
|
|
19
99
|
predictions : NDArray[np.bool_]
|
|
20
100
|
A 2-D array containing flattened bitmasks for each label.
|
|
21
|
-
groundtruth_labels : NDArray[np.
|
|
101
|
+
groundtruth_labels : NDArray[np.int64]
|
|
22
102
|
A 1-D array containing label indices.
|
|
23
|
-
groundtruth_labels : NDArray[np.
|
|
103
|
+
groundtruth_labels : NDArray[np.int64]
|
|
24
104
|
A 1-D array containing label indices.
|
|
25
105
|
n_labels : int
|
|
26
106
|
The number of unique labels.
|
|
27
107
|
|
|
28
108
|
Returns
|
|
29
109
|
-------
|
|
30
|
-
NDArray[np.
|
|
110
|
+
NDArray[np.int64]
|
|
31
111
|
A 2-D confusion matrix with shape (n_labels + 1, n_labels + 1).
|
|
32
112
|
"""
|
|
33
113
|
|
|
@@ -45,7 +125,7 @@ def compute_intermediate_confusion_matrices(
|
|
|
45
125
|
intersected_groundtruth_counts = intersection_counts.sum(axis=1)
|
|
46
126
|
intersected_prediction_counts = intersection_counts.sum(axis=0)
|
|
47
127
|
|
|
48
|
-
confusion_matrix = np.zeros((n_labels + 1, n_labels + 1), dtype=np.
|
|
128
|
+
confusion_matrix = np.zeros((n_labels + 1, n_labels + 1), dtype=np.int64)
|
|
49
129
|
confusion_matrix[0, 0] = background_counts
|
|
50
130
|
confusion_matrix[
|
|
51
131
|
np.ix_(groundtruth_labels + 1, prediction_labels + 1)
|
|
@@ -61,8 +141,8 @@ def compute_intermediate_confusion_matrices(
|
|
|
61
141
|
|
|
62
142
|
|
|
63
143
|
def compute_metrics(
|
|
64
|
-
|
|
65
|
-
label_metadata: NDArray[np.
|
|
144
|
+
confusion_matrices: NDArray[np.int64],
|
|
145
|
+
label_metadata: NDArray[np.int64],
|
|
66
146
|
n_pixels: int,
|
|
67
147
|
) -> tuple[
|
|
68
148
|
NDArray[np.float64],
|
|
@@ -80,10 +160,12 @@ def compute_metrics(
|
|
|
80
160
|
|
|
81
161
|
Parameters
|
|
82
162
|
----------
|
|
83
|
-
|
|
84
|
-
A 3-D array containing confusion matrices for each datum.
|
|
85
|
-
label_metadata : NDArray[np.
|
|
86
|
-
A 2-D array containing label metadata.
|
|
163
|
+
confusion_matrices : NDArray[np.int64]
|
|
164
|
+
A 3-D array containing confusion matrices for each datum with shape (n_datums, n_labels + 1, n_labels + 1).
|
|
165
|
+
label_metadata : NDArray[np.int64]
|
|
166
|
+
A 2-D array containing label metadata with shape (n_labels, 2).
|
|
167
|
+
Index 0: Ground Truth Label Count
|
|
168
|
+
Index 1: Prediction Label Count
|
|
87
169
|
|
|
88
170
|
Returns
|
|
89
171
|
-------
|
|
@@ -106,7 +188,7 @@ def compute_metrics(
|
|
|
106
188
|
gt_counts = label_metadata[:, 0]
|
|
107
189
|
pd_counts = label_metadata[:, 1]
|
|
108
190
|
|
|
109
|
-
counts =
|
|
191
|
+
counts = confusion_matrices.sum(axis=0)
|
|
110
192
|
|
|
111
193
|
# compute iou, unmatched_ground_truth and unmatched predictions
|
|
112
194
|
intersection_ = counts[1:, 1:]
|