@@ -187,6 +187,9 @@ class MeanAveragePrecision(Metric):
187
187
IoU thresholds, ``K`` is the number of classes, ``A`` is the number of areas and ``M`` is the number
188
188
of max detections per image.
189
189
190
+ average:
191
+ Method for averaging scores over labels. Choose between "``macro``"" and "``micro``". Default is "macro"
192
+
190
193
kwargs: Additional keyword arguments, see :ref:`Metric kwargs` for more info.
191
194
192
195
Raises:
@@ -329,6 +332,7 @@ def __init__(
329
332
max_detection_thresholds : Optional [List [int ]] = None ,
330
333
class_metrics : bool = False ,
331
334
extended_summary : bool = False ,
335
+ average : Literal ["macro" , "micro" ] = "macro" ,
332
336
** kwargs : Any ,
333
337
) -> None :
334
338
super ().__init__ (** kwargs )
@@ -379,6 +383,10 @@ def __init__(
379
383
raise ValueError ("Expected argument `extended_summary` to be a boolean" )
380
384
self .extended_summary = extended_summary
381
385
386
+ if average not in ("macro" , "micro" ):
387
+ raise ValueError (f"Expected argument `average` to be one of ('macro', 'micro') but got { average } " )
388
+ self .average = average
389
+
382
390
self .add_state ("detection_box" , default = [], dist_reduce_fx = None )
383
391
self .add_state ("detection_mask" , default = [], dist_reduce_fx = None )
384
392
self .add_state ("detection_scores" , default = [], dist_reduce_fx = None )
@@ -434,27 +442,10 @@ def update(self, preds: List[Dict[str, Tensor]], target: List[Dict[str, Tensor]]
434
442
435
443
def compute (self ) -> dict :
436
444
"""Computes the metric."""
437
- coco_target , coco_preds = COCO (), COCO ()
438
-
439
- coco_target .dataset = self ._get_coco_format (
440
- labels = self .groundtruth_labels ,
441
- boxes = self .groundtruth_box if len (self .groundtruth_box ) > 0 else None ,
442
- masks = self .groundtruth_mask if len (self .groundtruth_mask ) > 0 else None ,
443
- crowds = self .groundtruth_crowds ,
444
- area = self .groundtruth_area ,
445
- )
446
- coco_preds .dataset = self ._get_coco_format (
447
- labels = self .detection_labels ,
448
- boxes = self .detection_box if len (self .detection_box ) > 0 else None ,
449
- masks = self .detection_mask if len (self .detection_mask ) > 0 else None ,
450
- scores = self .detection_scores ,
451
- )
445
+ coco_preds , coco_target = self ._get_coco_datasets (average = self .average )
452
446
453
447
result_dict = {}
454
448
with contextlib .redirect_stdout (io .StringIO ()):
455
- coco_target .createIndex ()
456
- coco_preds .createIndex ()
457
-
458
449
for i_type in self .iou_type :
459
450
prefix = "" if len (self .iou_type ) == 1 else f"{ i_type } _"
460
451
if len (self .iou_type ) > 1 :
@@ -487,6 +478,15 @@ def compute(self) -> dict:
487
478
488
479
# if class mode is enabled, evaluate metrics per class
489
480
if self .class_metrics :
481
+ if self .average == "micro" :
482
+ # since micro averaging have all the data in one class, we need to reinitialize the coco_eval
483
+ # object in macro mode to get the per class stats
484
+ coco_preds , coco_target = self ._get_coco_datasets (average = "macro" )
485
+ coco_eval = COCOeval (coco_target , coco_preds , iouType = i_type )
486
+ coco_eval .params .iouThrs = np .array (self .iou_thresholds , dtype = np .float64 )
487
+ coco_eval .params .recThrs = np .array (self .rec_thresholds , dtype = np .float64 )
488
+ coco_eval .params .maxDets = self .max_detection_thresholds
489
+
490
490
map_per_class_list = []
491
491
mar_100_per_class_list = []
492
492
for class_id in self ._get_classes ():
@@ -516,8 +516,41 @@ def compute(self) -> dict:
516
516
517
517
return result_dict
518
518
519
+ def _get_coco_datasets (self , average : Literal ["macro" , "micro" ]) -> Tuple [COCO , COCO ]:
520
+ """Returns the coco datasets for the target and the predictions."""
521
+ if average == "micro" :
522
+ # for micro averaging we set everything to be the same class
523
+ groundtruth_labels = apply_to_collection (self .groundtruth_labels , Tensor , lambda x : torch .zeros_like (x ))
524
+ detection_labels = apply_to_collection (self .detection_labels , Tensor , lambda x : torch .zeros_like (x ))
525
+ else :
526
+ groundtruth_labels = self .groundtruth_labels
527
+ detection_labels = self .detection_labels
528
+
529
+ coco_target , coco_preds = COCO (), COCO ()
530
+
531
+ coco_target .dataset = self ._get_coco_format (
532
+ labels = groundtruth_labels ,
533
+ boxes = self .groundtruth_box if len (self .groundtruth_box ) > 0 else None ,
534
+ masks = self .groundtruth_mask if len (self .groundtruth_mask ) > 0 else None ,
535
+ crowds = self .groundtruth_crowds ,
536
+ area = self .groundtruth_area ,
537
+ )
538
+ coco_preds .dataset = self ._get_coco_format (
539
+ labels = detection_labels ,
540
+ boxes = self .detection_box if len (self .detection_box ) > 0 else None ,
541
+ masks = self .detection_mask if len (self .detection_mask ) > 0 else None ,
542
+ scores = self .detection_scores ,
543
+ )
544
+
545
+ with contextlib .redirect_stdout (io .StringIO ()):
546
+ coco_target .createIndex ()
547
+ coco_preds .createIndex ()
548
+
549
+ return coco_preds , coco_target
550
+
519
551
@staticmethod
520
552
def _coco_stats_to_tensor_dict (stats : List [float ], prefix : str ) -> Dict [str , Tensor ]:
553
+ """Converts the output of COCOeval.stats to a dict of tensors."""
521
554
return {
522
555
f"{ prefix } map" : torch .tensor ([stats [0 ]], dtype = torch .float32 ),
523
556
f"{ prefix } map_50" : torch .tensor ([stats [1 ]], dtype = torch .float32 ),
0 commit comments