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/__init__.py
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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.
|
|
74
|
-
label_metadata: np.
|
|
75
|
-
) -> np.
|
|
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.
|
|
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
|
|
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
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
-
|
|
158
|
-
-
|
|
221
|
+
-ranked_pairs[:, 3], # iou
|
|
222
|
+
-ranked_pairs[:, 6], # score
|
|
159
223
|
)
|
|
160
224
|
)
|
|
161
|
-
return
|
|
225
|
+
return ranked_pairs[indices]
|
|
162
226
|
|
|
163
227
|
|
|
164
228
|
def compute_metrics(
|
|
165
|
-
data: np.
|
|
166
|
-
label_metadata: np.
|
|
167
|
-
iou_thresholds: np.
|
|
168
|
-
score_thresholds: np.
|
|
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
|
|
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.
|
|
433
|
-
label_metadata: np.
|
|
434
|
-
iou_thresholds: np.
|
|
435
|
-
score_thresholds: np.
|
|
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.
|
|
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
|
|
521
|
+
Index 4 * n_samples + 2 - False Positive Hallucination Count
|
|
458
522
|
... Datum ID Examples
|
|
459
|
-
Index
|
|
523
|
+
Index 6 * n_samples + 3 - False Negative Misclassification Count
|
|
460
524
|
... Datum ID Examples
|
|
461
|
-
Index
|
|
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
|
|
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.
|
|
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 =
|
|
490
|
-
fp_halluc_idx =
|
|
491
|
-
fn_misclf_idx =
|
|
492
|
-
fn_misprd_idx =
|
|
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(
|
|
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
|
-
|
|
578
|
+
groundtruths_passing_ious = np.unique(groundtruths[mask_iou], axis=0)
|
|
513
579
|
mask_groundtruths_with_passing_ious = (
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
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
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
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
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
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 =
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
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,
|