valor-lite 0.33.2__py3-none-any.whl → 0.33.4__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/detection/__init__.py +3 -3
- valor_lite/detection/annotation.py +48 -4
- valor_lite/detection/computation.py +165 -76
- valor_lite/detection/manager.py +610 -286
- valor_lite/detection/metric.py +32 -7
- {valor_lite-0.33.2.dist-info → valor_lite-0.33.4.dist-info}/METADATA +2 -1
- valor_lite-0.33.4.dist-info/RECORD +12 -0
- valor_lite-0.33.2.dist-info/RECORD +0 -12
- {valor_lite-0.33.2.dist-info → valor_lite-0.33.4.dist-info}/LICENSE +0 -0
- {valor_lite-0.33.2.dist-info → valor_lite-0.33.4.dist-info}/WHEEL +0 -0
- {valor_lite-0.33.2.dist-info → valor_lite-0.33.4.dist-info}/top_level.txt +0 -0
valor_lite/detection/manager.py
CHANGED
|
@@ -3,12 +3,20 @@ from dataclasses import dataclass
|
|
|
3
3
|
|
|
4
4
|
import numpy as np
|
|
5
5
|
from numpy.typing import NDArray
|
|
6
|
+
from shapely.geometry import Polygon as ShapelyPolygon
|
|
6
7
|
from tqdm import tqdm
|
|
7
|
-
from valor_lite.detection.annotation import
|
|
8
|
+
from valor_lite.detection.annotation import (
|
|
9
|
+
Bitmask,
|
|
10
|
+
BoundingBox,
|
|
11
|
+
Detection,
|
|
12
|
+
Polygon,
|
|
13
|
+
)
|
|
8
14
|
from valor_lite.detection.computation import (
|
|
15
|
+
compute_bbox_iou,
|
|
16
|
+
compute_bitmask_iou,
|
|
9
17
|
compute_detailed_counts,
|
|
10
|
-
compute_iou,
|
|
11
18
|
compute_metrics,
|
|
19
|
+
compute_polygon_iou,
|
|
12
20
|
compute_ranked_pairs,
|
|
13
21
|
)
|
|
14
22
|
from valor_lite.detection.metric import (
|
|
@@ -35,7 +43,7 @@ Usage
|
|
|
35
43
|
-----
|
|
36
44
|
|
|
37
45
|
loader = DataLoader()
|
|
38
|
-
loader.
|
|
46
|
+
loader.add_bounding_boxes(
|
|
39
47
|
groundtruths=groundtruths,
|
|
40
48
|
predictions=predictions,
|
|
41
49
|
)
|
|
@@ -51,6 +59,103 @@ filtered_metrics = evaluator.evaluate(iou_thresholds=[0.5], filter_mask=filter_m
|
|
|
51
59
|
"""
|
|
52
60
|
|
|
53
61
|
|
|
62
|
+
def _get_valor_dict_annotation_key(
|
|
63
|
+
annotation_type: type[BoundingBox] | type[Polygon] | type[Bitmask],
|
|
64
|
+
) -> str:
|
|
65
|
+
"""Get the correct JSON key to extract a given annotation type."""
|
|
66
|
+
|
|
67
|
+
if issubclass(annotation_type, BoundingBox):
|
|
68
|
+
return "bounding_box"
|
|
69
|
+
if issubclass(annotation_type, Polygon):
|
|
70
|
+
return "polygon"
|
|
71
|
+
else:
|
|
72
|
+
return "raster"
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _get_annotation_representation(
|
|
76
|
+
annotation_type: type[BoundingBox] | type[Polygon] | type[Bitmask],
|
|
77
|
+
) -> str:
|
|
78
|
+
"""Get the correct representation of an annotation object."""
|
|
79
|
+
|
|
80
|
+
representation = (
|
|
81
|
+
"extrema"
|
|
82
|
+
if issubclass(annotation_type, BoundingBox)
|
|
83
|
+
else ("mask" if issubclass(annotation_type, Bitmask) else "shape")
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
return representation
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _get_annotation_representation_from_valor_dict(
|
|
90
|
+
data: list,
|
|
91
|
+
annotation_type: type[BoundingBox] | type[Polygon] | type[Bitmask],
|
|
92
|
+
) -> tuple[float, float, float, float] | ShapelyPolygon | NDArray[np.bool_]:
|
|
93
|
+
"""Get the correct representation of an annotation object from a valor dictionary."""
|
|
94
|
+
|
|
95
|
+
if issubclass(annotation_type, BoundingBox):
|
|
96
|
+
x = [point[0] for shape in data for point in shape]
|
|
97
|
+
y = [point[1] for shape in data for point in shape]
|
|
98
|
+
return (min(x), max(x), min(y), max(y))
|
|
99
|
+
if issubclass(annotation_type, Polygon):
|
|
100
|
+
return ShapelyPolygon(data)
|
|
101
|
+
else:
|
|
102
|
+
return np.array(data)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _get_annotation_data(
|
|
106
|
+
keyed_groundtruths: dict,
|
|
107
|
+
keyed_predictions: dict,
|
|
108
|
+
annotation_type: type[BoundingBox] | type[Polygon] | type[Bitmask] | None,
|
|
109
|
+
key=int,
|
|
110
|
+
) -> np.ndarray:
|
|
111
|
+
"""Create an array of annotation pairs for use when calculating IOU. Needed because we unpack bounding box representations, but not bitmask or polygon representations."""
|
|
112
|
+
if annotation_type == BoundingBox:
|
|
113
|
+
return np.array(
|
|
114
|
+
[
|
|
115
|
+
np.array([*gextrema, *pextrema])
|
|
116
|
+
for _, _, _, pextrema in keyed_predictions[key]
|
|
117
|
+
for _, _, gextrema in keyed_groundtruths[key]
|
|
118
|
+
]
|
|
119
|
+
)
|
|
120
|
+
else:
|
|
121
|
+
return np.array(
|
|
122
|
+
[
|
|
123
|
+
np.array([groundtruth_obj, prediction_obj])
|
|
124
|
+
for _, _, _, prediction_obj in keyed_predictions[key]
|
|
125
|
+
for _, _, groundtruth_obj in keyed_groundtruths[key]
|
|
126
|
+
]
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def compute_iou(
|
|
131
|
+
data: NDArray[np.floating],
|
|
132
|
+
annotation_type: type[BoundingBox] | type[Polygon] | type[Bitmask],
|
|
133
|
+
) -> NDArray[np.floating]:
|
|
134
|
+
"""
|
|
135
|
+
Computes intersection-over-union (IoU) calculations for various annotation types.
|
|
136
|
+
|
|
137
|
+
Parameters
|
|
138
|
+
----------
|
|
139
|
+
data : NDArray[np.floating]
|
|
140
|
+
A sorted array of bounding box, bitmask, or polygon pairs.
|
|
141
|
+
annotation_type: type[BoundingBox] | type[Polygon] | type[Bitmask]
|
|
142
|
+
The type of annotation contained in the data.
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
Returns
|
|
146
|
+
-------
|
|
147
|
+
NDArray[np.floating]
|
|
148
|
+
Computed IoU's.
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
if annotation_type == BoundingBox:
|
|
152
|
+
return compute_bbox_iou(data=data)
|
|
153
|
+
elif annotation_type == Bitmask:
|
|
154
|
+
return compute_bitmask_iou(data=data)
|
|
155
|
+
else:
|
|
156
|
+
return compute_polygon_iou(data=data)
|
|
157
|
+
|
|
158
|
+
|
|
54
159
|
@dataclass
|
|
55
160
|
class Filter:
|
|
56
161
|
indices: NDArray[np.int32]
|
|
@@ -74,6 +179,10 @@ class Evaluator:
|
|
|
74
179
|
self.uid_to_index: dict[str, int] = dict()
|
|
75
180
|
self.index_to_uid: dict[int, str] = dict()
|
|
76
181
|
|
|
182
|
+
# annotation reference
|
|
183
|
+
self.groundtruth_examples: dict[int, NDArray[np.float16]] = dict()
|
|
184
|
+
self.prediction_examples: dict[int, NDArray[np.float16]] = dict()
|
|
185
|
+
|
|
77
186
|
# label reference
|
|
78
187
|
self.label_to_index: dict[tuple[str, str], int] = dict()
|
|
79
188
|
self.index_to_label: dict[int, tuple[str, str]] = dict()
|
|
@@ -84,10 +193,10 @@ class Evaluator:
|
|
|
84
193
|
self.label_index_to_label_key_index: dict[int, int] = dict()
|
|
85
194
|
|
|
86
195
|
# computation caches
|
|
87
|
-
self._detailed_pairs = np.array([])
|
|
88
|
-
self._ranked_pairs = np.array([])
|
|
89
|
-
self._label_metadata = np.array([])
|
|
90
|
-
self._label_metadata_per_datum = np.array([])
|
|
196
|
+
self._detailed_pairs: NDArray[np.floating] = np.array([])
|
|
197
|
+
self._ranked_pairs: NDArray[np.floating] = np.array([])
|
|
198
|
+
self._label_metadata: NDArray[np.int32] = np.array([])
|
|
199
|
+
self._label_metadata_per_datum: NDArray[np.int32] = np.array([])
|
|
91
200
|
|
|
92
201
|
@property
|
|
93
202
|
def ignored_prediction_labels(self) -> list[tuple[str, str]]:
|
|
@@ -163,46 +272,35 @@ class Evaluator:
|
|
|
163
272
|
[self.uid_to_index[uid] for uid in datum_uids],
|
|
164
273
|
dtype=np.int32,
|
|
165
274
|
)
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
] =
|
|
170
|
-
mask_pairs &= mask
|
|
171
|
-
|
|
172
|
-
mask = np.zeros_like(mask_datums, dtype=np.bool_)
|
|
173
|
-
mask[datum_uids] = True
|
|
174
|
-
mask_datums &= mask
|
|
275
|
+
mask_pairs[
|
|
276
|
+
~np.isin(self._ranked_pairs[:, 0].astype(int), datum_uids)
|
|
277
|
+
] = False
|
|
278
|
+
mask_datums[~np.isin(np.arange(n_datums), datum_uids)] = False
|
|
175
279
|
|
|
176
280
|
if labels is not None:
|
|
177
281
|
if isinstance(labels, list):
|
|
178
282
|
labels = np.array(
|
|
179
283
|
[self.label_to_index[label] for label in labels]
|
|
180
284
|
)
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
mask = np.zeros_like(mask_labels, dtype=np.bool_)
|
|
186
|
-
mask[labels] = True
|
|
187
|
-
mask_labels &= mask
|
|
285
|
+
mask_pairs[
|
|
286
|
+
~np.isin(self._ranked_pairs[:, 4].astype(int), labels)
|
|
287
|
+
] = False
|
|
288
|
+
mask_labels[~np.isin(np.arange(n_labels), labels)] = False
|
|
188
289
|
|
|
189
290
|
if label_keys is not None:
|
|
190
291
|
if isinstance(label_keys, list):
|
|
191
292
|
label_keys = np.array(
|
|
192
293
|
[self.label_key_to_index[key] for key in label_keys]
|
|
193
294
|
)
|
|
194
|
-
label_indices =
|
|
195
|
-
np.isclose(self._label_metadata[:, 2], label_keys)
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
mask = np.zeros_like(mask_labels, dtype=np.bool_)
|
|
204
|
-
mask[label_indices] = True
|
|
205
|
-
mask_labels &= mask
|
|
295
|
+
label_indices = (
|
|
296
|
+
np.where(np.isclose(self._label_metadata[:, 2], label_keys))[0]
|
|
297
|
+
if label_keys.size > 0
|
|
298
|
+
else np.array([])
|
|
299
|
+
)
|
|
300
|
+
mask_pairs[
|
|
301
|
+
~np.isin(self._ranked_pairs[:, 4].astype(int), label_indices)
|
|
302
|
+
] = False
|
|
303
|
+
mask_labels[~np.isin(np.arange(n_labels), label_indices)] = False
|
|
206
304
|
|
|
207
305
|
mask = mask_datums[:, np.newaxis] & mask_labels[np.newaxis, :]
|
|
208
306
|
label_metadata_per_datum = self._label_metadata_per_datum.copy()
|
|
@@ -224,8 +322,10 @@ class Evaluator:
|
|
|
224
322
|
|
|
225
323
|
def evaluate(
|
|
226
324
|
self,
|
|
325
|
+
metrics_to_return: list[MetricType] = MetricType.base_metrics(),
|
|
227
326
|
iou_thresholds: list[float] = [0.5, 0.75, 0.9],
|
|
228
327
|
score_thresholds: list[float] = [0.5],
|
|
328
|
+
number_of_examples: int = 0,
|
|
229
329
|
filter_: Filter | None = None,
|
|
230
330
|
) -> dict[MetricType, list]:
|
|
231
331
|
"""
|
|
@@ -233,10 +333,14 @@ class Evaluator:
|
|
|
233
333
|
|
|
234
334
|
Parameters
|
|
235
335
|
----------
|
|
336
|
+
metrics_to_return : list[MetricType]
|
|
337
|
+
A list of metrics to return in the results.
|
|
236
338
|
iou_thresholds : list[float]
|
|
237
339
|
A list of IoU thresholds to compute metrics over.
|
|
238
340
|
score_thresholds : list[float]
|
|
239
341
|
A list of score thresholds to compute metrics over.
|
|
342
|
+
number_of_examples : int, default=0
|
|
343
|
+
Number of annotation examples to return in DetailedCounts.
|
|
240
344
|
filter_ : Filter, optional
|
|
241
345
|
An optional filter object.
|
|
242
346
|
|
|
@@ -284,7 +388,7 @@ class Evaluator:
|
|
|
284
388
|
)
|
|
285
389
|
for iou_idx in range(average_precision.shape[0])
|
|
286
390
|
for label_idx in range(average_precision.shape[1])
|
|
287
|
-
if int(label_metadata[label_idx
|
|
391
|
+
if int(label_metadata[label_idx, 0]) > 0
|
|
288
392
|
]
|
|
289
393
|
|
|
290
394
|
metrics[MetricType.mAP] = [
|
|
@@ -304,7 +408,7 @@ class Evaluator:
|
|
|
304
408
|
label=self.index_to_label[label_idx],
|
|
305
409
|
)
|
|
306
410
|
for label_idx in range(self.n_labels)
|
|
307
|
-
if int(label_metadata[label_idx
|
|
411
|
+
if int(label_metadata[label_idx, 0]) > 0
|
|
308
412
|
]
|
|
309
413
|
|
|
310
414
|
metrics[MetricType.mAPAveragedOverIOUs] = [
|
|
@@ -327,7 +431,7 @@ class Evaluator:
|
|
|
327
431
|
)
|
|
328
432
|
for score_idx in range(average_recall.shape[0])
|
|
329
433
|
for label_idx in range(average_recall.shape[1])
|
|
330
|
-
if int(label_metadata[label_idx
|
|
434
|
+
if int(label_metadata[label_idx, 0]) > 0
|
|
331
435
|
]
|
|
332
436
|
|
|
333
437
|
metrics[MetricType.mAR] = [
|
|
@@ -349,7 +453,7 @@ class Evaluator:
|
|
|
349
453
|
label=self.index_to_label[label_idx],
|
|
350
454
|
)
|
|
351
455
|
for label_idx in range(self.n_labels)
|
|
352
|
-
if int(label_metadata[label_idx
|
|
456
|
+
if int(label_metadata[label_idx, 0]) > 0
|
|
353
457
|
]
|
|
354
458
|
|
|
355
459
|
metrics[MetricType.mARAveragedOverScores] = [
|
|
@@ -372,16 +476,17 @@ class Evaluator:
|
|
|
372
476
|
)
|
|
373
477
|
for iou_idx, iou_threshold in enumerate(iou_thresholds)
|
|
374
478
|
for label_idx, label in self.index_to_label.items()
|
|
375
|
-
if int(label_metadata[label_idx
|
|
479
|
+
if int(label_metadata[label_idx, 0]) > 0
|
|
376
480
|
]
|
|
377
481
|
|
|
378
482
|
for label_idx, label in self.index_to_label.items():
|
|
483
|
+
|
|
484
|
+
if label_metadata[label_idx, 0] == 0:
|
|
485
|
+
continue
|
|
486
|
+
|
|
379
487
|
for score_idx, score_threshold in enumerate(score_thresholds):
|
|
380
488
|
for iou_idx, iou_threshold in enumerate(iou_thresholds):
|
|
381
489
|
|
|
382
|
-
if label_metadata[label_idx, 0] == 0:
|
|
383
|
-
continue
|
|
384
|
-
|
|
385
490
|
row = precision_recall[iou_idx][score_idx][label_idx]
|
|
386
491
|
kwargs = {
|
|
387
492
|
"label": label,
|
|
@@ -422,16 +527,27 @@ class Evaluator:
|
|
|
422
527
|
)
|
|
423
528
|
)
|
|
424
529
|
|
|
530
|
+
if MetricType.DetailedCounts in metrics_to_return:
|
|
531
|
+
metrics[MetricType.DetailedCounts] = self._compute_detailed_counts(
|
|
532
|
+
iou_thresholds=iou_thresholds,
|
|
533
|
+
score_thresholds=score_thresholds,
|
|
534
|
+
n_samples=number_of_examples,
|
|
535
|
+
)
|
|
536
|
+
|
|
537
|
+
for metric in set(metrics.keys()):
|
|
538
|
+
if metric not in metrics_to_return:
|
|
539
|
+
del metrics[metric]
|
|
540
|
+
|
|
425
541
|
return metrics
|
|
426
542
|
|
|
427
|
-
def
|
|
543
|
+
def _compute_detailed_counts(
|
|
428
544
|
self,
|
|
429
545
|
iou_thresholds: list[float] = [0.5],
|
|
430
546
|
score_thresholds: list[float] = [
|
|
431
547
|
score / 10.0 for score in range(1, 11)
|
|
432
548
|
],
|
|
433
549
|
n_samples: int = 0,
|
|
434
|
-
) -> list[
|
|
550
|
+
) -> list[DetailedCounts]:
|
|
435
551
|
"""
|
|
436
552
|
Computes detailed counting metrics.
|
|
437
553
|
|
|
@@ -454,7 +570,7 @@ class Evaluator:
|
|
|
454
570
|
return list()
|
|
455
571
|
|
|
456
572
|
metrics = compute_detailed_counts(
|
|
457
|
-
self._detailed_pairs,
|
|
573
|
+
data=self._detailed_pairs,
|
|
458
574
|
label_metadata=self._label_metadata,
|
|
459
575
|
iou_thresholds=np.array(iou_thresholds),
|
|
460
576
|
score_thresholds=np.array(score_thresholds),
|
|
@@ -462,95 +578,111 @@ class Evaluator:
|
|
|
462
578
|
)
|
|
463
579
|
|
|
464
580
|
tp_idx = 0
|
|
465
|
-
fp_misclf_idx =
|
|
466
|
-
fp_halluc_idx =
|
|
467
|
-
fn_misclf_idx =
|
|
468
|
-
fn_misprd_idx =
|
|
581
|
+
fp_misclf_idx = 2 * n_samples + 1
|
|
582
|
+
fp_halluc_idx = 4 * n_samples + 2
|
|
583
|
+
fn_misclf_idx = 6 * n_samples + 3
|
|
584
|
+
fn_misprd_idx = 8 * n_samples + 4
|
|
585
|
+
|
|
586
|
+
def _unpack_examples(
|
|
587
|
+
iou_idx: int,
|
|
588
|
+
label_idx: int,
|
|
589
|
+
type_idx: int,
|
|
590
|
+
example_source: dict[int, NDArray[np.float16]],
|
|
591
|
+
) -> list[list[tuple[str, tuple[float, float, float, float]]]]:
|
|
592
|
+
"""
|
|
593
|
+
Unpacks metric examples from computation.
|
|
594
|
+
"""
|
|
595
|
+
type_idx += 1
|
|
596
|
+
|
|
597
|
+
results = list()
|
|
598
|
+
for score_idx in range(n_scores):
|
|
599
|
+
examples = list()
|
|
600
|
+
for example_idx in range(n_samples):
|
|
601
|
+
datum_idx = metrics[
|
|
602
|
+
iou_idx,
|
|
603
|
+
score_idx,
|
|
604
|
+
label_idx,
|
|
605
|
+
type_idx + example_idx * 2,
|
|
606
|
+
]
|
|
607
|
+
annotation_idx = metrics[
|
|
608
|
+
iou_idx,
|
|
609
|
+
score_idx,
|
|
610
|
+
label_idx,
|
|
611
|
+
type_idx + example_idx * 2 + 1,
|
|
612
|
+
]
|
|
613
|
+
if datum_idx >= 0:
|
|
614
|
+
examples.append(
|
|
615
|
+
(
|
|
616
|
+
self.index_to_uid[datum_idx],
|
|
617
|
+
tuple(
|
|
618
|
+
example_source[datum_idx][
|
|
619
|
+
annotation_idx
|
|
620
|
+
].tolist()
|
|
621
|
+
),
|
|
622
|
+
)
|
|
623
|
+
)
|
|
624
|
+
results.append(examples)
|
|
625
|
+
|
|
626
|
+
return results
|
|
469
627
|
|
|
470
628
|
n_ious, n_scores, n_labels, _ = metrics.shape
|
|
471
629
|
return [
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
.
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
[
|
|
527
|
-
self.index_to_uid[int(datum_idx)]
|
|
528
|
-
for datum_idx in metrics[iou_idx][score_idx][
|
|
529
|
-
label_idx
|
|
530
|
-
][fn_misclf_idx + 1 : fn_misprd_idx]
|
|
531
|
-
if int(datum_idx) >= 0
|
|
532
|
-
]
|
|
533
|
-
for score_idx in range(n_scores)
|
|
534
|
-
],
|
|
535
|
-
fn_missing_prediction=metrics[
|
|
536
|
-
iou_idx, :, label_idx, fn_misprd_idx
|
|
537
|
-
]
|
|
538
|
-
.astype(int)
|
|
539
|
-
.tolist(),
|
|
540
|
-
fn_missing_prediction_examples=[
|
|
541
|
-
[
|
|
542
|
-
self.index_to_uid[int(datum_idx)]
|
|
543
|
-
for datum_idx in metrics[iou_idx][score_idx][
|
|
544
|
-
label_idx
|
|
545
|
-
][fn_misprd_idx + 1 :]
|
|
546
|
-
if int(datum_idx) >= 0
|
|
547
|
-
]
|
|
548
|
-
for score_idx in range(n_scores)
|
|
549
|
-
],
|
|
550
|
-
)
|
|
551
|
-
for iou_idx in range(n_ious)
|
|
552
|
-
]
|
|
630
|
+
DetailedCounts(
|
|
631
|
+
iou_threshold=iou_thresholds[iou_idx],
|
|
632
|
+
label=self.index_to_label[label_idx],
|
|
633
|
+
score_thresholds=score_thresholds,
|
|
634
|
+
tp=metrics[iou_idx, :, label_idx, tp_idx].astype(int).tolist(),
|
|
635
|
+
fp_misclassification=metrics[
|
|
636
|
+
iou_idx, :, label_idx, fp_misclf_idx
|
|
637
|
+
]
|
|
638
|
+
.astype(int)
|
|
639
|
+
.tolist(),
|
|
640
|
+
fp_hallucination=metrics[iou_idx, :, label_idx, fp_halluc_idx]
|
|
641
|
+
.astype(int)
|
|
642
|
+
.tolist(),
|
|
643
|
+
fn_misclassification=metrics[
|
|
644
|
+
iou_idx, :, label_idx, fn_misclf_idx
|
|
645
|
+
]
|
|
646
|
+
.astype(int)
|
|
647
|
+
.tolist(),
|
|
648
|
+
fn_missing_prediction=metrics[
|
|
649
|
+
iou_idx, :, label_idx, fn_misprd_idx
|
|
650
|
+
]
|
|
651
|
+
.astype(int)
|
|
652
|
+
.tolist(),
|
|
653
|
+
tp_examples=_unpack_examples(
|
|
654
|
+
iou_idx=iou_idx,
|
|
655
|
+
label_idx=label_idx,
|
|
656
|
+
type_idx=tp_idx,
|
|
657
|
+
example_source=self.prediction_examples,
|
|
658
|
+
),
|
|
659
|
+
fp_misclassification_examples=_unpack_examples(
|
|
660
|
+
iou_idx=iou_idx,
|
|
661
|
+
label_idx=label_idx,
|
|
662
|
+
type_idx=fp_misclf_idx,
|
|
663
|
+
example_source=self.prediction_examples,
|
|
664
|
+
),
|
|
665
|
+
fp_hallucination_examples=_unpack_examples(
|
|
666
|
+
iou_idx=iou_idx,
|
|
667
|
+
label_idx=label_idx,
|
|
668
|
+
type_idx=fp_halluc_idx,
|
|
669
|
+
example_source=self.prediction_examples,
|
|
670
|
+
),
|
|
671
|
+
fn_misclassification_examples=_unpack_examples(
|
|
672
|
+
iou_idx=iou_idx,
|
|
673
|
+
label_idx=label_idx,
|
|
674
|
+
type_idx=fn_misclf_idx,
|
|
675
|
+
example_source=self.groundtruth_examples,
|
|
676
|
+
),
|
|
677
|
+
fn_missing_prediction_examples=_unpack_examples(
|
|
678
|
+
iou_idx=iou_idx,
|
|
679
|
+
label_idx=label_idx,
|
|
680
|
+
type_idx=fn_misprd_idx,
|
|
681
|
+
example_source=self.groundtruth_examples,
|
|
682
|
+
),
|
|
683
|
+
)
|
|
553
684
|
for label_idx in range(n_labels)
|
|
685
|
+
for iou_idx in range(n_ious)
|
|
554
686
|
]
|
|
555
687
|
|
|
556
688
|
|
|
@@ -561,7 +693,7 @@ class DataLoader:
|
|
|
561
693
|
|
|
562
694
|
def __init__(self):
|
|
563
695
|
self._evaluator = Evaluator()
|
|
564
|
-
self.pairs = list()
|
|
696
|
+
self.pairs: list[NDArray[np.floating]] = list()
|
|
565
697
|
self.groundtruth_count = defaultdict(lambda: defaultdict(int))
|
|
566
698
|
self.prediction_count = defaultdict(lambda: defaultdict(int))
|
|
567
699
|
|
|
@@ -624,9 +756,143 @@ class DataLoader:
|
|
|
624
756
|
self._evaluator.label_key_to_index[label[0]],
|
|
625
757
|
)
|
|
626
758
|
|
|
627
|
-
def
|
|
759
|
+
def _compute_ious_and_cache_pairs(
|
|
760
|
+
self,
|
|
761
|
+
uid_index: int,
|
|
762
|
+
keyed_groundtruths: dict,
|
|
763
|
+
keyed_predictions: dict,
|
|
764
|
+
annotation_type: type[BoundingBox] | type[Polygon] | type[Bitmask],
|
|
765
|
+
) -> None:
|
|
766
|
+
"""
|
|
767
|
+
Compute IOUs between groundtruths and preditions before storing as pairs.
|
|
768
|
+
|
|
769
|
+
Parameters
|
|
770
|
+
----------
|
|
771
|
+
uid_index: int
|
|
772
|
+
The index of the detection.
|
|
773
|
+
keyed_groundtruths: dict
|
|
774
|
+
A dictionary of groundtruths.
|
|
775
|
+
keyed_predictions: dict
|
|
776
|
+
A dictionary of predictions.
|
|
777
|
+
annotation_type: type[BoundingBox] | type[Polygon] | type[Bitmask]
|
|
778
|
+
The type of annotation to compute IOUs for.
|
|
779
|
+
"""
|
|
780
|
+
gt_keys = set(keyed_groundtruths.keys())
|
|
781
|
+
pd_keys = set(keyed_predictions.keys())
|
|
782
|
+
joint_keys = gt_keys.intersection(pd_keys)
|
|
783
|
+
gt_unique_keys = gt_keys - pd_keys
|
|
784
|
+
pd_unique_keys = pd_keys - gt_keys
|
|
785
|
+
|
|
786
|
+
pairs = list()
|
|
787
|
+
for key in joint_keys:
|
|
788
|
+
n_predictions = len(keyed_predictions[key])
|
|
789
|
+
n_groundtruths = len(keyed_groundtruths[key])
|
|
790
|
+
data = _get_annotation_data(
|
|
791
|
+
keyed_groundtruths=keyed_groundtruths,
|
|
792
|
+
keyed_predictions=keyed_predictions,
|
|
793
|
+
key=key,
|
|
794
|
+
annotation_type=annotation_type,
|
|
795
|
+
)
|
|
796
|
+
ious = compute_iou(data=data, annotation_type=annotation_type)
|
|
797
|
+
mask_nonzero_iou = (ious > 1e-9).reshape(
|
|
798
|
+
(n_predictions, n_groundtruths)
|
|
799
|
+
)
|
|
800
|
+
mask_ious_halluc = ~(mask_nonzero_iou.any(axis=1))
|
|
801
|
+
mask_ious_misprd = ~(mask_nonzero_iou.any(axis=0))
|
|
802
|
+
|
|
803
|
+
pairs.extend(
|
|
804
|
+
[
|
|
805
|
+
np.array(
|
|
806
|
+
[
|
|
807
|
+
float(uid_index),
|
|
808
|
+
float(gidx),
|
|
809
|
+
float(pidx),
|
|
810
|
+
ious[pidx * len(keyed_groundtruths[key]) + gidx],
|
|
811
|
+
float(glabel),
|
|
812
|
+
float(plabel),
|
|
813
|
+
float(score),
|
|
814
|
+
]
|
|
815
|
+
)
|
|
816
|
+
for pidx, plabel, score, _ in keyed_predictions[key]
|
|
817
|
+
for gidx, glabel, _ in keyed_groundtruths[key]
|
|
818
|
+
if ious[pidx * len(keyed_groundtruths[key]) + gidx] > 1e-9
|
|
819
|
+
]
|
|
820
|
+
)
|
|
821
|
+
pairs.extend(
|
|
822
|
+
[
|
|
823
|
+
np.array(
|
|
824
|
+
[
|
|
825
|
+
float(uid_index),
|
|
826
|
+
-1.0,
|
|
827
|
+
float(pidx),
|
|
828
|
+
0.0,
|
|
829
|
+
-1.0,
|
|
830
|
+
float(plabel),
|
|
831
|
+
float(score),
|
|
832
|
+
]
|
|
833
|
+
)
|
|
834
|
+
for pidx, plabel, score, _ in keyed_predictions[key]
|
|
835
|
+
if mask_ious_halluc[pidx]
|
|
836
|
+
]
|
|
837
|
+
)
|
|
838
|
+
pairs.extend(
|
|
839
|
+
[
|
|
840
|
+
np.array(
|
|
841
|
+
[
|
|
842
|
+
float(uid_index),
|
|
843
|
+
float(gidx),
|
|
844
|
+
-1.0,
|
|
845
|
+
0.0,
|
|
846
|
+
float(glabel),
|
|
847
|
+
-1.0,
|
|
848
|
+
-1.0,
|
|
849
|
+
]
|
|
850
|
+
)
|
|
851
|
+
for gidx, glabel, _ in keyed_groundtruths[key]
|
|
852
|
+
if mask_ious_misprd[gidx]
|
|
853
|
+
]
|
|
854
|
+
)
|
|
855
|
+
for key in gt_unique_keys:
|
|
856
|
+
pairs.extend(
|
|
857
|
+
[
|
|
858
|
+
np.array(
|
|
859
|
+
[
|
|
860
|
+
float(uid_index),
|
|
861
|
+
float(gidx),
|
|
862
|
+
-1.0,
|
|
863
|
+
0.0,
|
|
864
|
+
float(glabel),
|
|
865
|
+
-1.0,
|
|
866
|
+
-1.0,
|
|
867
|
+
]
|
|
868
|
+
)
|
|
869
|
+
for gidx, glabel, _ in keyed_groundtruths[key]
|
|
870
|
+
]
|
|
871
|
+
)
|
|
872
|
+
for key in pd_unique_keys:
|
|
873
|
+
pairs.extend(
|
|
874
|
+
[
|
|
875
|
+
np.array(
|
|
876
|
+
[
|
|
877
|
+
float(uid_index),
|
|
878
|
+
-1.0,
|
|
879
|
+
float(pidx),
|
|
880
|
+
0.0,
|
|
881
|
+
-1.0,
|
|
882
|
+
float(plabel),
|
|
883
|
+
float(score),
|
|
884
|
+
]
|
|
885
|
+
)
|
|
886
|
+
for pidx, plabel, score, _ in keyed_predictions[key]
|
|
887
|
+
]
|
|
888
|
+
)
|
|
889
|
+
|
|
890
|
+
self.pairs.append(np.array(pairs))
|
|
891
|
+
|
|
892
|
+
def _add_data(
|
|
628
893
|
self,
|
|
629
894
|
detections: list[Detection],
|
|
895
|
+
annotation_type: type[Bitmask] | type[BoundingBox] | type[Polygon],
|
|
630
896
|
show_progress: bool = False,
|
|
631
897
|
):
|
|
632
898
|
"""
|
|
@@ -636,6 +902,8 @@ class DataLoader:
|
|
|
636
902
|
----------
|
|
637
903
|
detections : list[Detection]
|
|
638
904
|
A list of Detection objects.
|
|
905
|
+
annotation_type : type[Bitmask] | type[BoundingBox] | type[Polygon]
|
|
906
|
+
The annotation type to process.
|
|
639
907
|
show_progress : bool, default=False
|
|
640
908
|
Toggle for tqdm progress bar.
|
|
641
909
|
"""
|
|
@@ -650,108 +918,157 @@ class DataLoader:
|
|
|
650
918
|
# update datum uid index
|
|
651
919
|
uid_index = self._add_datum(uid=detection.uid)
|
|
652
920
|
|
|
921
|
+
# initialize bounding box examples
|
|
922
|
+
self._evaluator.groundtruth_examples[uid_index] = np.zeros(
|
|
923
|
+
(len(detection.groundtruths), 4), dtype=np.float16
|
|
924
|
+
)
|
|
925
|
+
self._evaluator.prediction_examples[uid_index] = np.zeros(
|
|
926
|
+
(len(detection.predictions), 4), dtype=np.float16
|
|
927
|
+
)
|
|
928
|
+
|
|
653
929
|
# cache labels and annotations
|
|
654
930
|
keyed_groundtruths = defaultdict(list)
|
|
655
931
|
keyed_predictions = defaultdict(list)
|
|
932
|
+
|
|
933
|
+
representation_property = _get_annotation_representation(
|
|
934
|
+
annotation_type=annotation_type
|
|
935
|
+
)
|
|
936
|
+
|
|
656
937
|
for gidx, gann in enumerate(detection.groundtruths):
|
|
938
|
+
if not isinstance(gann, annotation_type):
|
|
939
|
+
raise ValueError(
|
|
940
|
+
f"Expected {annotation_type}, but annotation is of type {type(gann)}."
|
|
941
|
+
)
|
|
942
|
+
|
|
943
|
+
if isinstance(gann, BoundingBox):
|
|
944
|
+
self._evaluator.groundtruth_examples[uid_index][
|
|
945
|
+
gidx
|
|
946
|
+
] = getattr(gann, representation_property)
|
|
947
|
+
else:
|
|
948
|
+
converted_box = gann.to_box()
|
|
949
|
+
self._evaluator.groundtruth_examples[uid_index][gidx] = (
|
|
950
|
+
getattr(converted_box, "extrema")
|
|
951
|
+
if converted_box is not None
|
|
952
|
+
else None
|
|
953
|
+
)
|
|
657
954
|
for glabel in gann.labels:
|
|
658
955
|
label_idx, label_key_idx = self._add_label(glabel)
|
|
659
956
|
self.groundtruth_count[label_idx][uid_index] += 1
|
|
957
|
+
representation = getattr(gann, representation_property)
|
|
660
958
|
keyed_groundtruths[label_key_idx].append(
|
|
661
959
|
(
|
|
662
960
|
gidx,
|
|
663
961
|
label_idx,
|
|
664
|
-
|
|
962
|
+
representation,
|
|
665
963
|
)
|
|
666
964
|
)
|
|
965
|
+
|
|
667
966
|
for pidx, pann in enumerate(detection.predictions):
|
|
967
|
+
if not isinstance(pann, annotation_type):
|
|
968
|
+
raise ValueError(
|
|
969
|
+
f"Expected {annotation_type}, but annotation is of type {type(pann)}."
|
|
970
|
+
)
|
|
971
|
+
|
|
972
|
+
if isinstance(pann, BoundingBox):
|
|
973
|
+
self._evaluator.prediction_examples[uid_index][
|
|
974
|
+
pidx
|
|
975
|
+
] = getattr(pann, representation_property)
|
|
976
|
+
else:
|
|
977
|
+
converted_box = pann.to_box()
|
|
978
|
+
self._evaluator.prediction_examples[uid_index][pidx] = (
|
|
979
|
+
getattr(converted_box, "extrema")
|
|
980
|
+
if converted_box is not None
|
|
981
|
+
else None
|
|
982
|
+
)
|
|
668
983
|
for plabel, pscore in zip(pann.labels, pann.scores):
|
|
669
984
|
label_idx, label_key_idx = self._add_label(plabel)
|
|
670
985
|
self.prediction_count[label_idx][uid_index] += 1
|
|
986
|
+
representation = representation = getattr(
|
|
987
|
+
pann, representation_property
|
|
988
|
+
)
|
|
671
989
|
keyed_predictions[label_key_idx].append(
|
|
672
990
|
(
|
|
673
991
|
pidx,
|
|
674
992
|
label_idx,
|
|
675
993
|
pscore,
|
|
676
|
-
|
|
994
|
+
representation,
|
|
677
995
|
)
|
|
678
996
|
)
|
|
679
997
|
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
[
|
|
737
|
-
float(uid_index),
|
|
738
|
-
-1.0,
|
|
739
|
-
float(pidx),
|
|
740
|
-
0.0,
|
|
741
|
-
-1.0,
|
|
742
|
-
float(plabel),
|
|
743
|
-
float(score),
|
|
744
|
-
]
|
|
745
|
-
)
|
|
746
|
-
for pidx, plabel, score, _ in keyed_predictions[key]
|
|
747
|
-
]
|
|
748
|
-
)
|
|
998
|
+
self._compute_ious_and_cache_pairs(
|
|
999
|
+
uid_index=uid_index,
|
|
1000
|
+
keyed_groundtruths=keyed_groundtruths,
|
|
1001
|
+
keyed_predictions=keyed_predictions,
|
|
1002
|
+
annotation_type=annotation_type,
|
|
1003
|
+
)
|
|
1004
|
+
|
|
1005
|
+
def add_bounding_boxes(
|
|
1006
|
+
self,
|
|
1007
|
+
detections: list[Detection],
|
|
1008
|
+
show_progress: bool = False,
|
|
1009
|
+
):
|
|
1010
|
+
"""
|
|
1011
|
+
Adds bounding box detections to the cache.
|
|
1012
|
+
|
|
1013
|
+
Parameters
|
|
1014
|
+
----------
|
|
1015
|
+
detections : list[Detection]
|
|
1016
|
+
A list of Detection objects.
|
|
1017
|
+
show_progress : bool, default=False
|
|
1018
|
+
Toggle for tqdm progress bar.
|
|
1019
|
+
"""
|
|
1020
|
+
return self._add_data(
|
|
1021
|
+
detections=detections,
|
|
1022
|
+
show_progress=show_progress,
|
|
1023
|
+
annotation_type=BoundingBox,
|
|
1024
|
+
)
|
|
1025
|
+
|
|
1026
|
+
def add_polygons(
|
|
1027
|
+
self,
|
|
1028
|
+
detections: list[Detection],
|
|
1029
|
+
show_progress: bool = False,
|
|
1030
|
+
):
|
|
1031
|
+
"""
|
|
1032
|
+
Adds polygon detections to the cache.
|
|
1033
|
+
|
|
1034
|
+
Parameters
|
|
1035
|
+
----------
|
|
1036
|
+
detections : list[Detection]
|
|
1037
|
+
A list of Detection objects.
|
|
1038
|
+
show_progress : bool, default=False
|
|
1039
|
+
Toggle for tqdm progress bar.
|
|
1040
|
+
"""
|
|
1041
|
+
return self._add_data(
|
|
1042
|
+
detections=detections,
|
|
1043
|
+
show_progress=show_progress,
|
|
1044
|
+
annotation_type=Polygon,
|
|
1045
|
+
)
|
|
1046
|
+
|
|
1047
|
+
def add_bitmasks(
|
|
1048
|
+
self,
|
|
1049
|
+
detections: list[Detection],
|
|
1050
|
+
show_progress: bool = False,
|
|
1051
|
+
):
|
|
1052
|
+
"""
|
|
1053
|
+
Adds bitmask detections to the cache.
|
|
749
1054
|
|
|
750
|
-
|
|
1055
|
+
Parameters
|
|
1056
|
+
----------
|
|
1057
|
+
detections : list[Detection]
|
|
1058
|
+
A list of Detection objects.
|
|
1059
|
+
show_progress : bool, default=False
|
|
1060
|
+
Toggle for tqdm progress bar.
|
|
1061
|
+
"""
|
|
1062
|
+
return self._add_data(
|
|
1063
|
+
detections=detections,
|
|
1064
|
+
show_progress=show_progress,
|
|
1065
|
+
annotation_type=Bitmask,
|
|
1066
|
+
)
|
|
751
1067
|
|
|
752
|
-
def
|
|
1068
|
+
def _add_data_from_valor_dict(
|
|
753
1069
|
self,
|
|
754
1070
|
detections: list[tuple[dict, dict]],
|
|
1071
|
+
annotation_type: type[Bitmask] | type[BoundingBox] | type[Polygon],
|
|
755
1072
|
show_progress: bool = False,
|
|
756
1073
|
):
|
|
757
1074
|
"""
|
|
@@ -761,20 +1078,14 @@ class DataLoader:
|
|
|
761
1078
|
----------
|
|
762
1079
|
detections : list[tuple[dict, dict]]
|
|
763
1080
|
A list of groundtruth, prediction pairs in Valor-format dictionaries.
|
|
1081
|
+
annotation_type : type[Bitmask] | type[BoundingBox] | type[Polygon]
|
|
1082
|
+
The annotation type to process.
|
|
764
1083
|
show_progress : bool, default=False
|
|
765
1084
|
Toggle for tqdm progress bar.
|
|
766
1085
|
"""
|
|
767
1086
|
|
|
768
|
-
def _get_bbox_extrema(
|
|
769
|
-
data: list[list[list[float]]],
|
|
770
|
-
) -> tuple[float, float, float, float]:
|
|
771
|
-
x = [point[0] for shape in data for point in shape]
|
|
772
|
-
y = [point[1] for shape in data for point in shape]
|
|
773
|
-
return (min(x), max(x), min(y), max(y))
|
|
774
|
-
|
|
775
1087
|
disable_tqdm = not show_progress
|
|
776
1088
|
for groundtruth, prediction in tqdm(detections, disable=disable_tqdm):
|
|
777
|
-
|
|
778
1089
|
# update metadata
|
|
779
1090
|
self._evaluator.n_datums += 1
|
|
780
1091
|
self._evaluator.n_groundtruths += len(groundtruth["annotations"])
|
|
@@ -783,10 +1094,45 @@ class DataLoader:
|
|
|
783
1094
|
# update datum uid index
|
|
784
1095
|
uid_index = self._add_datum(uid=groundtruth["datum"]["uid"])
|
|
785
1096
|
|
|
1097
|
+
# initialize bounding box examples
|
|
1098
|
+
self._evaluator.groundtruth_examples[uid_index] = np.zeros(
|
|
1099
|
+
(len(groundtruth["annotations"]), 4), dtype=np.float16
|
|
1100
|
+
)
|
|
1101
|
+
self._evaluator.prediction_examples[uid_index] = np.zeros(
|
|
1102
|
+
(len(prediction["annotations"]), 4), dtype=np.float16
|
|
1103
|
+
)
|
|
1104
|
+
|
|
786
1105
|
# cache labels and annotations
|
|
787
1106
|
keyed_groundtruths = defaultdict(list)
|
|
788
1107
|
keyed_predictions = defaultdict(list)
|
|
1108
|
+
|
|
1109
|
+
annotation_key = _get_valor_dict_annotation_key(
|
|
1110
|
+
annotation_type=annotation_type
|
|
1111
|
+
)
|
|
1112
|
+
invalid_keys = list(
|
|
1113
|
+
filter(
|
|
1114
|
+
lambda x: x != annotation_key,
|
|
1115
|
+
["bounding_box", "raster", "polygon"],
|
|
1116
|
+
)
|
|
1117
|
+
)
|
|
1118
|
+
|
|
789
1119
|
for gidx, gann in enumerate(groundtruth["annotations"]):
|
|
1120
|
+
if (gann[annotation_key] is None) or any(
|
|
1121
|
+
[gann[k] is not None for k in invalid_keys]
|
|
1122
|
+
):
|
|
1123
|
+
raise ValueError(
|
|
1124
|
+
f"Input JSON doesn't contain {annotation_type} data, or contains data for multiple annotation types."
|
|
1125
|
+
)
|
|
1126
|
+
if annotation_type == BoundingBox:
|
|
1127
|
+
self._evaluator.groundtruth_examples[uid_index][
|
|
1128
|
+
gidx
|
|
1129
|
+
] = np.array(
|
|
1130
|
+
_get_annotation_representation_from_valor_dict(
|
|
1131
|
+
gann[annotation_key],
|
|
1132
|
+
annotation_type=annotation_type,
|
|
1133
|
+
),
|
|
1134
|
+
)
|
|
1135
|
+
|
|
790
1136
|
for valor_label in gann["labels"]:
|
|
791
1137
|
glabel = (valor_label["key"], valor_label["value"])
|
|
792
1138
|
label_idx, label_key_idx = self._add_label(glabel)
|
|
@@ -795,10 +1141,29 @@ class DataLoader:
|
|
|
795
1141
|
(
|
|
796
1142
|
gidx,
|
|
797
1143
|
label_idx,
|
|
798
|
-
|
|
1144
|
+
_get_annotation_representation_from_valor_dict(
|
|
1145
|
+
gann[annotation_key],
|
|
1146
|
+
annotation_type=annotation_type,
|
|
1147
|
+
),
|
|
799
1148
|
)
|
|
800
1149
|
)
|
|
801
1150
|
for pidx, pann in enumerate(prediction["annotations"]):
|
|
1151
|
+
if (pann[annotation_key] is None) or any(
|
|
1152
|
+
[pann[k] is not None for k in invalid_keys]
|
|
1153
|
+
):
|
|
1154
|
+
raise ValueError(
|
|
1155
|
+
f"Input JSON doesn't contain {annotation_type} data, or contains data for multiple annotation types."
|
|
1156
|
+
)
|
|
1157
|
+
|
|
1158
|
+
if annotation_type == BoundingBox:
|
|
1159
|
+
self._evaluator.prediction_examples[uid_index][
|
|
1160
|
+
pidx
|
|
1161
|
+
] = np.array(
|
|
1162
|
+
_get_annotation_representation_from_valor_dict(
|
|
1163
|
+
pann[annotation_key],
|
|
1164
|
+
annotation_type=annotation_type,
|
|
1165
|
+
)
|
|
1166
|
+
)
|
|
802
1167
|
for valor_label in pann["labels"]:
|
|
803
1168
|
plabel = (valor_label["key"], valor_label["value"])
|
|
804
1169
|
pscore = valor_label["score"]
|
|
@@ -809,81 +1174,40 @@ class DataLoader:
|
|
|
809
1174
|
pidx,
|
|
810
1175
|
label_idx,
|
|
811
1176
|
pscore,
|
|
812
|
-
|
|
1177
|
+
_get_annotation_representation_from_valor_dict(
|
|
1178
|
+
pann[annotation_key],
|
|
1179
|
+
annotation_type=annotation_type,
|
|
1180
|
+
),
|
|
813
1181
|
)
|
|
814
1182
|
)
|
|
815
1183
|
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
pairs = list()
|
|
823
|
-
for key in joint_keys:
|
|
824
|
-
boxes = np.array(
|
|
825
|
-
[
|
|
826
|
-
np.array([*gextrema, *pextrema])
|
|
827
|
-
for _, _, _, pextrema in keyed_predictions[key]
|
|
828
|
-
for _, _, gextrema in keyed_groundtruths[key]
|
|
829
|
-
]
|
|
830
|
-
)
|
|
831
|
-
ious = compute_iou(boxes)
|
|
832
|
-
pairs.extend(
|
|
833
|
-
[
|
|
834
|
-
np.array(
|
|
835
|
-
[
|
|
836
|
-
float(uid_index),
|
|
837
|
-
float(gidx),
|
|
838
|
-
float(pidx),
|
|
839
|
-
ious[
|
|
840
|
-
pidx * len(keyed_groundtruths[key]) + gidx
|
|
841
|
-
],
|
|
842
|
-
float(glabel),
|
|
843
|
-
float(plabel),
|
|
844
|
-
float(score),
|
|
845
|
-
]
|
|
846
|
-
)
|
|
847
|
-
for pidx, plabel, score, _ in keyed_predictions[key]
|
|
848
|
-
for gidx, glabel, _ in keyed_groundtruths[key]
|
|
849
|
-
]
|
|
850
|
-
)
|
|
851
|
-
for key in gt_unique_keys:
|
|
852
|
-
pairs.extend(
|
|
853
|
-
[
|
|
854
|
-
np.array(
|
|
855
|
-
[
|
|
856
|
-
float(uid_index),
|
|
857
|
-
float(gidx),
|
|
858
|
-
-1.0,
|
|
859
|
-
0.0,
|
|
860
|
-
float(glabel),
|
|
861
|
-
-1.0,
|
|
862
|
-
-1.0,
|
|
863
|
-
]
|
|
864
|
-
)
|
|
865
|
-
for gidx, glabel, _ in keyed_groundtruths[key]
|
|
866
|
-
]
|
|
867
|
-
)
|
|
868
|
-
for key in pd_unique_keys:
|
|
869
|
-
pairs.extend(
|
|
870
|
-
[
|
|
871
|
-
np.array(
|
|
872
|
-
[
|
|
873
|
-
float(uid_index),
|
|
874
|
-
-1.0,
|
|
875
|
-
float(pidx),
|
|
876
|
-
0.0,
|
|
877
|
-
-1.0,
|
|
878
|
-
float(plabel),
|
|
879
|
-
float(score),
|
|
880
|
-
]
|
|
881
|
-
)
|
|
882
|
-
for pidx, plabel, score, _ in keyed_predictions[key]
|
|
883
|
-
]
|
|
884
|
-
)
|
|
1184
|
+
self._compute_ious_and_cache_pairs(
|
|
1185
|
+
uid_index=uid_index,
|
|
1186
|
+
keyed_groundtruths=keyed_groundtruths,
|
|
1187
|
+
keyed_predictions=keyed_predictions,
|
|
1188
|
+
annotation_type=annotation_type,
|
|
1189
|
+
)
|
|
885
1190
|
|
|
886
|
-
|
|
1191
|
+
def add_bounding_boxes_from_valor_dict(
|
|
1192
|
+
self,
|
|
1193
|
+
detections: list[tuple[dict, dict]],
|
|
1194
|
+
show_progress: bool = False,
|
|
1195
|
+
):
|
|
1196
|
+
"""
|
|
1197
|
+
Adds Valor-format bounding box detections to the cache.
|
|
1198
|
+
|
|
1199
|
+
Parameters
|
|
1200
|
+
----------
|
|
1201
|
+
detections : list[tuple[dict, dict]]
|
|
1202
|
+
A list of groundtruth, prediction pairs in Valor-format dictionaries.
|
|
1203
|
+
show_progress : bool, default=False
|
|
1204
|
+
Toggle for tqdm progress bar.
|
|
1205
|
+
"""
|
|
1206
|
+
return self._add_data_from_valor_dict(
|
|
1207
|
+
detections=detections,
|
|
1208
|
+
show_progress=show_progress,
|
|
1209
|
+
annotation_type=BoundingBox,
|
|
1210
|
+
)
|
|
887
1211
|
|
|
888
1212
|
def finalize(self) -> Evaluator:
|
|
889
1213
|
"""
|