{"record":{"id":"99d0d23abedad051","repo":"roboflow/supervision","slug":"meanaveragerecall-metric-requires-class-id-on-bo","errorCode":null,"errorMessage":"MeanAverageRecall metric requires `class_id` on both predictions and targets.","messagePattern":"MeanAverageRecall metric requires `class_id` on both predictions and targets\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/supervision/metrics/mean_average_recall.py","lineNumber":409,"sourceCode":"    ) -> MeanAverageRecallResult:\n        if size_category != ObjectSizeCategory.ANY:\n            # Recall is unaffected by false-positive bookkeeping, and out-of-bucket\n            # predictions must still consume top-K rank slots, so bucket-filtering\n            # the targets is all the size handling mAR needs.\n            targets_list = [\n                self._filter_detections_by_size(targets, size_category)\n                for targets in targets_list\n            ]\n\n        iou_thresholds = np.linspace(0.5, 0.95, 10, dtype=np.float32)\n        stats: list[Any] = []\n\n        for predictions, targets in zip(predictions_list, targets_list):\n            prediction_contents = self._detections_content(predictions)\n            target_contents = self._detections_content(targets)\n            if len(targets) > 0:\n                if predictions.class_id is None or targets.class_id is None:\n                    raise ValueError(\n                        \"MeanAverageRecall metric requires `class_id` on both \"\n                        \"predictions and targets.\"\n                    )\n                if len(predictions) == 0:\n                    target_class_ids = np.asarray(targets.class_id, dtype=np.int32)\n                    if len(target_class_ids) == 0:\n                        continue\n                    stats.append(\n                        (\n                            np.zeros((0, iou_thresholds.size), dtype=bool),\n                            np.zeros((0, iou_thresholds.size), dtype=bool),\n                            np.zeros((0,), dtype=int),\n                            np.zeros((0,), dtype=int),\n                            target_class_ids,\n                        )\n                    )\n\n                else:","sourceCodeStart":391,"sourceCodeEnd":427,"githubUrl":"https://github.com/roboflow/supervision/blob/7f254d9784d4c37e0f03cd89ddee164c8db099c0/src/supervision/metrics/mean_average_recall.py#L391-L427","documentation":"MeanAverageRecall matches predictions to targets per class, so class_id must be present on both sides. This ValueError is raised inside compute() when, for an image with at least one ground-truth target, either predictions.class_id or targets.class_id is None. Note the check runs when len(targets) > 0: images with no ground truth skip it, and class-agnostic evaluation is not supported by this metric without ids.","triggerScenarios":"Building sv.Detections without class_id (e.g. detector output parsed to only xyxy+confidence); a model connector that leaves class_id None; class_id set on predictions but omitted when constructing target Detections from label files; calling compute() after update() with class-less detections on any annotated image.","commonSituations":"Class-agnostic single-class setups where developers assume ids are unnecessary; hand-rolled COCO/VOC parsers that forget the class_id field; using a face/person detector whose output connector drops class ids; mixing data sources where one side populates class_id and the other does not.","solutions":["Always pass class_id (np.int32/int64 array) when constructing sv.Detections for MAR evaluation","For class-agnostic evaluation, assign a constant class_id=np.zeros(N, dtype=int) to both predictions and targets","If using a model connector, ensure it maps class names to ids (e.g. via the model's names dict) before update()","Validate before update(): if targets are non-empty, require class_id is not None on both sides"],"exampleFix":"# before\npreds = sv.Detections(xyxy=boxes, confidence=confs)  # class_id missing\nmar.update(preds, targets)\n\n# after\npreds = sv.Detections(xyxy=boxes, confidence=confs,\n                      class_id=class_ids)  # required by MAR\ntargets = sv.Detections(xyxy=gt_boxes, class_id=gt_class_ids)\nmar.update(preds, targets)","handlingStrategy":"validation","validationCode":"from supervision.detection.core import Detections\n\ndef ids_ready(preds: Detections, tgts: Detections) -> bool:\n    \"\"\"MAR needs class_id whenever targets are non-empty.\"\"\"\n    if len(tgts) == 0:\n        return True\n    return preds.class_id is not None and tgts.class_id is not None","typeGuard":"from supervision.detection.core import Detections\nfrom typing import Optional\nimport numpy as np\n\ndef has_class_id(dets: Detections) -> bool:\n    \"\"\"True when Detections carries a non-None class_id array.\"\"\"\n    return dets.class_id is not None","tryCatchPattern":null,"preventionTips":["Always pass class_id when constructing Detections for evaluation","For class-agnostic runs assign zeros as class_id on both sides","Check class_id is not None on both sides before update() when targets exist"],"tags":["metrics","mean-average-recall","class-id","validation","api-misuse"],"backgroundTag":null,"analyzedSha":"7f254d9784d4c37e0f03cd89ddee164c8db099c0","analyzedAt":"2026-08-15T05:13:01.950Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}