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.
@@ -1,17 +1,18 @@
1
1
  from collections import defaultdict
2
2
  from dataclasses import dataclass
3
+ from typing import Type
3
4
 
4
5
  import numpy as np
6
+ import valor_lite.object_detection.annotation as annotation
5
7
  from numpy.typing import NDArray
6
- from shapely.geometry import Polygon as ShapelyPolygon
7
8
  from tqdm import tqdm
8
- from valor_lite.detection.annotation import (
9
+ from valor_lite.object_detection.annotation import (
9
10
  Bitmask,
10
11
  BoundingBox,
11
12
  Detection,
12
13
  Polygon,
13
14
  )
14
- from valor_lite.detection.computation import (
15
+ from valor_lite.object_detection.computation import (
15
16
  compute_bbox_iou,
16
17
  compute_bitmask_iou,
17
18
  compute_confusion_matrix,
@@ -19,7 +20,7 @@ from valor_lite.detection.computation import (
19
20
  compute_polygon_iou,
20
21
  compute_ranked_pairs,
21
22
  )
22
- from valor_lite.detection.metric import (
23
+ from valor_lite.object_detection.metric import (
23
24
  AP,
24
25
  AR,
25
26
  F1,
@@ -59,103 +60,6 @@ filtered_metrics = evaluator.evaluate(iou_thresholds=[0.5], filter_mask=filter_m
59
60
  """
60
61
 
61
62
 
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
-
159
63
  @dataclass
160
64
  class Filter:
161
65
  ranked_indices: NDArray[np.int32]
@@ -185,22 +89,17 @@ class Evaluator:
185
89
  self.prediction_examples: dict[int, NDArray[np.float16]] = dict()
186
90
 
187
91
  # label reference
188
- self.label_to_index: dict[tuple[str, str], int] = dict()
189
- self.index_to_label: dict[int, tuple[str, str]] = dict()
190
-
191
- # label key reference
192
- self.index_to_label_key: dict[int, str] = dict()
193
- self.label_key_to_index: dict[str, int] = dict()
194
- self.label_index_to_label_key_index: dict[int, int] = dict()
92
+ self.label_to_index: dict[str, int] = dict()
93
+ self.index_to_label: dict[int, str] = dict()
195
94
 
196
95
  # computation caches
197
- self._detailed_pairs: NDArray[np.floating] = np.array([])
198
- self._ranked_pairs: NDArray[np.floating] = np.array([])
96
+ self._detailed_pairs: NDArray[np.float64] = np.array([])
97
+ self._ranked_pairs: NDArray[np.float64] = np.array([])
199
98
  self._label_metadata: NDArray[np.int32] = np.array([])
200
99
  self._label_metadata_per_datum: NDArray[np.int32] = np.array([])
201
100
 
202
101
  @property
203
- def ignored_prediction_labels(self) -> list[tuple[str, str]]:
102
+ def ignored_prediction_labels(self) -> list[str]:
204
103
  """
205
104
  Prediction labels that are not present in the ground truth set.
206
105
  """
@@ -211,7 +110,7 @@ class Evaluator:
211
110
  ]
212
111
 
213
112
  @property
214
- def missing_prediction_labels(self) -> list[tuple[str, str]]:
113
+ def missing_prediction_labels(self) -> list[str]:
215
114
  """
216
115
  Ground truth labels that are not present in the prediction set.
217
116
  """
@@ -238,8 +137,7 @@ class Evaluator:
238
137
  def create_filter(
239
138
  self,
240
139
  datum_uids: list[str] | NDArray[np.int32] | None = None,
241
- labels: list[tuple[str, str]] | NDArray[np.int32] | None = None,
242
- label_keys: list[str] | NDArray[np.int32] | None = None,
140
+ labels: list[str] | NDArray[np.int32] | None = None,
243
141
  ) -> Filter:
244
142
  """
245
143
  Creates a filter that can be passed to an evaluation.
@@ -248,10 +146,8 @@ class Evaluator:
248
146
  ----------
249
147
  datum_uids : list[str] | NDArray[np.int32], optional
250
148
  An optional list of string uids or a numpy array of uid indices.
251
- labels : list[tuple[str, str]] | NDArray[np.int32], optional
149
+ labels : list[str] | NDArray[np.int32], optional
252
150
  An optional list of labels or a numpy array of label indices.
253
- label_keys : list[str] | NDArray[np.int32], optional
254
- An optional list of label keys or a numpy array of label key indices.
255
151
 
256
152
  Returns
257
153
  -------
@@ -296,24 +192,6 @@ class Evaluator:
296
192
  ] = False
297
193
  mask_labels[~np.isin(np.arange(n_labels), labels)] = False
298
194
 
299
- if label_keys is not None:
300
- if isinstance(label_keys, list):
301
- label_keys = np.array(
302
- [self.label_key_to_index[key] for key in label_keys]
303
- )
304
- label_indices = (
305
- np.where(np.isclose(self._label_metadata[:, 2], label_keys))[0]
306
- if label_keys.size > 0
307
- else np.array([])
308
- )
309
- mask_ranked[
310
- ~np.isin(self._ranked_pairs[:, 4].astype(int), label_indices)
311
- ] = False
312
- mask_detailed[
313
- ~np.isin(self._detailed_pairs[:, 4].astype(int), label_indices)
314
- ] = False
315
- mask_labels[~np.isin(np.arange(n_labels), label_indices)] = False
316
-
317
195
  mask_label_metadata = (
318
196
  mask_datums[:, np.newaxis] & mask_labels[np.newaxis, :]
319
197
  )
@@ -321,13 +199,12 @@ class Evaluator:
321
199
  label_metadata_per_datum[:, ~mask_label_metadata] = 0
322
200
 
323
201
  label_metadata = np.zeros_like(self._label_metadata, dtype=np.int32)
324
- label_metadata[:, :2] = np.transpose(
202
+ label_metadata = np.transpose(
325
203
  np.sum(
326
204
  label_metadata_per_datum,
327
205
  axis=1,
328
206
  )
329
207
  )
330
- label_metadata[:, 2] = self._label_metadata[:, 2]
331
208
 
332
209
  return Filter(
333
210
  ranked_indices=np.where(mask_ranked)[0],
@@ -335,245 +212,22 @@ class Evaluator:
335
212
  label_metadata=label_metadata,
336
213
  )
337
214
 
338
- def evaluate(
339
- self,
340
- metrics_to_return: list[MetricType] = MetricType.base_metrics(),
341
- iou_thresholds: list[float] = [0.5, 0.75, 0.9],
342
- score_thresholds: list[float] = [0.5],
343
- number_of_examples: int = 0,
344
- filter_: Filter | None = None,
345
- as_dict: bool = False,
346
- ) -> dict[MetricType, list]:
215
+ def _convert_example_to_dict(
216
+ self, box: NDArray[np.float16]
217
+ ) -> dict[str, float]:
347
218
  """
348
- Performs an evaluation and returns metrics.
349
-
350
- Parameters
351
- ----------
352
- metrics_to_return : list[MetricType]
353
- A list of metrics to return in the results.
354
- iou_thresholds : list[float]
355
- A list of IoU thresholds to compute metrics over.
356
- score_thresholds : list[float]
357
- A list of score thresholds to compute metrics over.
358
- number_of_examples : int, default=0
359
- Maximum number of annotation examples to return in ConfusionMatrix.
360
- filter_ : Filter, optional
361
- An optional filter object.
362
- as_dict : bool, default=False
363
- An option to return metrics as dictionaries.
364
-
365
- Returns
366
- -------
367
- dict[MetricType, list]
368
- A dictionary mapping MetricType enumerations to lists of computed metrics.
219
+ Converts a cached bounding box example to dictionary format.
369
220
  """
370
-
371
- ranked_pairs = self._ranked_pairs
372
- detailed_pairs = self._detailed_pairs
373
- label_metadata = self._label_metadata
374
- if filter_ is not None:
375
- ranked_pairs = ranked_pairs[filter_.ranked_indices]
376
- detailed_pairs = detailed_pairs[filter_.detailed_indices]
377
- label_metadata = filter_.label_metadata
378
-
379
- (
380
- (
381
- average_precision,
382
- mean_average_precision,
383
- average_precision_average_over_ious,
384
- mean_average_precision_average_over_ious,
385
- ),
386
- (
387
- average_recall,
388
- mean_average_recall,
389
- average_recall_averaged_over_scores,
390
- mean_average_recall_averaged_over_scores,
391
- ),
392
- precision_recall,
393
- pr_curves,
394
- ) = compute_metrics(
395
- data=ranked_pairs,
396
- label_metadata=label_metadata,
397
- iou_thresholds=np.array(iou_thresholds),
398
- score_thresholds=np.array(score_thresholds),
399
- )
400
-
401
- metrics = defaultdict(list)
402
-
403
- metrics[MetricType.AP] = [
404
- AP(
405
- value=average_precision[iou_idx][label_idx],
406
- iou_threshold=iou_thresholds[iou_idx],
407
- label=self.index_to_label[label_idx],
408
- )
409
- for iou_idx in range(average_precision.shape[0])
410
- for label_idx in range(average_precision.shape[1])
411
- if int(label_metadata[label_idx, 0]) > 0
412
- ]
413
-
414
- metrics[MetricType.mAP] = [
415
- mAP(
416
- value=mean_average_precision[iou_idx][label_key_idx],
417
- iou_threshold=iou_thresholds[iou_idx],
418
- label_key=self.index_to_label_key[label_key_idx],
419
- )
420
- for iou_idx in range(mean_average_precision.shape[0])
421
- for label_key_idx in range(mean_average_precision.shape[1])
422
- ]
423
-
424
- metrics[MetricType.APAveragedOverIOUs] = [
425
- APAveragedOverIOUs(
426
- value=average_precision_average_over_ious[label_idx],
427
- iou_thresholds=iou_thresholds,
428
- label=self.index_to_label[label_idx],
429
- )
430
- for label_idx in range(self.n_labels)
431
- if int(label_metadata[label_idx, 0]) > 0
432
- ]
433
-
434
- metrics[MetricType.mAPAveragedOverIOUs] = [
435
- mAPAveragedOverIOUs(
436
- value=mean_average_precision_average_over_ious[label_key_idx],
437
- iou_thresholds=iou_thresholds,
438
- label_key=self.index_to_label_key[label_key_idx],
439
- )
440
- for label_key_idx in range(
441
- mean_average_precision_average_over_ious.shape[0]
442
- )
443
- ]
444
-
445
- metrics[MetricType.AR] = [
446
- AR(
447
- value=average_recall[score_idx][label_idx],
448
- iou_thresholds=iou_thresholds,
449
- score_threshold=score_thresholds[score_idx],
450
- label=self.index_to_label[label_idx],
451
- )
452
- for score_idx in range(average_recall.shape[0])
453
- for label_idx in range(average_recall.shape[1])
454
- if int(label_metadata[label_idx, 0]) > 0
455
- ]
456
-
457
- metrics[MetricType.mAR] = [
458
- mAR(
459
- value=mean_average_recall[score_idx][label_key_idx],
460
- iou_thresholds=iou_thresholds,
461
- score_threshold=score_thresholds[score_idx],
462
- label_key=self.index_to_label_key[label_key_idx],
463
- )
464
- for score_idx in range(mean_average_recall.shape[0])
465
- for label_key_idx in range(mean_average_recall.shape[1])
466
- ]
467
-
468
- metrics[MetricType.ARAveragedOverScores] = [
469
- ARAveragedOverScores(
470
- value=average_recall_averaged_over_scores[label_idx],
471
- score_thresholds=score_thresholds,
472
- iou_thresholds=iou_thresholds,
473
- label=self.index_to_label[label_idx],
474
- )
475
- for label_idx in range(self.n_labels)
476
- if int(label_metadata[label_idx, 0]) > 0
477
- ]
478
-
479
- metrics[MetricType.mARAveragedOverScores] = [
480
- mARAveragedOverScores(
481
- value=mean_average_recall_averaged_over_scores[label_key_idx],
482
- score_thresholds=score_thresholds,
483
- iou_thresholds=iou_thresholds,
484
- label_key=self.index_to_label_key[label_key_idx],
485
- )
486
- for label_key_idx in range(
487
- mean_average_recall_averaged_over_scores.shape[0]
488
- )
489
- ]
490
-
491
- metrics[MetricType.PrecisionRecallCurve] = [
492
- PrecisionRecallCurve(
493
- precision=list(pr_curves[iou_idx][label_idx]),
494
- iou_threshold=iou_threshold,
495
- label=label,
496
- )
497
- for iou_idx, iou_threshold in enumerate(iou_thresholds)
498
- for label_idx, label in self.index_to_label.items()
499
- if int(label_metadata[label_idx, 0]) > 0
500
- ]
501
-
502
- for label_idx, label in self.index_to_label.items():
503
-
504
- if label_metadata[label_idx, 0] == 0:
505
- continue
506
-
507
- for score_idx, score_threshold in enumerate(score_thresholds):
508
- for iou_idx, iou_threshold in enumerate(iou_thresholds):
509
-
510
- row = precision_recall[iou_idx][score_idx][label_idx]
511
- kwargs = {
512
- "label": label,
513
- "iou_threshold": iou_threshold,
514
- "score_threshold": score_threshold,
515
- }
516
- metrics[MetricType.Counts].append(
517
- Counts(
518
- tp=int(row[0]),
519
- fp=int(row[1]),
520
- fn=int(row[2]),
521
- **kwargs,
522
- )
523
- )
524
-
525
- metrics[MetricType.Precision].append(
526
- Precision(
527
- value=row[3],
528
- **kwargs,
529
- )
530
- )
531
- metrics[MetricType.Recall].append(
532
- Recall(
533
- value=row[4],
534
- **kwargs,
535
- )
536
- )
537
- metrics[MetricType.F1].append(
538
- F1(
539
- value=row[5],
540
- **kwargs,
541
- )
542
- )
543
- metrics[MetricType.Accuracy].append(
544
- Accuracy(
545
- value=row[6],
546
- **kwargs,
547
- )
548
- )
549
-
550
- if MetricType.ConfusionMatrix in metrics_to_return:
551
- metrics[
552
- MetricType.ConfusionMatrix
553
- ] = self._compute_confusion_matrix(
554
- data=detailed_pairs,
555
- label_metadata=label_metadata,
556
- iou_thresholds=iou_thresholds,
557
- score_thresholds=score_thresholds,
558
- number_of_examples=number_of_examples,
559
- )
560
-
561
- for metric in set(metrics.keys()):
562
- if metric not in metrics_to_return:
563
- del metrics[metric]
564
-
565
- if as_dict:
566
- return {
567
- mtype: [metric.to_dict() for metric in mvalues]
568
- for mtype, mvalues in metrics.items()
569
- }
570
-
571
- return metrics
221
+ return {
222
+ "xmin": float(box[0]),
223
+ "xmax": float(box[1]),
224
+ "ymin": float(box[2]),
225
+ "ymax": float(box[3]),
226
+ }
572
227
 
573
228
  def _unpack_confusion_matrix(
574
229
  self,
575
- confusion_matrix: NDArray[np.floating],
576
- label_key_idx: int,
230
+ confusion_matrix: NDArray[np.float64],
577
231
  number_of_labels: int,
578
232
  number_of_examples: int,
579
233
  ) -> dict[
@@ -586,7 +240,7 @@ class Evaluator:
586
240
  | list[
587
241
  dict[
588
242
  str,
589
- str | float | tuple[float, float, float, float],
243
+ str | dict[str, float] | float,
590
244
  ]
591
245
  ],
592
246
  ],
@@ -629,8 +283,8 @@ class Evaluator:
629
283
  )
630
284
 
631
285
  return {
632
- self.index_to_label[gt_label_idx][1]: {
633
- self.index_to_label[pd_label_idx][1]: {
286
+ self.index_to_label[gt_label_idx]: {
287
+ self.index_to_label[pd_label_idx]: {
634
288
  "count": max(
635
289
  int(confusion_matrix[gt_label_idx, pd_label_idx, 0]),
636
290
  0,
@@ -642,7 +296,7 @@ class Evaluator:
642
296
  gt_label_idx, pd_label_idx, example_idx
643
297
  )
644
298
  ],
645
- "groundtruth": tuple(
299
+ "groundtruth": self._convert_example_to_dict(
646
300
  self.groundtruth_examples[
647
301
  datum_idx(
648
302
  gt_label_idx,
@@ -655,9 +309,9 @@ class Evaluator:
655
309
  pd_label_idx,
656
310
  example_idx,
657
311
  )
658
- ].tolist()
312
+ ]
659
313
  ),
660
- "prediction": tuple(
314
+ "prediction": self._convert_example_to_dict(
661
315
  self.prediction_examples[
662
316
  datum_idx(
663
317
  gt_label_idx,
@@ -670,7 +324,7 @@ class Evaluator:
670
324
  pd_label_idx,
671
325
  example_idx,
672
326
  )
673
- ].tolist()
327
+ ]
674
328
  ),
675
329
  "score": score_idx(
676
330
  gt_label_idx, pd_label_idx, example_idx
@@ -682,30 +336,20 @@ class Evaluator:
682
336
  ],
683
337
  }
684
338
  for pd_label_idx in range(number_of_labels)
685
- if (
686
- self.label_index_to_label_key_index[pd_label_idx]
687
- == label_key_idx
688
- )
689
339
  }
690
340
  for gt_label_idx in range(number_of_labels)
691
- if (
692
- self.label_index_to_label_key_index[gt_label_idx]
693
- == label_key_idx
694
- )
695
341
  }
696
342
 
697
343
  def _unpack_hallucinations(
698
344
  self,
699
- hallucinations: NDArray[np.floating],
700
- label_key_idx: int,
345
+ hallucinations: NDArray[np.float64],
701
346
  number_of_labels: int,
702
347
  number_of_examples: int,
703
348
  ) -> dict[
704
349
  str,
705
350
  dict[
706
351
  str,
707
- int
708
- | list[dict[str, str | float | tuple[float, float, float, float]]],
352
+ int | list[dict[str, str | float | dict[str, float]]],
709
353
  ],
710
354
  ]:
711
355
  """
@@ -740,7 +384,7 @@ class Evaluator:
740
384
  )
741
385
 
742
386
  return {
743
- self.index_to_label[pd_label_idx][1]: {
387
+ self.index_to_label[pd_label_idx]: {
744
388
  "count": max(
745
389
  int(hallucinations[pd_label_idx, 0]),
746
390
  0,
@@ -750,12 +394,10 @@ class Evaluator:
750
394
  "datum": self.index_to_uid[
751
395
  datum_idx(pd_label_idx, example_idx)
752
396
  ],
753
- "prediction": tuple(
397
+ "prediction": self._convert_example_to_dict(
754
398
  self.prediction_examples[
755
399
  datum_idx(pd_label_idx, example_idx)
756
- ][
757
- prediction_idx(pd_label_idx, example_idx)
758
- ].tolist()
400
+ ][prediction_idx(pd_label_idx, example_idx)]
759
401
  ),
760
402
  "score": score_idx(pd_label_idx, example_idx),
761
403
  }
@@ -764,25 +406,14 @@ class Evaluator:
764
406
  ],
765
407
  }
766
408
  for pd_label_idx in range(number_of_labels)
767
- if (
768
- self.label_index_to_label_key_index[pd_label_idx]
769
- == label_key_idx
770
- )
771
409
  }
772
410
 
773
411
  def _unpack_missing_predictions(
774
412
  self,
775
413
  missing_predictions: NDArray[np.int32],
776
- label_key_idx: int,
777
414
  number_of_labels: int,
778
415
  number_of_examples: int,
779
- ) -> dict[
780
- str,
781
- dict[
782
- str,
783
- int | list[dict[str, str | tuple[float, float, float, float]]],
784
- ],
785
- ]:
416
+ ) -> dict[str, dict[str, int | list[dict[str, str | dict[str, float]]]]]:
786
417
  """
787
418
  Unpacks a numpy array of missing prediction counts and examples.
788
419
  """
@@ -806,7 +437,7 @@ class Evaluator:
806
437
  )
807
438
 
808
439
  return {
809
- self.index_to_label[gt_label_idx][1]: {
440
+ self.index_to_label[gt_label_idx]: {
810
441
  "count": max(
811
442
  int(missing_predictions[gt_label_idx, 0]),
812
443
  0,
@@ -816,12 +447,10 @@ class Evaluator:
816
447
  "datum": self.index_to_uid[
817
448
  datum_idx(gt_label_idx, example_idx)
818
449
  ],
819
- "groundtruth": tuple(
450
+ "groundtruth": self._convert_example_to_dict(
820
451
  self.groundtruth_examples[
821
452
  datum_idx(gt_label_idx, example_idx)
822
- ][
823
- groundtruth_idx(gt_label_idx, example_idx)
824
- ].tolist()
453
+ ][groundtruth_idx(gt_label_idx, example_idx)]
825
454
  ),
826
455
  }
827
456
  for example_idx in range(number_of_examples)
@@ -829,51 +458,253 @@ class Evaluator:
829
458
  ],
830
459
  }
831
460
  for gt_label_idx in range(number_of_labels)
832
- if (
833
- self.label_index_to_label_key_index[gt_label_idx]
834
- == label_key_idx
835
- )
836
461
  }
837
462
 
838
- def _compute_confusion_matrix(
463
+ def compute_precision_recall(
839
464
  self,
840
- data: NDArray[np.floating],
841
- label_metadata: NDArray[np.int32],
842
- iou_thresholds: list[float],
843
- score_thresholds: list[float],
844
- number_of_examples: int,
845
- ) -> list[ConfusionMatrix]:
465
+ iou_thresholds: list[float] = [0.5, 0.75, 0.9],
466
+ score_thresholds: list[float] = [0.5],
467
+ filter_: Filter | None = None,
468
+ as_dict: bool = False,
469
+ ) -> dict[MetricType, list]:
846
470
  """
847
- Computes detailed counting metrics.
471
+ Computes all metrics except for ConfusionMatrix
848
472
 
849
473
  Parameters
850
474
  ----------
851
- data : NDArray[np.floating]
852
- An array containing detailed pairs of detections.
853
- label_metadata : NDArray[np.int32]
854
- An array containing label metadata.
855
475
  iou_thresholds : list[float]
856
- List of IoU thresholds to compute metrics for.
476
+ A list of IoU thresholds to compute metrics over.
857
477
  score_thresholds : list[float]
858
- List of confidence thresholds to compute metrics for.
859
- number_of_examples : int
860
- Maximum number of annotation examples to return per metric.
478
+ A list of score thresholds to compute metrics over.
479
+ filter_ : Filter, optional
480
+ An optional filter object.
481
+ as_dict : bool, default=False
482
+ An option to return metrics as dictionaries.
861
483
 
862
484
  Returns
863
485
  -------
864
- list[list[ConfusionMatrix]]
865
- Outer list is indexed by label, inner list is by IoU.
486
+ dict[MetricType, list]
487
+ A dictionary mapping MetricType enumerations to lists of computed metrics.
866
488
  """
867
489
 
868
- if data.size == 0:
869
- return list()
490
+ ranked_pairs = self._ranked_pairs
491
+ label_metadata = self._label_metadata
492
+ if filter_ is not None:
493
+ ranked_pairs = ranked_pairs[filter_.ranked_indices]
494
+ label_metadata = filter_.label_metadata
870
495
 
871
496
  (
872
- confusion_matrix,
873
- hallucinations,
874
- missing_predictions,
875
- ) = compute_confusion_matrix(
876
- data=data,
497
+ (
498
+ average_precision,
499
+ mean_average_precision,
500
+ average_precision_average_over_ious,
501
+ mean_average_precision_average_over_ious,
502
+ ),
503
+ (
504
+ average_recall,
505
+ mean_average_recall,
506
+ average_recall_averaged_over_scores,
507
+ mean_average_recall_averaged_over_scores,
508
+ ),
509
+ precision_recall,
510
+ pr_curves,
511
+ ) = compute_metrics(
512
+ data=ranked_pairs,
513
+ label_metadata=label_metadata,
514
+ iou_thresholds=np.array(iou_thresholds),
515
+ score_thresholds=np.array(score_thresholds),
516
+ )
517
+
518
+ metrics = defaultdict(list)
519
+
520
+ metrics[MetricType.AP] = [
521
+ AP(
522
+ value=float(average_precision[iou_idx][label_idx]),
523
+ iou_threshold=iou_thresholds[iou_idx],
524
+ label=self.index_to_label[label_idx],
525
+ )
526
+ for iou_idx in range(average_precision.shape[0])
527
+ for label_idx in range(average_precision.shape[1])
528
+ if int(label_metadata[label_idx, 0]) > 0
529
+ ]
530
+
531
+ metrics[MetricType.mAP] = [
532
+ mAP(
533
+ value=float(mean_average_precision[iou_idx]),
534
+ iou_threshold=iou_thresholds[iou_idx],
535
+ )
536
+ for iou_idx in range(mean_average_precision.shape[0])
537
+ ]
538
+
539
+ metrics[MetricType.APAveragedOverIOUs] = [
540
+ APAveragedOverIOUs(
541
+ value=float(average_precision_average_over_ious[label_idx]),
542
+ iou_thresholds=iou_thresholds,
543
+ label=self.index_to_label[label_idx],
544
+ )
545
+ for label_idx in range(self.n_labels)
546
+ if int(label_metadata[label_idx, 0]) > 0
547
+ ]
548
+
549
+ metrics[MetricType.mAPAveragedOverIOUs] = [
550
+ mAPAveragedOverIOUs(
551
+ value=float(mean_average_precision_average_over_ious),
552
+ iou_thresholds=iou_thresholds,
553
+ )
554
+ ]
555
+
556
+ metrics[MetricType.AR] = [
557
+ AR(
558
+ value=float(average_recall[score_idx][label_idx]),
559
+ iou_thresholds=iou_thresholds,
560
+ score_threshold=score_thresholds[score_idx],
561
+ label=self.index_to_label[label_idx],
562
+ )
563
+ for score_idx in range(average_recall.shape[0])
564
+ for label_idx in range(average_recall.shape[1])
565
+ if int(label_metadata[label_idx, 0]) > 0
566
+ ]
567
+
568
+ metrics[MetricType.mAR] = [
569
+ mAR(
570
+ value=float(mean_average_recall[score_idx]),
571
+ iou_thresholds=iou_thresholds,
572
+ score_threshold=score_thresholds[score_idx],
573
+ )
574
+ for score_idx in range(mean_average_recall.shape[0])
575
+ ]
576
+
577
+ metrics[MetricType.ARAveragedOverScores] = [
578
+ ARAveragedOverScores(
579
+ value=float(average_recall_averaged_over_scores[label_idx]),
580
+ score_thresholds=score_thresholds,
581
+ iou_thresholds=iou_thresholds,
582
+ label=self.index_to_label[label_idx],
583
+ )
584
+ for label_idx in range(self.n_labels)
585
+ if int(label_metadata[label_idx, 0]) > 0
586
+ ]
587
+
588
+ metrics[MetricType.mARAveragedOverScores] = [
589
+ mARAveragedOverScores(
590
+ value=float(mean_average_recall_averaged_over_scores),
591
+ score_thresholds=score_thresholds,
592
+ iou_thresholds=iou_thresholds,
593
+ )
594
+ ]
595
+
596
+ metrics[MetricType.PrecisionRecallCurve] = [
597
+ PrecisionRecallCurve(
598
+ precision=pr_curves[iou_idx][label_idx].astype(float).tolist(),
599
+ iou_threshold=iou_threshold,
600
+ label=label,
601
+ )
602
+ for iou_idx, iou_threshold in enumerate(iou_thresholds)
603
+ for label_idx, label in self.index_to_label.items()
604
+ if int(label_metadata[label_idx, 0]) > 0
605
+ ]
606
+
607
+ for label_idx, label in self.index_to_label.items():
608
+
609
+ if label_metadata[label_idx, 0] == 0:
610
+ continue
611
+
612
+ for score_idx, score_threshold in enumerate(score_thresholds):
613
+ for iou_idx, iou_threshold in enumerate(iou_thresholds):
614
+
615
+ row = precision_recall[iou_idx][score_idx][label_idx]
616
+ kwargs = {
617
+ "label": label,
618
+ "iou_threshold": iou_threshold,
619
+ "score_threshold": score_threshold,
620
+ }
621
+ metrics[MetricType.Counts].append(
622
+ Counts(
623
+ tp=int(row[0]),
624
+ fp=int(row[1]),
625
+ fn=int(row[2]),
626
+ **kwargs,
627
+ )
628
+ )
629
+
630
+ metrics[MetricType.Precision].append(
631
+ Precision(
632
+ value=float(row[3]),
633
+ **kwargs,
634
+ )
635
+ )
636
+ metrics[MetricType.Recall].append(
637
+ Recall(
638
+ value=float(row[4]),
639
+ **kwargs,
640
+ )
641
+ )
642
+ metrics[MetricType.F1].append(
643
+ F1(
644
+ value=float(row[5]),
645
+ **kwargs,
646
+ )
647
+ )
648
+ metrics[MetricType.Accuracy].append(
649
+ Accuracy(
650
+ value=float(row[6]),
651
+ **kwargs,
652
+ )
653
+ )
654
+
655
+ if as_dict:
656
+ return {
657
+ mtype: [metric.to_dict() for metric in mvalues]
658
+ for mtype, mvalues in metrics.items()
659
+ }
660
+
661
+ return metrics
662
+
663
+ def compute_confusion_matrix(
664
+ self,
665
+ iou_thresholds: list[float] = [0.5, 0.75, 0.9],
666
+ score_thresholds: list[float] = [0.5],
667
+ number_of_examples: int = 0,
668
+ filter_: Filter | None = None,
669
+ as_dict: bool = False,
670
+ ) -> list:
671
+ """
672
+ Computes confusion matrices at various thresholds.
673
+
674
+ Parameters
675
+ ----------
676
+ iou_thresholds : list[float]
677
+ A list of IoU thresholds to compute metrics over.
678
+ score_thresholds : list[float]
679
+ A list of score thresholds to compute metrics over.
680
+ number_of_examples : int, default=0
681
+ Maximum number of annotation examples to return in ConfusionMatrix.
682
+ filter_ : Filter, optional
683
+ An optional filter object.
684
+ as_dict : bool, default=False
685
+ An option to return metrics as dictionaries.
686
+
687
+ Returns
688
+ -------
689
+ list[ConfusionMatrix] | list[dict]
690
+ List of confusion matrices per threshold pair.
691
+ """
692
+
693
+ detailed_pairs = self._detailed_pairs
694
+ label_metadata = self._label_metadata
695
+ if filter_ is not None:
696
+ detailed_pairs = detailed_pairs[filter_.detailed_indices]
697
+ label_metadata = filter_.label_metadata
698
+
699
+ if detailed_pairs.size == 0:
700
+ return list()
701
+
702
+ (
703
+ confusion_matrix,
704
+ hallucinations,
705
+ missing_predictions,
706
+ ) = compute_confusion_matrix(
707
+ data=detailed_pairs,
877
708
  label_metadata=label_metadata,
878
709
  iou_thresholds=np.array(iou_thresholds),
879
710
  score_thresholds=np.array(score_thresholds),
@@ -881,23 +712,20 @@ class Evaluator:
881
712
  )
882
713
 
883
714
  n_ious, n_scores, n_labels, _, _ = confusion_matrix.shape
884
- return [
715
+ matrices = [
885
716
  ConfusionMatrix(
886
717
  iou_threshold=iou_thresholds[iou_idx],
887
718
  score_threshold=score_thresholds[score_idx],
888
- label_key=label_key,
889
719
  number_of_examples=number_of_examples,
890
720
  confusion_matrix=self._unpack_confusion_matrix(
891
721
  confusion_matrix=confusion_matrix[
892
722
  iou_idx, score_idx, :, :, :
893
723
  ],
894
- label_key_idx=label_key_idx,
895
724
  number_of_labels=n_labels,
896
725
  number_of_examples=number_of_examples,
897
726
  ),
898
727
  hallucinations=self._unpack_hallucinations(
899
728
  hallucinations=hallucinations[iou_idx, score_idx, :, :],
900
- label_key_idx=label_key_idx,
901
729
  number_of_labels=n_labels,
902
730
  number_of_examples=number_of_examples,
903
731
  ),
@@ -905,16 +733,62 @@ class Evaluator:
905
733
  missing_predictions=missing_predictions[
906
734
  iou_idx, score_idx, :, :
907
735
  ],
908
- label_key_idx=label_key_idx,
909
736
  number_of_labels=n_labels,
910
737
  number_of_examples=number_of_examples,
911
738
  ),
912
739
  )
913
- for label_key_idx, label_key in self.index_to_label_key.items()
914
740
  for iou_idx in range(n_ious)
915
741
  for score_idx in range(n_scores)
916
742
  ]
917
743
 
744
+ if as_dict:
745
+ return [m.to_dict() for m in matrices]
746
+ return matrices
747
+
748
+ def evaluate(
749
+ self,
750
+ iou_thresholds: list[float] = [0.5, 0.75, 0.9],
751
+ score_thresholds: list[float] = [0.5],
752
+ number_of_examples: int = 0,
753
+ filter_: Filter | None = None,
754
+ as_dict: bool = False,
755
+ ) -> dict[MetricType, list]:
756
+ """
757
+ Computes all avaiable metrics.
758
+
759
+ Parameters
760
+ ----------
761
+ iou_thresholds : list[float]
762
+ A list of IoU thresholds to compute metrics over.
763
+ score_thresholds : list[float]
764
+ A list of score thresholds to compute metrics over.
765
+ number_of_examples : int, default=0
766
+ Maximum number of annotation examples to return in ConfusionMatrix.
767
+ filter_ : Filter, optional
768
+ An optional filter object.
769
+ as_dict : bool, default=False
770
+ An option to return metrics as dictionaries.
771
+
772
+ Returns
773
+ -------
774
+ dict[MetricType, list]
775
+ A dictionary mapping metric type to a list of metrics.
776
+ """
777
+ results = self.compute_precision_recall(
778
+ iou_thresholds=iou_thresholds,
779
+ score_thresholds=score_thresholds,
780
+ filter_=filter_,
781
+ as_dict=as_dict,
782
+ )
783
+ results[MetricType.ConfusionMatrix] = self.compute_confusion_matrix(
784
+ iou_thresholds=iou_thresholds,
785
+ score_thresholds=score_thresholds,
786
+ number_of_examples=number_of_examples,
787
+ filter_=filter_,
788
+ as_dict=as_dict,
789
+ )
790
+ return results
791
+
918
792
 
919
793
  class DataLoader:
920
794
  """
@@ -923,7 +797,7 @@ class DataLoader:
923
797
 
924
798
  def __init__(self):
925
799
  self._evaluator = Evaluator()
926
- self.pairs: list[NDArray[np.floating]] = list()
800
+ self.pairs: list[NDArray[np.float64]] = list()
927
801
  self.groundtruth_count = defaultdict(lambda: defaultdict(int))
928
802
  self.prediction_count = defaultdict(lambda: defaultdict(int))
929
803
 
@@ -947,51 +821,36 @@ class DataLoader:
947
821
  self._evaluator.index_to_uid[index] = uid
948
822
  return self._evaluator.uid_to_index[uid]
949
823
 
950
- def _add_label(self, label: tuple[str, str]) -> tuple[int, int]:
824
+ def _add_label(self, label: str) -> int:
951
825
  """
952
826
  Helper function for adding a label to the cache.
953
827
 
954
828
  Parameters
955
829
  ----------
956
- label : tuple[str, str]
957
- The label as a tuple in format (key, value).
830
+ label : str
831
+ The label associated with the annotation.
958
832
 
959
833
  Returns
960
834
  -------
961
835
  int
962
836
  Label index.
963
- int
964
- Label key index.
965
837
  """
966
838
 
967
839
  label_id = len(self._evaluator.index_to_label)
968
- label_key_id = len(self._evaluator.index_to_label_key)
969
840
  if label not in self._evaluator.label_to_index:
970
841
  self._evaluator.label_to_index[label] = label_id
971
842
  self._evaluator.index_to_label[label_id] = label
972
843
 
973
- # update label key index
974
- if label[0] not in self._evaluator.label_key_to_index:
975
- self._evaluator.label_key_to_index[label[0]] = label_key_id
976
- self._evaluator.index_to_label_key[label_key_id] = label[0]
977
- label_key_id += 1
978
-
979
- self._evaluator.label_index_to_label_key_index[
980
- label_id
981
- ] = self._evaluator.label_key_to_index[label[0]]
982
844
  label_id += 1
983
845
 
984
- return (
985
- self._evaluator.label_to_index[label],
986
- self._evaluator.label_key_to_index[label[0]],
987
- )
846
+ return self._evaluator.label_to_index[label]
988
847
 
989
848
  def _compute_ious_and_cache_pairs(
990
849
  self,
991
850
  uid_index: int,
992
- keyed_groundtruths: dict,
993
- keyed_predictions: dict,
994
- annotation_type: type[BoundingBox] | type[Polygon] | type[Bitmask],
851
+ groundtruths: list,
852
+ predictions: list,
853
+ annotation_type: Type[BoundingBox] | Type[Polygon] | Type[Bitmask],
995
854
  ) -> None:
996
855
  """
997
856
  Compute IOUs between groundtruths and preditions before storing as pairs.
@@ -1000,122 +859,92 @@ class DataLoader:
1000
859
  ----------
1001
860
  uid_index: int
1002
861
  The index of the detection.
1003
- keyed_groundtruths: dict
1004
- A dictionary of groundtruths.
1005
- keyed_predictions: dict
1006
- A dictionary of predictions.
862
+ groundtruths: list
863
+ A list of groundtruths.
864
+ predictions: list
865
+ A list of predictions.
1007
866
  annotation_type: type[BoundingBox] | type[Polygon] | type[Bitmask]
1008
867
  The type of annotation to compute IOUs for.
1009
868
  """
1010
- gt_keys = set(keyed_groundtruths.keys())
1011
- pd_keys = set(keyed_predictions.keys())
1012
- joint_keys = gt_keys.intersection(pd_keys)
1013
- gt_unique_keys = gt_keys - pd_keys
1014
- pd_unique_keys = pd_keys - gt_keys
1015
869
 
1016
870
  pairs = list()
1017
- for key in joint_keys:
1018
- n_predictions = len(keyed_predictions[key])
1019
- n_groundtruths = len(keyed_groundtruths[key])
1020
- data = _get_annotation_data(
1021
- keyed_groundtruths=keyed_groundtruths,
1022
- keyed_predictions=keyed_predictions,
1023
- key=key,
1024
- annotation_type=annotation_type,
1025
- )
1026
- ious = compute_iou(data=data, annotation_type=annotation_type)
1027
- mask_nonzero_iou = (ious > 1e-9).reshape(
1028
- (n_predictions, n_groundtruths)
1029
- )
1030
- mask_ious_halluc = ~(mask_nonzero_iou.any(axis=1))
1031
- mask_ious_misprd = ~(mask_nonzero_iou.any(axis=0))
871
+ n_predictions = len(predictions)
872
+ n_groundtruths = len(groundtruths)
1032
873
 
1033
- pairs.extend(
1034
- [
1035
- np.array(
1036
- [
1037
- float(uid_index),
1038
- float(gidx),
1039
- float(pidx),
1040
- ious[pidx * len(keyed_groundtruths[key]) + gidx],
1041
- float(glabel),
1042
- float(plabel),
1043
- float(score),
1044
- ]
1045
- )
1046
- for pidx, plabel, score, _ in keyed_predictions[key]
1047
- for gidx, glabel, _ in keyed_groundtruths[key]
1048
- if ious[pidx * len(keyed_groundtruths[key]) + gidx] > 1e-9
1049
- ]
1050
- )
1051
- pairs.extend(
1052
- [
1053
- np.array(
1054
- [
1055
- float(uid_index),
1056
- -1.0,
1057
- float(pidx),
1058
- 0.0,
1059
- -1.0,
1060
- float(plabel),
1061
- float(score),
1062
- ]
1063
- )
1064
- for pidx, plabel, score, _ in keyed_predictions[key]
1065
- if mask_ious_halluc[pidx]
1066
- ]
1067
- )
1068
- pairs.extend(
1069
- [
1070
- np.array(
1071
- [
1072
- float(uid_index),
1073
- float(gidx),
1074
- -1.0,
1075
- 0.0,
1076
- float(glabel),
1077
- -1.0,
1078
- -1.0,
1079
- ]
1080
- )
1081
- for gidx, glabel, _ in keyed_groundtruths[key]
1082
- if mask_ious_misprd[gidx]
1083
- ]
1084
- )
1085
- for key in gt_unique_keys:
1086
- pairs.extend(
1087
- [
1088
- np.array(
1089
- [
1090
- float(uid_index),
1091
- float(gidx),
1092
- -1.0,
1093
- 0.0,
1094
- float(glabel),
1095
- -1.0,
1096
- -1.0,
1097
- ]
1098
- )
1099
- for gidx, glabel, _ in keyed_groundtruths[key]
1100
- ]
1101
- )
1102
- for key in pd_unique_keys:
1103
- pairs.extend(
1104
- [
1105
- np.array(
1106
- [
1107
- float(uid_index),
1108
- -1.0,
1109
- float(pidx),
1110
- 0.0,
1111
- -1.0,
1112
- float(plabel),
1113
- float(score),
1114
- ]
1115
- )
1116
- for pidx, plabel, score, _ in keyed_predictions[key]
1117
- ]
1118
- )
874
+ all_pairs = np.array(
875
+ [
876
+ np.array([gann, pann])
877
+ for _, _, _, pann in predictions
878
+ for _, _, gann in groundtruths
879
+ ]
880
+ )
881
+
882
+ match annotation_type:
883
+ case annotation.BoundingBox:
884
+ ious = compute_bbox_iou(all_pairs)
885
+ case annotation.Polygon:
886
+ ious = compute_polygon_iou(all_pairs)
887
+ case annotation.Bitmask:
888
+ ious = compute_bitmask_iou(all_pairs)
889
+ case _:
890
+ raise ValueError(
891
+ f"Invalid annotation type `{annotation_type}`."
892
+ )
893
+
894
+ ious = ious.reshape(n_predictions, n_groundtruths)
895
+ predictions_with_iou_of_zero = np.where((ious < 1e-9).all(axis=1))[0]
896
+ groundtruths_with_iou_of_zero = np.where((ious < 1e-9).all(axis=0))[0]
897
+
898
+ pairs.extend(
899
+ [
900
+ np.array(
901
+ [
902
+ float(uid_index),
903
+ float(gidx),
904
+ float(pidx),
905
+ ious[pidx, gidx],
906
+ float(glabel),
907
+ float(plabel),
908
+ float(score),
909
+ ]
910
+ )
911
+ for pidx, plabel, score, _ in predictions
912
+ for gidx, glabel, _ in groundtruths
913
+ if ious[pidx, gidx] >= 1e-9
914
+ ]
915
+ )
916
+ pairs.extend(
917
+ [
918
+ np.array(
919
+ [
920
+ float(uid_index),
921
+ -1.0,
922
+ float(predictions[index][0]),
923
+ 0.0,
924
+ -1.0,
925
+ float(predictions[index][1]),
926
+ float(predictions[index][2]),
927
+ ]
928
+ )
929
+ for index in predictions_with_iou_of_zero
930
+ ]
931
+ )
932
+ pairs.extend(
933
+ [
934
+ np.array(
935
+ [
936
+ float(uid_index),
937
+ float(groundtruths[index][0]),
938
+ -1.0,
939
+ 0.0,
940
+ float(groundtruths[index][1]),
941
+ -1.0,
942
+ -1.0,
943
+ ]
944
+ )
945
+ for index in groundtruths_with_iou_of_zero
946
+ ]
947
+ )
1119
948
 
1120
949
  self.pairs.append(np.array(pairs))
1121
950
 
@@ -1157,12 +986,8 @@ class DataLoader:
1157
986
  )
1158
987
 
1159
988
  # cache labels and annotations
1160
- keyed_groundtruths = defaultdict(list)
1161
- keyed_predictions = defaultdict(list)
1162
-
1163
- representation_property = _get_annotation_representation(
1164
- annotation_type=annotation_type
1165
- )
989
+ groundtruths = list()
990
+ predictions = list()
1166
991
 
1167
992
  for gidx, gann in enumerate(detection.groundtruths):
1168
993
  if not isinstance(gann, annotation_type):
@@ -1170,26 +995,17 @@ class DataLoader:
1170
995
  f"Expected {annotation_type}, but annotation is of type {type(gann)}."
1171
996
  )
1172
997
 
1173
- if isinstance(gann, BoundingBox):
1174
- self._evaluator.groundtruth_examples[uid_index][
1175
- gidx
1176
- ] = getattr(gann, representation_property)
1177
- else:
1178
- converted_box = gann.to_box()
1179
- self._evaluator.groundtruth_examples[uid_index][gidx] = (
1180
- getattr(converted_box, "extrema")
1181
- if converted_box is not None
1182
- else None
1183
- )
998
+ self._evaluator.groundtruth_examples[uid_index][
999
+ gidx
1000
+ ] = gann.extrema
1184
1001
  for glabel in gann.labels:
1185
- label_idx, label_key_idx = self._add_label(glabel)
1002
+ label_idx = self._add_label(glabel)
1186
1003
  self.groundtruth_count[label_idx][uid_index] += 1
1187
- representation = getattr(gann, representation_property)
1188
- keyed_groundtruths[label_key_idx].append(
1004
+ groundtruths.append(
1189
1005
  (
1190
1006
  gidx,
1191
1007
  label_idx,
1192
- representation,
1008
+ gann.annotation,
1193
1009
  )
1194
1010
  )
1195
1011
 
@@ -1199,36 +1015,25 @@ class DataLoader:
1199
1015
  f"Expected {annotation_type}, but annotation is of type {type(pann)}."
1200
1016
  )
1201
1017
 
1202
- if isinstance(pann, BoundingBox):
1203
- self._evaluator.prediction_examples[uid_index][
1204
- pidx
1205
- ] = getattr(pann, representation_property)
1206
- else:
1207
- converted_box = pann.to_box()
1208
- self._evaluator.prediction_examples[uid_index][pidx] = (
1209
- getattr(converted_box, "extrema")
1210
- if converted_box is not None
1211
- else None
1212
- )
1018
+ self._evaluator.prediction_examples[uid_index][
1019
+ pidx
1020
+ ] = pann.extrema
1213
1021
  for plabel, pscore in zip(pann.labels, pann.scores):
1214
- label_idx, label_key_idx = self._add_label(plabel)
1022
+ label_idx = self._add_label(plabel)
1215
1023
  self.prediction_count[label_idx][uid_index] += 1
1216
- representation = representation = getattr(
1217
- pann, representation_property
1218
- )
1219
- keyed_predictions[label_key_idx].append(
1024
+ predictions.append(
1220
1025
  (
1221
1026
  pidx,
1222
1027
  label_idx,
1223
1028
  pscore,
1224
- representation,
1029
+ pann.annotation,
1225
1030
  )
1226
1031
  )
1227
1032
 
1228
1033
  self._compute_ious_and_cache_pairs(
1229
1034
  uid_index=uid_index,
1230
- keyed_groundtruths=keyed_groundtruths,
1231
- keyed_predictions=keyed_predictions,
1035
+ groundtruths=groundtruths,
1036
+ predictions=predictions,
1232
1037
  annotation_type=annotation_type,
1233
1038
  )
1234
1039
 
@@ -1295,150 +1100,6 @@ class DataLoader:
1295
1100
  annotation_type=Bitmask,
1296
1101
  )
1297
1102
 
1298
- def _add_data_from_valor_dict(
1299
- self,
1300
- detections: list[tuple[dict, dict]],
1301
- annotation_type: type[Bitmask] | type[BoundingBox] | type[Polygon],
1302
- show_progress: bool = False,
1303
- ):
1304
- """
1305
- Adds Valor-format detections to the cache.
1306
-
1307
- Parameters
1308
- ----------
1309
- detections : list[tuple[dict, dict]]
1310
- A list of groundtruth, prediction pairs in Valor-format dictionaries.
1311
- annotation_type : type[Bitmask] | type[BoundingBox] | type[Polygon]
1312
- The annotation type to process.
1313
- show_progress : bool, default=False
1314
- Toggle for tqdm progress bar.
1315
- """
1316
-
1317
- disable_tqdm = not show_progress
1318
- for groundtruth, prediction in tqdm(detections, disable=disable_tqdm):
1319
- # update metadata
1320
- self._evaluator.n_datums += 1
1321
- self._evaluator.n_groundtruths += len(groundtruth["annotations"])
1322
- self._evaluator.n_predictions += len(prediction["annotations"])
1323
-
1324
- # update datum uid index
1325
- uid_index = self._add_datum(uid=groundtruth["datum"]["uid"])
1326
-
1327
- # initialize bounding box examples
1328
- self._evaluator.groundtruth_examples[uid_index] = np.zeros(
1329
- (len(groundtruth["annotations"]), 4), dtype=np.float16
1330
- )
1331
- self._evaluator.prediction_examples[uid_index] = np.zeros(
1332
- (len(prediction["annotations"]), 4), dtype=np.float16
1333
- )
1334
-
1335
- # cache labels and annotations
1336
- keyed_groundtruths = defaultdict(list)
1337
- keyed_predictions = defaultdict(list)
1338
-
1339
- annotation_key = _get_valor_dict_annotation_key(
1340
- annotation_type=annotation_type
1341
- )
1342
- invalid_keys = list(
1343
- filter(
1344
- lambda x: x != annotation_key,
1345
- ["bounding_box", "raster", "polygon"],
1346
- )
1347
- )
1348
-
1349
- for gidx, gann in enumerate(groundtruth["annotations"]):
1350
- if (gann[annotation_key] is None) or any(
1351
- [gann[k] is not None for k in invalid_keys]
1352
- ):
1353
- raise ValueError(
1354
- f"Input JSON doesn't contain {annotation_type} data, or contains data for multiple annotation types."
1355
- )
1356
- if annotation_type == BoundingBox:
1357
- self._evaluator.groundtruth_examples[uid_index][
1358
- gidx
1359
- ] = np.array(
1360
- _get_annotation_representation_from_valor_dict(
1361
- gann[annotation_key],
1362
- annotation_type=annotation_type,
1363
- ),
1364
- )
1365
-
1366
- for valor_label in gann["labels"]:
1367
- glabel = (valor_label["key"], valor_label["value"])
1368
- label_idx, label_key_idx = self._add_label(glabel)
1369
- self.groundtruth_count[label_idx][uid_index] += 1
1370
- keyed_groundtruths[label_key_idx].append(
1371
- (
1372
- gidx,
1373
- label_idx,
1374
- _get_annotation_representation_from_valor_dict(
1375
- gann[annotation_key],
1376
- annotation_type=annotation_type,
1377
- ),
1378
- )
1379
- )
1380
- for pidx, pann in enumerate(prediction["annotations"]):
1381
- if (pann[annotation_key] is None) or any(
1382
- [pann[k] is not None for k in invalid_keys]
1383
- ):
1384
- raise ValueError(
1385
- f"Input JSON doesn't contain {annotation_type} data, or contains data for multiple annotation types."
1386
- )
1387
-
1388
- if annotation_type == BoundingBox:
1389
- self._evaluator.prediction_examples[uid_index][
1390
- pidx
1391
- ] = np.array(
1392
- _get_annotation_representation_from_valor_dict(
1393
- pann[annotation_key],
1394
- annotation_type=annotation_type,
1395
- )
1396
- )
1397
- for valor_label in pann["labels"]:
1398
- plabel = (valor_label["key"], valor_label["value"])
1399
- pscore = valor_label["score"]
1400
- label_idx, label_key_idx = self._add_label(plabel)
1401
- self.prediction_count[label_idx][uid_index] += 1
1402
- keyed_predictions[label_key_idx].append(
1403
- (
1404
- pidx,
1405
- label_idx,
1406
- pscore,
1407
- _get_annotation_representation_from_valor_dict(
1408
- pann[annotation_key],
1409
- annotation_type=annotation_type,
1410
- ),
1411
- )
1412
- )
1413
-
1414
- self._compute_ious_and_cache_pairs(
1415
- uid_index=uid_index,
1416
- keyed_groundtruths=keyed_groundtruths,
1417
- keyed_predictions=keyed_predictions,
1418
- annotation_type=annotation_type,
1419
- )
1420
-
1421
- def add_bounding_boxes_from_valor_dict(
1422
- self,
1423
- detections: list[tuple[dict, dict]],
1424
- show_progress: bool = False,
1425
- ):
1426
- """
1427
- Adds Valor-format bounding box detections to the cache.
1428
-
1429
- Parameters
1430
- ----------
1431
- detections : list[tuple[dict, dict]]
1432
- A list of groundtruth, prediction pairs in Valor-format dictionaries.
1433
- show_progress : bool, default=False
1434
- Toggle for tqdm progress bar.
1435
- """
1436
- return self._add_data_from_valor_dict(
1437
- detections=detections,
1438
- show_progress=show_progress,
1439
- annotation_type=BoundingBox,
1440
- )
1441
-
1442
1103
  def finalize(self) -> Evaluator:
1443
1104
  """
1444
1105
  Performs data finalization and some preprocessing steps.
@@ -1494,11 +1155,6 @@ class DataLoader:
1494
1155
  ]
1495
1156
  )
1496
1157
  ),
1497
- float(
1498
- self._evaluator.label_index_to_label_key_index[
1499
- label_idx
1500
- ]
1501
- ),
1502
1158
  ]
1503
1159
  for label_idx in range(n_labels)
1504
1160
  ]