valor-lite 0.33.7__py3-none-any.whl → 0.33.8__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.
@@ -35,7 +35,7 @@ class Counts:
35
35
  tn: list[int]
36
36
  score_thresholds: list[float]
37
37
  hardmax: bool
38
- label: tuple[str, str]
38
+ label: str
39
39
 
40
40
  @property
41
41
  def metric(self) -> Metric:
@@ -50,10 +50,7 @@ class Counts:
50
50
  parameters={
51
51
  "score_thresholds": self.score_thresholds,
52
52
  "hardmax": self.hardmax,
53
- "label": {
54
- "key": self.label[0],
55
- "value": self.label[1],
56
- },
53
+ "label": self.label,
57
54
  },
58
55
  )
59
56
 
@@ -66,7 +63,7 @@ class _ThresholdValue:
66
63
  value: list[float]
67
64
  score_thresholds: list[float]
68
65
  hardmax: bool
69
- label: tuple[str, str]
66
+ label: str
70
67
 
71
68
  @property
72
69
  def metric(self) -> Metric:
@@ -76,10 +73,7 @@ class _ThresholdValue:
76
73
  parameters={
77
74
  "score_thresholds": self.score_thresholds,
78
75
  "hardmax": self.hardmax,
79
- "label": {
80
- "key": self.label[0],
81
- "value": self.label[1],
82
- },
76
+ "label": self.label,
83
77
  },
84
78
  )
85
79
 
@@ -106,19 +100,14 @@ class F1(_ThresholdValue):
106
100
  @dataclass
107
101
  class ROCAUC:
108
102
  value: float
109
- label: tuple[str, str]
103
+ label: str
110
104
 
111
105
  @property
112
106
  def metric(self) -> Metric:
113
107
  return Metric(
114
108
  type=type(self).__name__,
115
109
  value=self.value,
116
- parameters={
117
- "label": {
118
- "key": self.label[0],
119
- "value": self.label[1],
120
- },
121
- },
110
+ parameters={"label": self.label},
122
111
  )
123
112
 
124
113
  def to_dict(self) -> dict:
@@ -128,16 +117,13 @@ class ROCAUC:
128
117
  @dataclass
129
118
  class mROCAUC:
130
119
  value: float
131
- label_key: str
132
120
 
133
121
  @property
134
122
  def metric(self) -> Metric:
135
123
  return Metric(
136
124
  type=type(self).__name__,
137
125
  value=self.value,
138
- parameters={
139
- "label_key": self.label_key,
140
- },
126
+ parameters={},
141
127
  )
142
128
 
143
129
  def to_dict(self) -> dict:
@@ -170,7 +156,6 @@ class ConfusionMatrix:
170
156
  ],
171
157
  ]
172
158
  score_threshold: float
173
- label_key: str
174
159
  number_of_examples: int
175
160
 
176
161
  @property
@@ -183,7 +168,6 @@ class ConfusionMatrix:
183
168
  },
184
169
  parameters={
185
170
  "score_threshold": self.score_threshold,
186
- "label_key": self.label_key,
187
171
  },
188
172
  )
189
173
 
@@ -11,10 +11,14 @@ class BoundingBox:
11
11
  xmax: float
12
12
  ymin: float
13
13
  ymax: float
14
- labels: list[tuple[str, str]]
14
+ labels: list[str]
15
15
  scores: list[float] = field(default_factory=list)
16
16
 
17
17
  def __post_init__(self):
18
+ if len(self.scores) == 0 and len(self.labels) != 1:
19
+ raise ValueError(
20
+ "Ground truths must be defined with no scores and a single label. If you meant to define a prediction, then please include one score for every label provided."
21
+ )
18
22
  if len(self.scores) > 0 and len(self.labels) != len(self.scores):
19
23
  raise ValueError(
20
24
  "If scores are defined, there must be a 1:1 pairing with labels."
@@ -22,66 +26,97 @@ class BoundingBox:
22
26
 
23
27
  @property
24
28
  def extrema(self) -> tuple[float, float, float, float]:
29
+ """
30
+ Returns annotation extrema in the form (xmin, xmax, ymin, ymax).
31
+ """
25
32
  return (self.xmin, self.xmax, self.ymin, self.ymax)
26
33
 
34
+ @property
35
+ def annotation(self) -> tuple[float, float, float, float]:
36
+ """
37
+ Returns the annotation's data representation.
38
+ """
39
+ return self.extrema
40
+
27
41
 
28
42
  @dataclass
29
43
  class Polygon:
30
44
  shape: ShapelyPolygon
31
- labels: list[tuple[str, str]]
45
+ labels: list[str]
32
46
  scores: list[float] = field(default_factory=list)
33
47
 
34
48
  def __post_init__(self):
35
49
  if not isinstance(self.shape, ShapelyPolygon):
36
50
  raise TypeError("shape must be of type shapely.geometry.Polygon.")
51
+ if self.shape.is_empty:
52
+ raise ValueError("Polygon is empty.")
53
+
54
+ if len(self.scores) == 0 and len(self.labels) != 1:
55
+ raise ValueError(
56
+ "Ground truths must be defined with no scores and a single label. If you meant to define a prediction, then please include one score for every label provided."
57
+ )
37
58
  if len(self.scores) > 0 and len(self.labels) != len(self.scores):
38
59
  raise ValueError(
39
60
  "If scores are defined, there must be a 1:1 pairing with labels."
40
61
  )
41
62
 
42
- def to_box(self) -> BoundingBox | None:
43
-
44
- if self.shape.is_empty:
45
- return None
46
-
63
+ @property
64
+ def extrema(self) -> tuple[float, float, float, float]:
65
+ """
66
+ Returns annotation extrema in the form (xmin, xmax, ymin, ymax).
67
+ """
47
68
  xmin, ymin, xmax, ymax = self.shape.bounds
69
+ return (xmin, xmax, ymin, ymax)
48
70
 
49
- return BoundingBox(
50
- xmin=xmin,
51
- xmax=xmax,
52
- ymin=ymin,
53
- ymax=ymax,
54
- labels=self.labels,
55
- scores=self.scores,
56
- )
71
+ @property
72
+ def annotation(self) -> ShapelyPolygon:
73
+ """
74
+ Returns the annotation's data representation.
75
+ """
76
+ return self.shape
57
77
 
58
78
 
59
79
  @dataclass
60
80
  class Bitmask:
61
81
  mask: NDArray[np.bool_]
62
- labels: list[tuple[str, str]]
82
+ labels: list[str]
63
83
  scores: list[float] = field(default_factory=list)
64
84
 
65
85
  def __post_init__(self):
86
+
87
+ if (
88
+ not isinstance(self.mask, np.ndarray)
89
+ or self.mask.dtype != np.bool_
90
+ ):
91
+ raise ValueError(
92
+ "Expected mask to be of type `NDArray[np.bool_]`."
93
+ )
94
+ elif not self.mask.any():
95
+ raise ValueError("Mask does not define any object instances.")
96
+
97
+ if len(self.scores) == 0 and len(self.labels) != 1:
98
+ raise ValueError(
99
+ "Ground truths must be defined with no scores and a single label. If you meant to define a prediction, then please include one score for every label provided."
100
+ )
66
101
  if len(self.scores) > 0 and len(self.labels) != len(self.scores):
67
102
  raise ValueError(
68
103
  "If scores are defined, there must be a 1:1 pairing with labels."
69
104
  )
70
105
 
71
- def to_box(self) -> BoundingBox | None:
72
-
73
- if not self.mask.any():
74
- return None
75
-
106
+ @property
107
+ def extrema(self) -> tuple[float, float, float, float]:
108
+ """
109
+ Returns annotation extrema in the form (xmin, xmax, ymin, ymax).
110
+ """
76
111
  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
- )
112
+ return (cols.min(), cols.max(), rows.min(), rows.max())
113
+
114
+ @property
115
+ def annotation(self) -> NDArray[np.bool_]:
116
+ """
117
+ Returns the annotation's data representation.
118
+ """
119
+ return self.mask
85
120
 
86
121
 
87
122
  @dataclass
@@ -1,8 +1,9 @@
1
1
  import numpy as np
2
+ import shapely
2
3
  from numpy.typing import NDArray
3
4
 
4
5
 
5
- def compute_bbox_iou(data: NDArray[np.floating]) -> NDArray[np.floating]:
6
+ def compute_bbox_iou(data: NDArray[np.float64]) -> NDArray[np.float64]:
6
7
  """
7
8
  Computes intersection-over-union (IoU) for axis-aligned bounding boxes.
8
9
 
@@ -23,26 +24,30 @@ def compute_bbox_iou(data: NDArray[np.floating]) -> NDArray[np.floating]:
23
24
 
24
25
  Parameters
25
26
  ----------
26
- data : NDArray[np.floating]
27
+ data : NDArray[np.float64]
27
28
  A sorted array of bounding box pairs.
28
29
 
29
30
  Returns
30
31
  -------
31
- NDArray[np.floating]
32
+ NDArray[np.float64]
32
33
  Computed IoU's.
33
34
  """
35
+ if data.size == 0:
36
+ return np.array([], dtype=np.float64)
37
+
38
+ n_pairs = data.shape[0]
34
39
 
35
40
  xmin1, xmax1, ymin1, ymax1 = (
36
- data[:, 0],
37
- data[:, 1],
38
- data[:, 2],
39
- data[:, 3],
41
+ data[:, 0, 0],
42
+ data[:, 0, 1],
43
+ data[:, 0, 2],
44
+ data[:, 0, 3],
40
45
  )
41
46
  xmin2, xmax2, ymin2, ymax2 = (
42
- data[:, 4],
43
- data[:, 5],
44
- data[:, 6],
45
- data[:, 7],
47
+ data[:, 1, 0],
48
+ data[:, 1, 1],
49
+ data[:, 1, 2],
50
+ data[:, 1, 3],
46
51
  )
47
52
 
48
53
  xmin = np.maximum(xmin1, xmin2)
@@ -59,15 +64,17 @@ def compute_bbox_iou(data: NDArray[np.floating]) -> NDArray[np.floating]:
59
64
 
60
65
  union_area = area1 + area2 - intersection_area
61
66
 
62
- iou = np.zeros(data.shape[0])
63
- valid_union_mask = union_area >= 1e-9
64
- iou[valid_union_mask] = (
65
- intersection_area[valid_union_mask] / union_area[valid_union_mask]
67
+ ious = np.zeros(n_pairs, dtype=np.float64)
68
+ np.divide(
69
+ intersection_area,
70
+ union_area,
71
+ where=union_area >= 1e-9,
72
+ out=ious,
66
73
  )
67
- return iou
74
+ return ious
68
75
 
69
76
 
70
- def compute_bitmask_iou(data: NDArray[np.floating]) -> NDArray[np.floating]:
77
+ def compute_bitmask_iou(data: NDArray[np.bool_]) -> NDArray[np.float64]:
71
78
  """
72
79
  Computes intersection-over-union (IoU) for bitmasks.
73
80
 
@@ -82,23 +89,41 @@ def compute_bitmask_iou(data: NDArray[np.floating]) -> NDArray[np.floating]:
82
89
 
83
90
  Parameters
84
91
  ----------
85
- data : NDArray[np.floating]
92
+ data : NDArray[np.float64]
86
93
  A sorted array of bitmask pairs.
87
94
 
88
95
  Returns
89
96
  -------
90
- NDArray[np.floating]
97
+ NDArray[np.float64]
91
98
  Computed IoU's.
92
99
  """
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
100
 
96
- return intersection_ / union_
101
+ if data.size == 0:
102
+ return np.array([], dtype=np.float64)
103
+
104
+ n_pairs = data.shape[0]
105
+ lhs = data[:, 0, :, :].reshape(n_pairs, -1)
106
+ rhs = data[:, 1, :, :].reshape(n_pairs, -1)
107
+
108
+ lhs_sum = lhs.sum(axis=1)
109
+ rhs_sum = rhs.sum(axis=1)
110
+
111
+ intersection_ = np.logical_and(lhs, rhs).sum(axis=1)
112
+ union_ = lhs_sum + rhs_sum - intersection_
113
+
114
+ ious = np.zeros(n_pairs, dtype=np.float64)
115
+ np.divide(
116
+ intersection_,
117
+ union_,
118
+ where=union_ >= 1e-9,
119
+ out=ious,
120
+ )
121
+ return ious
97
122
 
98
123
 
99
124
  def compute_polygon_iou(
100
- data: NDArray[np.floating],
101
- ) -> NDArray[np.floating]:
125
+ data: NDArray[np.float64],
126
+ ) -> NDArray[np.float64]:
102
127
  """
103
128
  Computes intersection-over-union (IoU) for shapely polygons.
104
129
 
@@ -113,31 +138,43 @@ def compute_polygon_iou(
113
138
 
114
139
  Parameters
115
140
  ----------
116
- data : NDArray[np.floating]
141
+ data : NDArray[np.float64]
117
142
  A sorted array of polygon pairs.
118
143
 
119
144
  Returns
120
145
  -------
121
- NDArray[np.floating]
146
+ NDArray[np.float64]
122
147
  Computed IoU's.
123
148
  """
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
149
 
134
- return intersection_ / union_
150
+ if data.size == 0:
151
+ return np.array([], dtype=np.float64)
152
+
153
+ n_pairs = data.shape[0]
154
+
155
+ lhs = data[:, 0]
156
+ rhs = data[:, 1]
157
+
158
+ intersections = shapely.intersection(lhs, rhs)
159
+ intersection_areas = shapely.area(intersections)
160
+
161
+ unions = shapely.union(lhs, rhs)
162
+ union_areas = shapely.area(unions)
163
+
164
+ ious = np.zeros(n_pairs, dtype=np.float64)
165
+ np.divide(
166
+ intersection_areas,
167
+ union_areas,
168
+ where=union_areas >= 1e-9,
169
+ out=ious,
170
+ )
171
+ return ious
135
172
 
136
173
 
137
174
  def _compute_ranked_pairs_for_datum(
138
- data: NDArray[np.floating],
175
+ data: NDArray[np.float64],
139
176
  label_metadata: NDArray[np.int32],
140
- ) -> NDArray[np.floating]:
177
+ ) -> NDArray[np.float64]:
141
178
  """
142
179
  Computes ranked pairs for a datum.
143
180
  """
@@ -177,9 +214,9 @@ def _compute_ranked_pairs_for_datum(
177
214
 
178
215
 
179
216
  def compute_ranked_pairs(
180
- data: list[NDArray[np.floating]],
217
+ data: list[NDArray[np.float64]],
181
218
  label_metadata: NDArray[np.int32],
182
- ) -> NDArray[np.floating]:
219
+ ) -> NDArray[np.float64]:
183
220
  """
184
221
  Performs pair ranking on input data.
185
222
 
@@ -197,14 +234,14 @@ def compute_ranked_pairs(
197
234
 
198
235
  Parameters
199
236
  ----------
200
- data : NDArray[np.floating]
237
+ data : NDArray[np.float64]
201
238
  A sorted array summarizing the IOU calculations of one or more pairs.
202
239
  label_metadata : NDArray[np.int32]
203
240
  An array containing metadata related to labels.
204
241
 
205
242
  Returns
206
243
  -------
207
- NDArray[np.floating]
244
+ NDArray[np.float64]
208
245
  A filtered array containing only ranked pairs.
209
246
  """
210
247
 
@@ -226,25 +263,25 @@ def compute_ranked_pairs(
226
263
 
227
264
 
228
265
  def compute_metrics(
229
- data: NDArray[np.floating],
266
+ data: NDArray[np.float64],
230
267
  label_metadata: NDArray[np.int32],
231
- iou_thresholds: NDArray[np.floating],
232
- score_thresholds: NDArray[np.floating],
268
+ iou_thresholds: NDArray[np.float64],
269
+ score_thresholds: NDArray[np.float64],
233
270
  ) -> tuple[
234
271
  tuple[
235
- NDArray[np.floating],
236
- NDArray[np.floating],
237
- NDArray[np.floating],
238
- NDArray[np.floating],
272
+ NDArray[np.float64],
273
+ NDArray[np.float64],
274
+ NDArray[np.float64],
275
+ float,
239
276
  ],
240
277
  tuple[
241
- NDArray[np.floating],
242
- NDArray[np.floating],
243
- NDArray[np.floating],
244
- NDArray[np.floating],
278
+ NDArray[np.float64],
279
+ NDArray[np.float64],
280
+ NDArray[np.float64],
281
+ float,
245
282
  ],
246
- NDArray[np.floating],
247
- NDArray[np.floating],
283
+ NDArray[np.float64],
284
+ NDArray[np.float64],
248
285
  ]:
249
286
  """
250
287
  Computes Object Detection metrics.
@@ -261,20 +298,20 @@ def compute_metrics(
261
298
 
262
299
  Parameters
263
300
  ----------
264
- data : NDArray[np.floating]
301
+ data : NDArray[np.float64]
265
302
  A sorted array summarizing the IOU calculations of one or more pairs.
266
303
  label_metadata : NDArray[np.int32]
267
304
  An array containing metadata related to labels.
268
- iou_thresholds : NDArray[np.floating]
305
+ iou_thresholds : NDArray[np.float64]
269
306
  A 1-D array containing IoU thresholds.
270
- score_thresholds : NDArray[np.floating]
307
+ score_thresholds : NDArray[np.float64]
271
308
  A 1-D array containing score thresholds.
272
309
 
273
310
  Returns
274
311
  -------
275
- tuple[NDArray, NDArray, NDArray NDArray]
312
+ tuple[NDArray, NDArray, NDArray, float]
276
313
  Average Precision results.
277
- tuple[NDArray, NDArray, NDArray NDArray]
314
+ tuple[NDArray, NDArray, NDArray, float]
278
315
  Average Recall results.
279
316
  np.ndarray
280
317
  Precision, Recall, TP, FP, FN, F1 Score, Accuracy.
@@ -287,6 +324,11 @@ def compute_metrics(
287
324
  n_ious = iou_thresholds.shape[0]
288
325
  n_scores = score_thresholds.shape[0]
289
326
 
327
+ if n_ious == 0:
328
+ raise ValueError("At least one IoU threshold must be passed.")
329
+ elif n_scores == 0:
330
+ raise ValueError("At least one score threshold must be passed.")
331
+
290
332
  average_precision = np.zeros((n_ious, n_labels))
291
333
  average_recall = np.zeros((n_scores, n_labels))
292
334
  counts = np.zeros((n_ious, n_scores, n_labels, 7))
@@ -450,26 +492,23 @@ def compute_metrics(
450
492
  average_precision = average_precision / 101.0
451
493
 
452
494
  # calculate average recall
453
- average_recall /= n_ious
495
+ average_recall = average_recall / n_ious
454
496
 
455
497
  # calculate mAP and mAR
456
- label_key_mapping = label_metadata[unique_pd_labels, 2]
457
- label_keys = np.unique(label_metadata[:, 2])
458
- mAP = np.ones((n_ious, label_keys.shape[0])) * -1.0
459
- mAR = np.ones((n_scores, label_keys.shape[0])) * -1.0
460
- for key in np.unique(label_key_mapping):
461
- labels = unique_pd_labels[label_key_mapping == key]
462
- key_idx = int(key)
463
- mAP[:, key_idx] = average_precision[:, labels].mean(axis=1)
464
- mAR[:, key_idx] = average_recall[:, labels].mean(axis=1)
465
-
466
- # calculate AP and mAP averaged over iou thresholds
498
+ if unique_pd_labels.size > 0:
499
+ mAP = average_precision[:, unique_pd_labels].mean(axis=1)
500
+ mAR = average_recall[:, unique_pd_labels].mean(axis=1)
501
+ else:
502
+ mAP = np.zeros(n_ious, dtype=np.float64)
503
+ mAR = np.zeros(n_scores, dtype=np.float64)
504
+
505
+ # calculate AR and AR averaged over thresholds
467
506
  APAveragedOverIoUs = average_precision.mean(axis=0)
468
- mAPAveragedOverIoUs = mAP.mean(axis=0)
507
+ ARAveragedOverScores = average_recall.mean(axis=0)
469
508
 
470
- # calculate AR and mAR averaged over score thresholds
471
- ARAveragedOverIoUs = average_recall.mean(axis=0)
472
- mARAveragedOverIoUs = mAR.mean(axis=0)
509
+ # calculate mAP and mAR averaged over thresholds
510
+ mAPAveragedOverIoUs = mAP.mean(axis=0)
511
+ mARAveragedOverScores = mAR.mean(axis=0)
473
512
 
474
513
  ap_results = (
475
514
  average_precision,
@@ -480,8 +519,8 @@ def compute_metrics(
480
519
  ar_results = (
481
520
  average_recall,
482
521
  mAR,
483
- ARAveragedOverIoUs,
484
- mARAveragedOverIoUs,
522
+ ARAveragedOverScores,
523
+ mARAveragedOverScores,
485
524
  )
486
525
 
487
526
  return (
@@ -493,16 +532,16 @@ def compute_metrics(
493
532
 
494
533
 
495
534
  def _count_with_examples(
496
- data: NDArray[np.floating],
535
+ data: NDArray[np.float64],
497
536
  unique_idx: int | list[int],
498
537
  label_idx: int | list[int],
499
- ) -> tuple[NDArray[np.floating], NDArray[np.int32], NDArray[np.int32]]:
538
+ ) -> tuple[NDArray[np.float64], NDArray[np.int32], NDArray[np.int32]]:
500
539
  """
501
540
  Helper function for counting occurences of unique detailed pairs.
502
541
 
503
542
  Parameters
504
543
  ----------
505
- data : NDArray[np.floating]
544
+ data : NDArray[np.float64]
506
545
  A masked portion of a detailed pairs array.
507
546
  unique_idx : int | list[int]
508
547
  The index or indices upon which uniqueness is constrained.
@@ -511,7 +550,7 @@ def _count_with_examples(
511
550
 
512
551
  Returns
513
552
  -------
514
- NDArray[np.floating]
553
+ NDArray[np.float64]
515
554
  Examples drawn from the data input.
516
555
  NDArray[np.int32]
517
556
  Unique label indices.
@@ -531,13 +570,12 @@ def _count_with_examples(
531
570
 
532
571
 
533
572
  def compute_confusion_matrix(
534
- data: NDArray[np.floating],
573
+ data: NDArray[np.float64],
535
574
  label_metadata: NDArray[np.int32],
536
- iou_thresholds: NDArray[np.floating],
537
- score_thresholds: NDArray[np.floating],
575
+ iou_thresholds: NDArray[np.float64],
576
+ score_thresholds: NDArray[np.float64],
538
577
  n_examples: int,
539
- ) -> tuple[NDArray[np.floating], NDArray[np.floating], NDArray[np.int32]]:
540
-
578
+ ) -> tuple[NDArray[np.float64], NDArray[np.float64], NDArray[np.int32]]:
541
579
  """
542
580
  Compute detailed counts.
543
581
 
@@ -553,22 +591,22 @@ def compute_confusion_matrix(
553
591
 
554
592
  Parameters
555
593
  ----------
556
- data : NDArray[np.floating]
594
+ data : NDArray[np.float64]
557
595
  A sorted array summarizing the IOU calculations of one or more pairs.
558
596
  label_metadata : NDArray[np.int32]
559
597
  An array containing metadata related to labels.
560
- iou_thresholds : NDArray[np.floating]
598
+ iou_thresholds : NDArray[np.float64]
561
599
  A 1-D array containing IoU thresholds.
562
- score_thresholds : NDArray[np.floating]
600
+ score_thresholds : NDArray[np.float64]
563
601
  A 1-D array containing score thresholds.
564
602
  n_examples : int
565
603
  The maximum number of examples to return per count.
566
604
 
567
605
  Returns
568
606
  -------
569
- NDArray[np.floating]
607
+ NDArray[np.float64]
570
608
  Confusion matrix.
571
- NDArray[np.floating]
609
+ NDArray[np.float64]
572
610
  Hallucinations.
573
611
  NDArray[np.int32]
574
612
  Missing Predictions.