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.

@@ -1,11 +1,10 @@
1
- from .annotation import Bitmask, BoundingBox, Detection
1
+ from .annotation import Bitmask, BoundingBox, Detection, Polygon
2
2
  from .computation import (
3
3
  compute_detailed_counts,
4
- compute_iou,
5
4
  compute_metrics,
6
5
  compute_ranked_pairs,
7
6
  )
8
- from .manager import DataLoader, Evaluator
7
+ from .manager import DataLoader, Evaluator, compute_iou
9
8
  from .metric import (
10
9
  AP,
11
10
  AR,
@@ -29,6 +28,7 @@ __all__ = [
29
28
  "Bitmask",
30
29
  "BoundingBox",
31
30
  "Detection",
31
+ "Polygon",
32
32
  "MetricType",
33
33
  "Counts",
34
34
  "Precision",
@@ -2,6 +2,7 @@ from dataclasses import dataclass, field
2
2
 
3
3
  import numpy as np
4
4
  from numpy.typing import NDArray
5
+ from shapely.geometry import Polygon as ShapelyPolygon
5
6
 
6
7
 
7
8
  @dataclass
@@ -24,6 +25,37 @@ class BoundingBox:
24
25
  return (self.xmin, self.xmax, self.ymin, self.ymax)
25
26
 
26
27
 
28
+ @dataclass
29
+ class Polygon:
30
+ shape: ShapelyPolygon
31
+ labels: list[tuple[str, str]]
32
+ scores: list[float] = field(default_factory=list)
33
+
34
+ def __post_init__(self):
35
+ if not isinstance(self.shape, ShapelyPolygon):
36
+ raise TypeError("shape must be of type shapely.geometry.Polygon.")
37
+ if len(self.scores) > 0 and len(self.labels) != len(self.scores):
38
+ raise ValueError(
39
+ "If scores are defined, there must be a 1:1 pairing with labels."
40
+ )
41
+
42
+ def to_box(self) -> BoundingBox | None:
43
+
44
+ if self.shape.is_empty:
45
+ return None
46
+
47
+ xmin, ymin, xmax, ymax = self.shape.bounds
48
+
49
+ return BoundingBox(
50
+ xmin=xmin,
51
+ xmax=xmax,
52
+ ymin=ymin,
53
+ ymax=ymax,
54
+ labels=self.labels,
55
+ scores=self.scores,
56
+ )
57
+
58
+
27
59
  @dataclass
28
60
  class Bitmask:
29
61
  mask: NDArray[np.bool_]
@@ -36,15 +68,27 @@ class Bitmask:
36
68
  "If scores are defined, there must be a 1:1 pairing with labels."
37
69
  )
38
70
 
39
- def to_box(self) -> BoundingBox:
40
- raise NotImplementedError
71
+ def to_box(self) -> BoundingBox | None:
72
+
73
+ if not self.mask.any():
74
+ return None
75
+
76
+ rows, cols = np.nonzero(self.mask)
77
+ return BoundingBox(
78
+ xmin=cols.min(),
79
+ xmax=cols.max(),
80
+ ymin=rows.min(),
81
+ ymax=rows.max(),
82
+ labels=self.labels,
83
+ scores=self.scores,
84
+ )
41
85
 
42
86
 
43
87
  @dataclass
44
88
  class Detection:
45
89
  uid: str
46
- groundtruths: list[BoundingBox]
47
- predictions: list[BoundingBox]
90
+ groundtruths: list[BoundingBox] | list[Bitmask] | list[Polygon]
91
+ predictions: list[BoundingBox] | list[Bitmask] | list[Polygon]
48
92
 
49
93
  def __post_init__(self):
50
94
  for prediction in self.predictions:
@@ -2,7 +2,7 @@ import numpy as np
2
2
  from numpy.typing import NDArray
3
3
 
4
4
 
5
- def compute_iou(data: NDArray[np.floating]) -> NDArray[np.floating]:
5
+ def compute_bbox_iou(data: NDArray[np.floating]) -> NDArray[np.floating]:
6
6
  """
7
7
  Computes intersection-over-union (IoU) for axis-aligned bounding boxes.
8
8
 
@@ -24,14 +24,12 @@ def compute_iou(data: NDArray[np.floating]) -> NDArray[np.floating]:
24
24
  Parameters
25
25
  ----------
26
26
  data : NDArray[np.floating]
27
- A sorted array of classification pairs.
28
- label_metadata : NDArray[np.int32]
29
- An array containing metadata related to labels.
27
+ A sorted array of bounding box pairs.
30
28
 
31
29
  Returns
32
30
  -------
33
31
  NDArray[np.floating]
34
- Compute IoU's.
32
+ Computed IoU's.
35
33
  """
36
34
 
37
35
  xmin1, xmax1, ymin1, ymax1 = (
@@ -69,10 +67,77 @@ def compute_iou(data: NDArray[np.floating]) -> NDArray[np.floating]:
69
67
  return iou
70
68
 
71
69
 
70
+ def compute_bitmask_iou(data: NDArray[np.floating]) -> NDArray[np.floating]:
71
+ """
72
+ Computes intersection-over-union (IoU) for bitmasks.
73
+
74
+ Takes data with shape (N, 2):
75
+
76
+ Index 0 - first bitmask
77
+ Index 1 - second bitmask
78
+
79
+ Returns data with shape (N, 1):
80
+
81
+ Index 0 - IoU
82
+
83
+ Parameters
84
+ ----------
85
+ data : NDArray[np.floating]
86
+ A sorted array of bitmask pairs.
87
+
88
+ Returns
89
+ -------
90
+ NDArray[np.floating]
91
+ Computed IoU's.
92
+ """
93
+ intersection_ = np.array([np.logical_and(x, y).sum() for x, y in data])
94
+ union_ = np.array([np.logical_or(x, y).sum() for x, y in data])
95
+
96
+ return intersection_ / union_
97
+
98
+
99
+ def compute_polygon_iou(
100
+ data: NDArray[np.floating],
101
+ ) -> NDArray[np.floating]:
102
+ """
103
+ Computes intersection-over-union (IoU) for shapely polygons.
104
+
105
+ Takes data with shape (N, 2):
106
+
107
+ Index 0 - first polygon
108
+ Index 1 - second polygon
109
+
110
+ Returns data with shape (N, 1):
111
+
112
+ Index 0 - IoU
113
+
114
+ Parameters
115
+ ----------
116
+ data : NDArray[np.floating]
117
+ A sorted array of polygon pairs.
118
+
119
+ Returns
120
+ -------
121
+ NDArray[np.floating]
122
+ Computed IoU's.
123
+ """
124
+ intersection_ = np.array(
125
+ [poly1.intersection(poly2).area for poly1, poly2 in data]
126
+ )
127
+ union_ = np.array(
128
+ [
129
+ poly1.area + poly2.area - intersection_[i]
130
+ for i, (poly1, poly2) in enumerate(data)
131
+ ]
132
+ )
133
+
134
+ return intersection_ / union_
135
+
136
+
72
137
  def _compute_ranked_pairs_for_datum(
73
- data: np.ndarray,
74
- label_metadata: np.ndarray,
75
- ) -> np.ndarray:
138
+ data: NDArray[np.floating],
139
+ label_metadata: NDArray[np.int32],
140
+ ) -> NDArray[np.floating]:
76
141
  """
77
142
  Computes ranked pairs for a datum.
78
143
  """
@@ -113,7 +178,7 @@ def _compute_ranked_pairs_for_datum(
113
178
 
114
179
  def compute_ranked_pairs(
115
180
  data: list[NDArray[np.floating]],
116
- label_metadata: NDArray[np.integer],
181
+ label_metadata: NDArray[np.int32],
117
182
  ) -> NDArray[np.floating]:
118
183
  """
119
184
  Performs pair ranking on input data.
@@ -133,7 +198,7 @@ def compute_ranked_pairs(
133
198
  Parameters
134
199
  ----------
135
200
  data : NDArray[np.floating]
136
- A sorted array of classification pairs.
201
+ A sorted array summarizing the IOU calculations of one or more pairs.
137
202
  label_metadata : NDArray[np.int32]
138
203
  An array containing metadata related to labels.
139
204
 
@@ -142,30 +207,29 @@ def compute_ranked_pairs(
142
207
  NDArray[np.floating]
143
208
  A filtered array containing only ranked pairs.
144
209
  """
145
- pairs = np.concatenate(
146
- [
147
- _compute_ranked_pairs_for_datum(
148
- datum,
149
- label_metadata=label_metadata,
150
- )
151
- for datum in data
152
- ],
153
- axis=0,
154
- )
210
+
211
+ ranked_pairs_by_datum = [
212
+ _compute_ranked_pairs_for_datum(
213
+ data=datum,
214
+ label_metadata=label_metadata,
215
+ )
216
+ for datum in data
217
+ ]
218
+ ranked_pairs = np.concatenate(ranked_pairs_by_datum, axis=0)
155
219
  indices = np.lexsort(
156
220
  (
157
- -pairs[:, 3], # iou
158
- -pairs[:, 6], # score
221
+ -ranked_pairs[:, 3], # iou
222
+ -ranked_pairs[:, 6], # score
159
223
  )
160
224
  )
161
- return pairs[indices]
225
+ return ranked_pairs[indices]
162
226
 
163
227
 
164
228
  def compute_metrics(
165
- data: np.ndarray,
166
- label_metadata: np.ndarray,
167
- iou_thresholds: np.ndarray,
168
- score_thresholds: np.ndarray,
229
+ data: NDArray[np.floating],
230
+ label_metadata: NDArray[np.int32],
231
+ iou_thresholds: NDArray[np.floating],
232
+ score_thresholds: NDArray[np.floating],
169
233
  ) -> tuple[
170
234
  tuple[
171
235
  NDArray[np.floating],
@@ -198,7 +262,7 @@ def compute_metrics(
198
262
  Parameters
199
263
  ----------
200
264
  data : NDArray[np.floating]
201
- A sorted array of classification pairs.
265
+ A sorted array summarizing the IOU calculations of one or more pairs.
202
266
  label_metadata : NDArray[np.int32]
203
267
  An array containing metadata related to labels.
204
268
  iou_thresholds : NDArray[np.floating]
@@ -429,12 +493,12 @@ def compute_metrics(
429
493
 
430
494
 
431
495
  def compute_detailed_counts(
432
- data: np.ndarray,
433
- label_metadata: np.ndarray,
434
- iou_thresholds: np.ndarray,
435
- score_thresholds: np.ndarray,
496
+ data: NDArray[np.floating],
497
+ label_metadata: NDArray[np.int32],
498
+ iou_thresholds: NDArray[np.floating],
499
+ score_thresholds: NDArray[np.floating],
436
500
  n_samples: int,
437
- ) -> np.ndarray:
501
+ ) -> NDArray[np.int32]:
438
502
  """
439
503
  Compute detailed counts.
440
504
 
@@ -452,19 +516,19 @@ def compute_detailed_counts(
452
516
 
453
517
  Index 0 - True Positive Count
454
518
  ... Datum ID Examples
455
- Index n_samples + 1 - False Positive Misclassification Count
519
+ Index 2 * n_samples + 1 - False Positive Misclassification Count
456
520
  ... Datum ID Examples
457
- Index 2 * n_samples + 2 - False Positive Hallucination Count
521
+ Index 4 * n_samples + 2 - False Positive Hallucination Count
458
522
  ... Datum ID Examples
459
- Index 3 * n_samples + 3 - False Negative Misclassification Count
523
+ Index 6 * n_samples + 3 - False Negative Misclassification Count
460
524
  ... Datum ID Examples
461
- Index 4 * n_samples + 4 - False Negative Missing Prediction Count
525
+ Index 8 * n_samples + 4 - False Negative Missing Prediction Count
462
526
  ... Datum ID Examples
463
527
 
464
528
  Parameters
465
529
  ----------
466
530
  data : NDArray[np.floating]
467
- A sorted array of classification pairs.
531
+ A sorted array summarizing the IOU calculations of one or more pairs.
468
532
  label_metadata : NDArray[np.int32]
469
533
  An array containing metadata related to labels.
470
534
  iou_thresholds : NDArray[np.floating]
@@ -476,22 +540,24 @@ def compute_detailed_counts(
476
540
 
477
541
  Returns
478
542
  -------
479
- NDArray[np.floating]
543
+ NDArray[np.int32]
480
544
  The detailed counts with optional examples.
481
545
  """
482
546
 
483
547
  n_labels = label_metadata.shape[0]
484
548
  n_ious = iou_thresholds.shape[0]
485
549
  n_scores = score_thresholds.shape[0]
486
- n_metrics = 5 * (n_samples + 1)
550
+ n_metrics = 5 * (2 * n_samples + 1)
487
551
 
488
552
  tp_idx = 0
489
- fp_misclf_idx = tp_idx + n_samples + 1
490
- fp_halluc_idx = fp_misclf_idx + n_samples + 1
491
- fn_misclf_idx = fp_halluc_idx + n_samples + 1
492
- fn_misprd_idx = fn_misclf_idx + n_samples + 1
553
+ fp_misclf_idx = 2 * n_samples + 1
554
+ fp_halluc_idx = 4 * n_samples + 2
555
+ fn_misclf_idx = 6 * n_samples + 3
556
+ fn_misprd_idx = 8 * n_samples + 4
493
557
 
494
- detailed_pr_curve = np.ones((n_ious, n_scores, n_labels, n_metrics)) * -1.0
558
+ detailed_pr_curve = -1 * np.ones(
559
+ (n_ious, n_scores, n_labels, n_metrics), dtype=np.int32
560
+ )
495
561
 
496
562
  mask_gt_exists = data[:, 1] > -0.5
497
563
  mask_pd_exists = data[:, 2] > -0.5
@@ -509,13 +575,14 @@ def compute_detailed_counts(
509
575
  mask_iou_threshold = data[:, 3] >= iou_thresholds[iou_idx]
510
576
  mask_iou = mask_iou_nonzero & mask_iou_threshold
511
577
 
512
- groundtruths_with_pairs = np.unique(groundtruths[mask_iou], axis=0)
578
+ groundtruths_passing_ious = np.unique(groundtruths[mask_iou], axis=0)
513
579
  mask_groundtruths_with_passing_ious = (
514
- groundtruths.reshape(-1, 1, 2)
515
- == groundtruths_with_pairs.reshape(1, -1, 2)
516
- ).all(axis=2)
517
- mask_groundtruths_with_passing_ious = (
518
- mask_groundtruths_with_passing_ious.any(axis=1)
580
+ (
581
+ groundtruths.reshape(-1, 1, 2)
582
+ == groundtruths_passing_ious.reshape(1, -1, 2)
583
+ )
584
+ .all(axis=2)
585
+ .any(axis=1)
519
586
  )
520
587
  mask_groundtruths_without_passing_ious = (
521
588
  ~mask_groundtruths_with_passing_ious & mask_gt_exists
@@ -525,11 +592,12 @@ def compute_detailed_counts(
525
592
  predictions[mask_iou], axis=0
526
593
  )
527
594
  mask_predictions_with_passing_ious = (
528
- predictions.reshape(-1, 1, 2)
529
- == predictions_with_passing_ious.reshape(1, -1, 2)
530
- ).all(axis=2)
531
- mask_predictions_with_passing_ious = (
532
- mask_predictions_with_passing_ious.any(axis=1)
595
+ (
596
+ predictions.reshape(-1, 1, 2)
597
+ == predictions_with_passing_ious.reshape(1, -1, 2)
598
+ )
599
+ .all(axis=2)
600
+ .any(axis=1)
533
601
  )
534
602
  mask_predictions_without_passing_ious = (
535
603
  ~mask_predictions_with_passing_ious & mask_pd_exists
@@ -543,11 +611,12 @@ def compute_detailed_counts(
543
611
  groundtruths[mask_iou & mask_score], axis=0
544
612
  )
545
613
  mask_groundtruths_with_passing_score = (
546
- groundtruths.reshape(-1, 1, 2)
547
- == groundtruths_with_passing_score.reshape(1, -1, 2)
548
- ).all(axis=2)
549
- mask_groundtruths_with_passing_score = (
550
- mask_groundtruths_with_passing_score.any(axis=1)
614
+ (
615
+ groundtruths.reshape(-1, 1, 2)
616
+ == groundtruths_with_passing_score.reshape(1, -1, 2)
617
+ )
618
+ .all(axis=2)
619
+ .any(axis=1)
551
620
  )
552
621
  mask_groundtruths_without_passing_score = (
553
622
  ~mask_groundtruths_with_passing_score & mask_gt_exists
@@ -623,21 +692,41 @@ def compute_detailed_counts(
623
692
 
624
693
  if n_samples > 0:
625
694
  for label_idx in range(n_labels):
626
- tp_examples = tp[tp[:, 2].astype(int) == label_idx][
627
- :n_samples, 0
628
- ]
629
- fp_misclf_examples = fp_misclf[
630
- fp_misclf[:, 2].astype(int) == label_idx
631
- ][:n_samples, 0]
632
- fp_halluc_examples = fp_halluc[
633
- fp_halluc[:, 2].astype(int) == label_idx
634
- ][:n_samples, 0]
635
- fn_misclf_examples = fn_misclf[
636
- fn_misclf[:, 2].astype(int) == label_idx
637
- ][:n_samples, 0]
638
- fn_misprd_examples = fn_misprd[
639
- fn_misprd[:, 2].astype(int) == label_idx
640
- ][:n_samples, 0]
695
+ tp_examples = (
696
+ tp[tp[:, 2].astype(int) == label_idx][
697
+ :n_samples, [0, 1]
698
+ ]
699
+ .astype(int)
700
+ .flatten()
701
+ )
702
+ fp_misclf_examples = (
703
+ fp_misclf[fp_misclf[:, 2].astype(int) == label_idx][
704
+ :n_samples, [0, 1]
705
+ ]
706
+ .astype(int)
707
+ .flatten()
708
+ )
709
+ fp_halluc_examples = (
710
+ fp_halluc[fp_halluc[:, 2].astype(int) == label_idx][
711
+ :n_samples, [0, 1]
712
+ ]
713
+ .astype(int)
714
+ .flatten()
715
+ )
716
+ fn_misclf_examples = (
717
+ fn_misclf[fn_misclf[:, 2].astype(int) == label_idx][
718
+ :n_samples, [0, 1]
719
+ ]
720
+ .astype(int)
721
+ .flatten()
722
+ )
723
+ fn_misprd_examples = (
724
+ fn_misprd[fn_misprd[:, 2].astype(int) == label_idx][
725
+ :n_samples, [0, 1]
726
+ ]
727
+ .astype(int)
728
+ .flatten()
729
+ )
641
730
 
642
731
  detailed_pr_curve[
643
732
  iou_idx,