scale-nucleus 0.12b1__py3-none-any.whl → 0.14.14b0__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.
- cli/slices.py +14 -28
- nucleus/__init__.py +211 -18
- nucleus/annotation.py +28 -5
- nucleus/connection.py +9 -1
- nucleus/constants.py +9 -3
- nucleus/dataset.py +197 -59
- nucleus/dataset_item.py +11 -1
- nucleus/job.py +1 -1
- nucleus/metrics/__init__.py +2 -1
- nucleus/metrics/base.py +34 -56
- nucleus/metrics/categorization_metrics.py +6 -2
- nucleus/metrics/cuboid_utils.py +4 -6
- nucleus/metrics/errors.py +4 -0
- nucleus/metrics/filtering.py +369 -19
- nucleus/metrics/polygon_utils.py +3 -3
- nucleus/metrics/segmentation_loader.py +30 -0
- nucleus/metrics/segmentation_metrics.py +256 -195
- nucleus/metrics/segmentation_to_poly_metrics.py +229 -105
- nucleus/metrics/segmentation_utils.py +239 -8
- nucleus/model.py +66 -10
- nucleus/model_run.py +1 -1
- nucleus/{shapely_not_installed.py → package_not_installed.py} +3 -3
- nucleus/payload_constructor.py +4 -0
- nucleus/prediction.py +6 -3
- nucleus/scene.py +7 -0
- nucleus/slice.py +160 -16
- nucleus/utils.py +51 -12
- nucleus/validate/__init__.py +1 -0
- nucleus/validate/client.py +57 -8
- nucleus/validate/constants.py +1 -0
- nucleus/validate/data_transfer_objects/eval_function.py +22 -0
- nucleus/validate/data_transfer_objects/scenario_test_evaluations.py +13 -5
- nucleus/validate/eval_functions/available_eval_functions.py +33 -20
- nucleus/validate/eval_functions/config_classes/segmentation.py +2 -46
- nucleus/validate/scenario_test.py +71 -13
- nucleus/validate/scenario_test_evaluation.py +21 -21
- nucleus/validate/utils.py +1 -1
- {scale_nucleus-0.12b1.dist-info → scale_nucleus-0.14.14b0.dist-info}/LICENSE +0 -0
- {scale_nucleus-0.12b1.dist-info → scale_nucleus-0.14.14b0.dist-info}/METADATA +13 -11
- {scale_nucleus-0.12b1.dist-info → scale_nucleus-0.14.14b0.dist-info}/RECORD +42 -41
- {scale_nucleus-0.12b1.dist-info → scale_nucleus-0.14.14b0.dist-info}/WHEEL +1 -1
- {scale_nucleus-0.12b1.dist-info → scale_nucleus-0.14.14b0.dist-info}/entry_points.txt +0 -0
@@ -1,16 +1,21 @@
|
|
1
1
|
import abc
|
2
|
+
import logging
|
3
|
+
from enum import Enum
|
2
4
|
from typing import List, Optional, Union
|
3
5
|
|
4
|
-
import fsspec
|
5
6
|
import numpy as np
|
6
|
-
from PIL import Image
|
7
|
-
from s3fs import S3FileSystem
|
8
7
|
|
9
|
-
from nucleus.annotation import AnnotationList
|
8
|
+
from nucleus.annotation import AnnotationList, SegmentationAnnotation
|
10
9
|
from nucleus.metrics.base import MetricResult
|
11
|
-
from nucleus.metrics.filtering import
|
10
|
+
from nucleus.metrics.filtering import (
|
11
|
+
ListOfAndFilters,
|
12
|
+
ListOfOrAndFilters,
|
13
|
+
apply_filters,
|
14
|
+
)
|
12
15
|
from nucleus.metrics.segmentation_utils import (
|
13
16
|
instance_mask_to_polys,
|
17
|
+
rasterize_polygons_to_segmentation_mask,
|
18
|
+
setup_iou_thresholds,
|
14
19
|
transform_poly_codes_to_poly_preds,
|
15
20
|
)
|
16
21
|
from nucleus.prediction import PredictionList
|
@@ -19,20 +24,25 @@ from .base import Metric, ScalarResult
|
|
19
24
|
from .polygon_metrics import (
|
20
25
|
PolygonAveragePrecision,
|
21
26
|
PolygonIOU,
|
22
|
-
PolygonMAP,
|
23
27
|
PolygonPrecision,
|
24
28
|
PolygonRecall,
|
25
29
|
)
|
30
|
+
from .segmentation_loader import (
|
31
|
+
DummyLoader,
|
32
|
+
InMemoryLoader,
|
33
|
+
SegmentationMaskLoader,
|
34
|
+
)
|
35
|
+
from .segmentation_metrics import (
|
36
|
+
SegmentationIOU,
|
37
|
+
SegmentationMAP,
|
38
|
+
SegmentationPrecision,
|
39
|
+
SegmentationRecall,
|
40
|
+
)
|
26
41
|
|
27
42
|
|
28
|
-
class
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
def fetch(self, url: str):
|
33
|
-
with self.fs.open(url) as fh:
|
34
|
-
img = Image.open(fh)
|
35
|
-
return img
|
43
|
+
class SegToPolyMode(str, Enum):
|
44
|
+
GENERATE_GT_FROM_POLY = "gt_from_poly"
|
45
|
+
GENERATE_PRED_POLYS_FROM_MASK = "gt_from_poly"
|
36
46
|
|
37
47
|
|
38
48
|
class SegmentationMaskToPolyMetric(Metric):
|
@@ -46,6 +56,7 @@ class SegmentationMaskToPolyMetric(Metric):
|
|
46
56
|
prediction_filters: Optional[
|
47
57
|
Union[ListOfOrAndFilters, ListOfAndFilters]
|
48
58
|
] = None,
|
59
|
+
mode: SegToPolyMode = SegToPolyMode.GENERATE_GT_FROM_POLY,
|
49
60
|
):
|
50
61
|
"""Initializes PolygonMetric abstract object.
|
51
62
|
|
@@ -71,11 +82,15 @@ class SegmentationMaskToPolyMetric(Metric):
|
|
71
82
|
(AND), forming a more selective `and` multiple field predicate.
|
72
83
|
Finally, the most outer list combines these filters as a disjunction (OR).
|
73
84
|
"""
|
74
|
-
|
85
|
+
# Since segmentation annotations are very different from everything else we can't rely on the upper filtering
|
86
|
+
super().__init__(None, None)
|
87
|
+
self._annotation_filters = annotation_filters
|
88
|
+
self._prediction_filters = prediction_filters
|
75
89
|
self.enforce_label_match = enforce_label_match
|
76
90
|
assert 0 <= confidence_threshold <= 1
|
77
91
|
self.confidence_threshold = confidence_threshold
|
78
|
-
self.loader =
|
92
|
+
self.loader: SegmentationMaskLoader = DummyLoader()
|
93
|
+
self.mode = mode
|
79
94
|
|
80
95
|
def call_metric(
|
81
96
|
self, annotations: AnnotationList, predictions: PredictionList
|
@@ -88,22 +103,92 @@ class SegmentationMaskToPolyMetric(Metric):
|
|
88
103
|
if predictions.segmentation_predictions
|
89
104
|
else None
|
90
105
|
)
|
91
|
-
|
92
|
-
|
93
|
-
np.asarray(pred_img)
|
94
|
-
) # typing: ignore
|
95
|
-
code_to_label = {s.index: s.label for s in prediction.annotations}
|
96
|
-
poly_predictions = transform_poly_codes_to_poly_preds(
|
97
|
-
prediction.reference_id, pred_value, pred_polys, code_to_label
|
106
|
+
annotations.polygon_annotations = apply_filters(
|
107
|
+
annotations.polygon_annotations, self._annotation_filters # type: ignore
|
98
108
|
)
|
99
|
-
|
100
|
-
annotations,
|
109
|
+
annotations.box_annotations = apply_filters(
|
110
|
+
annotations.box_annotations, self._annotation_filters # type: ignore
|
111
|
+
)
|
112
|
+
predictions.segmentation_predictions = apply_filters(
|
113
|
+
predictions.segmentation_predictions, self._prediction_filters # type: ignore
|
114
|
+
)
|
115
|
+
if prediction:
|
116
|
+
if self.mode == SegToPolyMode.GENERATE_GT_FROM_POLY:
|
117
|
+
pred_img = self.loader.fetch(prediction.mask_url)
|
118
|
+
ann_img, segments = rasterize_polygons_to_segmentation_mask(
|
119
|
+
annotations.polygon_annotations
|
120
|
+
+ annotations.box_annotations, # type:ignore
|
121
|
+
pred_img.shape,
|
122
|
+
)
|
123
|
+
# TODO: apply Segmentation filters after?
|
124
|
+
annotations.segmentation_annotations = [
|
125
|
+
SegmentationAnnotation(
|
126
|
+
"__no_url",
|
127
|
+
annotations=segments,
|
128
|
+
reference_id=annotations.polygon_annotations[
|
129
|
+
0
|
130
|
+
].reference_id,
|
131
|
+
)
|
132
|
+
]
|
133
|
+
return self.call_segmentation_metric(
|
134
|
+
annotations,
|
135
|
+
np.asarray(ann_img),
|
136
|
+
predictions,
|
137
|
+
np.asarray(pred_img),
|
138
|
+
)
|
139
|
+
elif self.mode == SegToPolyMode.GENERATE_PRED_POLYS_FROM_MASK:
|
140
|
+
pred_img = self.loader.fetch(prediction.mask_url)
|
141
|
+
pred_value, pred_polys = instance_mask_to_polys(
|
142
|
+
np.asarray(pred_img)
|
143
|
+
) # typing: ignore
|
144
|
+
code_to_label = {
|
145
|
+
s.index: s.label for s in prediction.annotations
|
146
|
+
}
|
147
|
+
poly_predictions = transform_poly_codes_to_poly_preds(
|
148
|
+
prediction.reference_id,
|
149
|
+
pred_value,
|
150
|
+
pred_polys,
|
151
|
+
code_to_label,
|
152
|
+
)
|
153
|
+
return self.call_poly_metric(
|
154
|
+
annotations,
|
155
|
+
PredictionList(polygon_predictions=poly_predictions),
|
156
|
+
)
|
157
|
+
else:
|
158
|
+
raise RuntimeError(
|
159
|
+
f"Misonconfigured class. Got mode '{self.mode}', expected one of {list(SegToPolyMode)}"
|
160
|
+
)
|
161
|
+
else:
|
162
|
+
return ScalarResult(0, weight=0)
|
163
|
+
|
164
|
+
def call_segmentation_metric(
|
165
|
+
self,
|
166
|
+
annotations: AnnotationList,
|
167
|
+
ann_img: np.ndarray,
|
168
|
+
predictions: PredictionList,
|
169
|
+
pred_img: np.ndarray,
|
170
|
+
):
|
171
|
+
metric = self.configure_metric()
|
172
|
+
metric.loader = InMemoryLoader(
|
173
|
+
{
|
174
|
+
annotations.segmentation_annotations[0].mask_url: ann_img,
|
175
|
+
predictions.segmentation_predictions[0].mask_url: pred_img,
|
176
|
+
}
|
101
177
|
)
|
178
|
+
return metric(annotations, predictions)
|
102
179
|
|
103
|
-
@abc.abstractmethod
|
104
180
|
def call_poly_metric(
|
105
181
|
self, annotations: AnnotationList, predictions: PredictionList
|
106
182
|
):
|
183
|
+
metric = self.configure_metric()
|
184
|
+
return metric(annotations, predictions)
|
185
|
+
|
186
|
+
def aggregate_score(self, results: List[MetricResult]) -> ScalarResult:
|
187
|
+
metric = self.configure_metric()
|
188
|
+
return metric.aggregate_score(results) # type: ignore
|
189
|
+
|
190
|
+
@abc.abstractmethod
|
191
|
+
def configure_metric(self):
|
107
192
|
pass
|
108
193
|
|
109
194
|
|
@@ -119,6 +204,7 @@ class SegmentationToPolyIOU(SegmentationMaskToPolyMetric):
|
|
119
204
|
prediction_filters: Optional[
|
120
205
|
Union[ListOfOrAndFilters, ListOfAndFilters]
|
121
206
|
] = None,
|
207
|
+
mode: SegToPolyMode = SegToPolyMode.GENERATE_GT_FROM_POLY,
|
122
208
|
):
|
123
209
|
"""Initializes PolygonIOU object.
|
124
210
|
|
@@ -154,22 +240,25 @@ class SegmentationToPolyIOU(SegmentationMaskToPolyMetric):
|
|
154
240
|
confidence_threshold,
|
155
241
|
annotation_filters,
|
156
242
|
prediction_filters,
|
157
|
-
|
158
|
-
self.metric = PolygonIOU(
|
159
|
-
self.enforce_label_match,
|
160
|
-
self.iou_threshold,
|
161
|
-
self.confidence_threshold,
|
162
|
-
self.annotation_filters,
|
163
|
-
self.prediction_filters,
|
243
|
+
mode,
|
164
244
|
)
|
165
245
|
|
166
|
-
def
|
167
|
-
self
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
246
|
+
def configure_metric(self):
|
247
|
+
if self.mode == SegToPolyMode.GENERATE_GT_FROM_POLY:
|
248
|
+
metric = SegmentationIOU(
|
249
|
+
self.annotation_filters,
|
250
|
+
self.prediction_filters,
|
251
|
+
self.iou_threshold,
|
252
|
+
)
|
253
|
+
else:
|
254
|
+
metric = PolygonIOU(
|
255
|
+
self.enforce_label_match,
|
256
|
+
self.iou_threshold,
|
257
|
+
self.confidence_threshold,
|
258
|
+
self.annotation_filters,
|
259
|
+
self.prediction_filters,
|
260
|
+
)
|
261
|
+
return metric
|
173
262
|
|
174
263
|
|
175
264
|
class SegmentationToPolyPrecision(SegmentationMaskToPolyMetric):
|
@@ -184,6 +273,7 @@ class SegmentationToPolyPrecision(SegmentationMaskToPolyMetric):
|
|
184
273
|
prediction_filters: Optional[
|
185
274
|
Union[ListOfOrAndFilters, ListOfAndFilters]
|
186
275
|
] = None,
|
276
|
+
mode: SegToPolyMode = SegToPolyMode.GENERATE_GT_FROM_POLY,
|
187
277
|
):
|
188
278
|
"""Initializes SegmentationToPolyPrecision object.
|
189
279
|
|
@@ -219,22 +309,25 @@ class SegmentationToPolyPrecision(SegmentationMaskToPolyMetric):
|
|
219
309
|
confidence_threshold,
|
220
310
|
annotation_filters,
|
221
311
|
prediction_filters,
|
312
|
+
mode,
|
222
313
|
)
|
223
|
-
self.metric = PolygonPrecision(
|
224
|
-
self.enforce_label_match,
|
225
|
-
self.iou_threshold,
|
226
|
-
self.confidence_threshold,
|
227
|
-
self.annotation_filters,
|
228
|
-
self.prediction_filters,
|
229
|
-
)
|
230
|
-
|
231
|
-
def call_poly_metric(
|
232
|
-
self, annotations: AnnotationList, predictions: PredictionList
|
233
|
-
):
|
234
|
-
return self.metric(annotations, predictions)
|
235
314
|
|
236
|
-
def
|
237
|
-
|
315
|
+
def configure_metric(self):
|
316
|
+
if self.mode == SegToPolyMode.GENERATE_GT_FROM_POLY:
|
317
|
+
metric = SegmentationPrecision(
|
318
|
+
self.annotation_filters,
|
319
|
+
self.prediction_filters,
|
320
|
+
self.iou_threshold,
|
321
|
+
)
|
322
|
+
else:
|
323
|
+
metric = PolygonPrecision(
|
324
|
+
self.enforce_label_match,
|
325
|
+
self.iou_threshold,
|
326
|
+
self.confidence_threshold,
|
327
|
+
self.annotation_filters,
|
328
|
+
self.prediction_filters,
|
329
|
+
)
|
330
|
+
return metric
|
238
331
|
|
239
332
|
|
240
333
|
class SegmentationToPolyRecall(SegmentationMaskToPolyMetric):
|
@@ -284,6 +377,7 @@ class SegmentationToPolyRecall(SegmentationMaskToPolyMetric):
|
|
284
377
|
prediction_filters: Optional[
|
285
378
|
Union[ListOfOrAndFilters, ListOfAndFilters]
|
286
379
|
] = None,
|
380
|
+
mode: SegToPolyMode = SegToPolyMode.GENERATE_GT_FROM_POLY,
|
287
381
|
):
|
288
382
|
"""Initializes PolygonRecall object.
|
289
383
|
|
@@ -317,24 +411,27 @@ class SegmentationToPolyRecall(SegmentationMaskToPolyMetric):
|
|
317
411
|
super().__init__(
|
318
412
|
enforce_label_match,
|
319
413
|
confidence_threshold,
|
320
|
-
annotation_filters
|
321
|
-
prediction_filters
|
322
|
-
|
323
|
-
self.metric = PolygonRecall(
|
324
|
-
self.enforce_label_match,
|
325
|
-
self.iou_threshold,
|
326
|
-
self.confidence_threshold,
|
327
|
-
self.annotation_filters,
|
328
|
-
self.prediction_filters,
|
414
|
+
annotation_filters,
|
415
|
+
prediction_filters,
|
416
|
+
mode,
|
329
417
|
)
|
330
418
|
|
331
|
-
def
|
332
|
-
self
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
419
|
+
def configure_metric(self):
|
420
|
+
if self.mode == SegToPolyMode.GENERATE_GT_FROM_POLY:
|
421
|
+
metric = SegmentationRecall(
|
422
|
+
self.annotation_filters,
|
423
|
+
self.prediction_filters,
|
424
|
+
self.iou_threshold,
|
425
|
+
)
|
426
|
+
else:
|
427
|
+
metric = PolygonRecall(
|
428
|
+
self.enforce_label_match,
|
429
|
+
self.iou_threshold,
|
430
|
+
self.confidence_threshold,
|
431
|
+
self.annotation_filters,
|
432
|
+
self.prediction_filters,
|
433
|
+
)
|
434
|
+
return metric
|
338
435
|
|
339
436
|
|
340
437
|
class SegmentationToPolyAveragePrecision(SegmentationMaskToPolyMetric):
|
@@ -383,6 +480,7 @@ class SegmentationToPolyAveragePrecision(SegmentationMaskToPolyMetric):
|
|
383
480
|
prediction_filters: Optional[
|
384
481
|
Union[ListOfOrAndFilters, ListOfAndFilters]
|
385
482
|
] = None,
|
483
|
+
mode: SegToPolyMode = SegToPolyMode.GENERATE_GT_FROM_POLY,
|
386
484
|
):
|
387
485
|
"""Initializes PolygonRecall object.
|
388
486
|
|
@@ -418,20 +516,23 @@ class SegmentationToPolyAveragePrecision(SegmentationMaskToPolyMetric):
|
|
418
516
|
annotation_filters=annotation_filters,
|
419
517
|
prediction_filters=prediction_filters,
|
420
518
|
)
|
421
|
-
self.metric = PolygonAveragePrecision(
|
422
|
-
self.label,
|
423
|
-
self.iou_threshold,
|
424
|
-
self.annotation_filters,
|
425
|
-
self.prediction_filters,
|
426
|
-
)
|
427
519
|
|
428
|
-
def
|
429
|
-
self
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
520
|
+
def configure_metric(self):
|
521
|
+
if self.mode == SegToPolyMode.GENERATE_GT_FROM_POLY:
|
522
|
+
# TODO(gunnar): Add a label filter
|
523
|
+
metric = SegmentationPrecision(
|
524
|
+
self.annotation_filters,
|
525
|
+
self.prediction_filters,
|
526
|
+
self.iou_threshold,
|
527
|
+
)
|
528
|
+
else:
|
529
|
+
metric = PolygonAveragePrecision(
|
530
|
+
self.label,
|
531
|
+
self.iou_threshold,
|
532
|
+
self.annotation_filters,
|
533
|
+
self.prediction_filters,
|
534
|
+
)
|
535
|
+
return metric
|
435
536
|
|
436
537
|
|
437
538
|
class SegmentationToPolyMAP(SegmentationMaskToPolyMetric):
|
@@ -472,18 +573,20 @@ class SegmentationToPolyMAP(SegmentationMaskToPolyMetric):
|
|
472
573
|
# TODO: Remove defaults once these are surfaced more cleanly to users.
|
473
574
|
def __init__(
|
474
575
|
self,
|
475
|
-
iou_threshold: float =
|
576
|
+
iou_threshold: float = -1,
|
577
|
+
iou_thresholds: Union[List[float], str] = "coco",
|
476
578
|
annotation_filters: Optional[
|
477
579
|
Union[ListOfOrAndFilters, ListOfAndFilters]
|
478
580
|
] = None,
|
479
581
|
prediction_filters: Optional[
|
480
582
|
Union[ListOfOrAndFilters, ListOfAndFilters]
|
481
583
|
] = None,
|
584
|
+
mode: SegToPolyMode = SegToPolyMode.GENERATE_GT_FROM_POLY,
|
482
585
|
):
|
483
586
|
"""Initializes PolygonRecall object.
|
484
587
|
|
485
588
|
Args:
|
486
|
-
|
589
|
+
iou_thresholds: IOU thresholds to check AP at
|
487
590
|
annotation_filters: Filter predicates. Allowed formats are:
|
488
591
|
ListOfAndFilters where each Filter forms a chain of AND predicates.
|
489
592
|
or
|
@@ -503,26 +606,47 @@ class SegmentationToPolyMAP(SegmentationMaskToPolyMetric):
|
|
503
606
|
(AND), forming a more selective `and` multiple field predicate.
|
504
607
|
Finally, the most outer list combines these filters as a disjunction (OR).
|
505
608
|
"""
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
609
|
+
if iou_threshold:
|
610
|
+
logging.warning(
|
611
|
+
"Got deprecated parameter 'iou_threshold'. Ignoring it."
|
612
|
+
)
|
613
|
+
self.iou_thresholds = setup_iou_thresholds(iou_thresholds)
|
510
614
|
super().__init__(
|
511
|
-
|
512
|
-
confidence_threshold=0,
|
513
|
-
annotation_filters=annotation_filters,
|
514
|
-
prediction_filters=prediction_filters,
|
515
|
-
)
|
516
|
-
self.metric = PolygonMAP(
|
517
|
-
self.iou_threshold,
|
518
|
-
self.annotation_filters,
|
519
|
-
self.prediction_filters,
|
615
|
+
False, 0, annotation_filters, prediction_filters, mode
|
520
616
|
)
|
521
617
|
|
522
|
-
def
|
523
|
-
self
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
618
|
+
def configure_metric(self):
|
619
|
+
if self.mode == SegToPolyMode.GENERATE_GT_FROM_POLY:
|
620
|
+
# TODO(gunnar): Add a label filter
|
621
|
+
metric = SegmentationMAP(
|
622
|
+
self.annotation_filters,
|
623
|
+
self.prediction_filters,
|
624
|
+
self.iou_thresholds,
|
625
|
+
)
|
626
|
+
else:
|
627
|
+
|
628
|
+
def patched_average_precision(annotations, predictions):
|
629
|
+
ap_per_threshold = []
|
630
|
+
labels = [p.label for p in predictions.polygon_predictions]
|
631
|
+
for threshold in self.iou_thresholds:
|
632
|
+
ap_per_label = []
|
633
|
+
for label in labels:
|
634
|
+
call_metric = PolygonAveragePrecision(
|
635
|
+
label,
|
636
|
+
iou_threshold=threshold,
|
637
|
+
annotation_filters=self.annotation_filters,
|
638
|
+
prediction_filters=self.prediction_filters,
|
639
|
+
)
|
640
|
+
result = call_metric(annotations, predictions)
|
641
|
+
ap_per_label.append(result.value) # type: ignore
|
642
|
+
ap_per_threshold = np.mean(ap_per_label)
|
643
|
+
|
644
|
+
thresholds = np.concatenate([[0], self.iou_thresholds, [1]])
|
645
|
+
steps = np.diff(thresholds)
|
646
|
+
mean_ap = (
|
647
|
+
np.array(ap_per_threshold + [ap_per_threshold[-1]]) * steps
|
648
|
+
).sum()
|
649
|
+
return ScalarResult(mean_ap)
|
650
|
+
|
651
|
+
metric = patched_average_precision
|
652
|
+
return metric
|