{"record":{"id":"6e76772308bc4639","repo":"roboflow/supervision","slug":"meanaverageprecision-with-metrictarget-masks-req","errorCode":null,"errorMessage":"MeanAveragePrecision with `MetricTarget.MASKS` requires masks on both predictions and targets.","messagePattern":"MeanAveragePrecision with `MetricTarget\\.MASKS` requires masks on both predictions and targets\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/supervision/metrics/mean_average_precision.py","lineNumber":1470,"sourceCode":"                if prediction.class_id is not None:\n                    prediction.class_id[:] = -1\n            for target in targets:\n                if target.class_id is not None:\n                    target.class_id[:] = -1\n\n        self._predictions_list.extend(predictions)\n        self._targets_list.extend(targets)\n\n        return self\n\n    def _detections_content(self, detections: Detections) -> npt.NDArray[Any] | None:\n        \"\"\"Return per-detection masks or oriented boxes for the metric target,\n        or `None` for the box target and for empty detections.\"\"\"\n        if self._metric_target == MetricTarget.BOXES or len(detections) == 0:\n            return None\n        if self._metric_target == MetricTarget.MASKS:\n            if detections.mask is None:\n                raise ValueError(\n                    \"MeanAveragePrecision with `MetricTarget.MASKS` requires\"\n                    \" masks on both predictions and targets.\"\n                )\n            return np.asarray(detections.mask).astype(bool)\n        if self._metric_target == MetricTarget.ORIENTED_BOUNDING_BOXES:\n            obb = detections.data.get(ORIENTED_BOX_COORDINATES)\n            if obb is None:\n                raise ValueError(\n                    \"MeanAveragePrecision with\"\n                    \" `MetricTarget.ORIENTED_BOUNDING_BOXES` requires\"\n                    f\" `{ORIENTED_BOX_COORDINATES}` in `data` on both\"\n                    \" predictions and targets.\"\n                )\n            return np.asarray(obb, dtype=np.float32).reshape(-1, 4, 2)\n        raise ValueError(f\"Invalid metric target: {self._metric_target}\")\n\n    def _content_area(\n        self, xywh: list[float], content: npt.NDArray[Any] | None, idx: int","sourceCodeStart":1452,"sourceCodeEnd":1488,"githubUrl":"https://github.com/roboflow/supervision/blob/7f254d9784d4c37e0f03cd89ddee164c8db099c0/src/supervision/metrics/mean_average_precision.py#L1452-L1488","documentation":"When MeanAveragePrecision is configured with metric_target=MetricTarget.MASKS, _detections_content must return each detection's boolean mask to compute mask IoU. This ValueError fires when detections.mask is None for a non-empty Detections object (the method returns None early for empty detections, so only populated detections are checked). It is raised for whichever side — predictions or targets — lacks masks.","triggerScenarios":"MeanAveragePrecision(metric_target=MetricTarget.MASKS).update() with box-only Detections (no mask= kwarg); segmentation model output passed through a detection-only connector that drops masks; masks present on predictions but ground-truth Detections built from bounding-box annotations only; class-agnostic deep-copies in update() still carry no masks.","commonSituations":"Switching metric_target from BOXES to MASKS without switching models/annotation loaders to segmentation; evaluating a detector checkpoint with the segmentation metric; GT annotation pipeline (COCO boxes, Pascal VOC) that never produced masks; one-sided mask availability (predictions segmented, GT boxed).","solutions":["Populate .mask on both predictions and targets: sv.Detections(..., mask=bool_array_of_shape_NHW)","Use a segmentation model and its connector so masks flow through automatically","If you only have boxes, evaluate with the default MetricTarget.BOXES","Pre-check before update: require (det.mask is not None) or det.is_empty() for both sides"],"exampleFix":"# before\nmap_ = sv.MeanAveragePrecision(metric_target=sv.MetricTarget.MASKS)\npreds = sv.Detections(xyxy=boxes, class_id=ids, confidence=confs)\nmap_.update(preds, targets)   # no masks -> ValueError\n\n# after\npreds = sv.Detections(xyxy=boxes, class_id=ids, confidence=confs,\n                      mask=pred_masks)    # (N, H, W) bool\ntargets = sv.Detections(xyxy=gt_boxes, class_id=gt_ids, mask=gt_masks)\nmap_.update(preds, targets)","handlingStrategy":"validation","validationCode":"def masks_ok(dets) -> bool:\n    \"\"\"MASKS-target precondition: empty or carries .mask.\"\"\"\n    return len(dets) == 0 or dets.mask is not None\n\nassert masks_ok(preds) and masks_ok(targets)","typeGuard":"from supervision.detection.core import Detections\n\ndef has_mask_data(dets: Detections) -> bool:\n    \"\"\"True when Detections is empty or has a populated mask field.\"\"\"\n    return dets.mask is not None or dets.is_empty()","tryCatchPattern":null,"preventionTips":["Match metric_target to the model task: MASKS only with segmentation models","Populate mask= on both predictions and targets from the same preprocessor","Add a pipeline precondition check before update()"],"tags":["metrics","mean-average-precision","masks","segmentation","validation"],"backgroundTag":null,"analyzedSha":"7f254d9784d4c37e0f03cd89ddee164c8db099c0","analyzedAt":"2026-08-15T05:13:01.950Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}