{"record":{"id":"929272a1c089743e","repo":"roboflow/supervision","slug":"meanaveragerecall-with-metrictarget-masks-requir","errorCode":null,"errorMessage":"MeanAverageRecall with `MetricTarget.MASKS` requires detections to include masks.","messagePattern":"MeanAverageRecall with `MetricTarget\\.MASKS` requires detections to include masks\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/supervision/metrics/mean_average_recall.py","lineNumber":687,"sourceCode":"        return result_recall\n\n    def _detections_content(\n        self, detections: Detections\n    ) -> npt.NDArray[Any] | CompactMask:\n        \"\"\"Return boxes, masks or oriented bounding boxes from detections.\n\n        For the mask target this may return a\n        :class:`~supervision.detection.compact_mask.CompactMask` rather than a\n        dense boolean array when the detections carry compact masks.\n        \"\"\"\n        if self._metric_target == MetricTarget.BOXES:\n            return cast(npt.NDArray[Any], detections.xyxy)\n        if self._metric_target == MetricTarget.MASKS:\n            if detections.mask is not None:\n                # detections.mask is NDArray[bool] | CompactMask; return as-is.\n                return detections.mask\n            if len(detections) > 0:\n                raise ValueError(\n                    \"MeanAverageRecall with `MetricTarget.MASKS` requires \"\n                    \"detections to include masks.\"\n                )\n            return self._make_empty_content()\n        if self._metric_target == MetricTarget.ORIENTED_BOUNDING_BOXES:\n            obb = detections.data.get(ORIENTED_BOX_COORDINATES)\n            if obb is not None and len(obb) > 0:\n                result_obb: npt.NDArray[np.float32] = np.array(obb, dtype=np.float32)\n                return result_obb\n            return self._make_empty_content()\n        raise ValueError(f\"Invalid metric target: {self._metric_target}\")\n\n    def _make_empty_content(self) -> npt.NDArray[Any]:\n        if self._metric_target == MetricTarget.BOXES:\n            empty_boxes: npt.NDArray[np.float32] = np.empty((0, 4), dtype=np.float32)\n            return empty_boxes\n\n        if self._metric_target == MetricTarget.MASKS:","sourceCodeStart":669,"sourceCodeEnd":705,"githubUrl":"https://github.com/roboflow/supervision/blob/7f254d9784d4c37e0f03cd89ddee164c8db099c0/src/supervision/metrics/mean_average_recall.py#L669-L705","documentation":"When MeanAverageRecall is configured with metric_target=MetricTarget.MASKS, it must extract a boolean mask per detection. This ValueError fires in _detections_content when detections.mask is None while the Detections object still contains >=1 detection. Empty Detections are tolerated (an empty mask placeholder is returned), but any non-empty detections without masks cannot be evaluated and are rejected.","triggerScenarios":"Constructing MeanAverageRecall(metric_target=MetricTarget.MASKS) and calling update()/compute() with Detections built from box-only model outputs (sv.Detections(xyxy=..., class_id=...) with no mask= kwarg); using a detector connector (e.g. from_ultralytics on a detection model) instead of a segmentation connector; masks present on predictions but missing on targets (error names whichever side lacks them).","commonSituations":"Running a YOLO detect (not segment) checkpoint with the MASKS metric target; forgetting to pass mask= when hand-building Detections from postprocessed arrays; mixing pipelines where inference adds masks but GT loading (COCO/labels) drops them; migrating from BOXES default to MASKS without regenerating targets.","solutions":["If evaluating boxes, keep the default metric_target=MetricTarget.BOXES","If masks are required, feed detections that carry masks: use a segmentation model and the appropriate connector (e.g. YOLO-Seg via from_ultralytics) so detections.mask is populated","When hand-building Detections, pass mask=np.array([H,W,N] boolean) explicitly for both predictions and targets","Verify per-image before update: if metric target is MASKS, assert detections.mask is not None or detections.is_empty()"],"exampleFix":"# before\nmar = sv.MeanAverageRecall(metric_target=sv.MetricTarget.MASKS)\npreds = sv.Detections(xyxy=boxes, confidence=confs, class_id=ids)  # no mask\nmar.update(preds, targets)\n\n# after\nmar = sv.MeanAverageRecall(metric_target=sv.MetricTarget.MASKS)\npreds = sv.Detections(xyxy=boxes, confidence=confs, class_id=ids,\n                      mask=pred_masks)          # (N,H,W) bool\ntargets = sv.Detections(xyxy=gt_boxes, class_id=gt_ids, mask=gt_masks)\nmar.update(preds, targets)","handlingStrategy":"validation","validationCode":"from supervision.detection.core import Detections\n\ndef masks_ready(dets: Detections) -> bool:\n    \"\"\"Non-empty Detections must carry masks for MASKS-target evaluation.\"\"\"\n    return dets.is_empty() or dets.mask is not None","typeGuard":"from supervision.detection.core import Detections\n\ndef has_masks(dets: Detections) -> bool:\n    \"\"\"True when Detections is empty or carries a mask array.\"\"\"\n    return dets.mask is not None or len(dets) == 0","tryCatchPattern":null,"preventionTips":["Use a segmentation model and its connector so .mask is populated end to end","Keep metric_target consistent with the model task (detect vs segment)","Set a pipeline precondition: MASKS target => .mask is not None on every non-empty Detections"],"tags":["metrics","mean-average-recall","masks","segmentation","validation"],"backgroundTag":null,"analyzedSha":"7f254d9784d4c37e0f03cd89ddee164c8db099c0","analyzedAt":"2026-08-15T05:13:01.950Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}