roboflow/supervision · error · ValueError

MeanAveragePrecision with `MetricTarget.MASKS` requires mask

Error message

MeanAveragePrecision with `MetricTarget.MASKS` requires masks on both predictions and targets.

What it means

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.

Source

Thrown at src/supervision/metrics/mean_average_precision.py:1470

                if prediction.class_id is not None:
                    prediction.class_id[:] = -1
            for target in targets:
                if target.class_id is not None:
                    target.class_id[:] = -1

        self._predictions_list.extend(predictions)
        self._targets_list.extend(targets)

        return self

    def _detections_content(self, detections: Detections) -> npt.NDArray[Any] | None:
        """Return per-detection masks or oriented boxes for the metric target,
        or `None` for the box target and for empty detections."""
        if self._metric_target == MetricTarget.BOXES or len(detections) == 0:
            return None
        if self._metric_target == MetricTarget.MASKS:
            if detections.mask is None:
                raise ValueError(
                    "MeanAveragePrecision with `MetricTarget.MASKS` requires"
                    " masks on both predictions and targets."
                )
            return np.asarray(detections.mask).astype(bool)
        if self._metric_target == MetricTarget.ORIENTED_BOUNDING_BOXES:
            obb = detections.data.get(ORIENTED_BOX_COORDINATES)
            if obb is None:
                raise ValueError(
                    "MeanAveragePrecision with"
                    " `MetricTarget.ORIENTED_BOUNDING_BOXES` requires"
                    f" `{ORIENTED_BOX_COORDINATES}` in `data` on both"
                    " predictions and targets."
                )
            return np.asarray(obb, dtype=np.float32).reshape(-1, 4, 2)
        raise ValueError(f"Invalid metric target: {self._metric_target}")

    def _content_area(
        self, xywh: list[float], content: npt.NDArray[Any] | None, idx: int

View on GitHub (pinned to 7f254d9784)

Solutions

  1. Populate .mask on both predictions and targets: sv.Detections(..., mask=bool_array_of_shape_NHW)
  2. Use a segmentation model and its connector so masks flow through automatically
  3. If you only have boxes, evaluate with the default MetricTarget.BOXES
  4. Pre-check before update: require (det.mask is not None) or det.is_empty() for both sides

Example fix

# before
map_ = sv.MeanAveragePrecision(metric_target=sv.MetricTarget.MASKS)
preds = sv.Detections(xyxy=boxes, class_id=ids, confidence=confs)
map_.update(preds, targets)   # no masks -> ValueError

# after
preds = sv.Detections(xyxy=boxes, class_id=ids, confidence=confs,
                      mask=pred_masks)    # (N, H, W) bool
targets = sv.Detections(xyxy=gt_boxes, class_id=gt_ids, mask=gt_masks)
map_.update(preds, targets)
Defensive patterns

Strategy: validation

Validate before calling

def masks_ok(dets) -> bool:
    """MASKS-target precondition: empty or carries .mask."""
    return len(dets) == 0 or dets.mask is not None

assert masks_ok(preds) and masks_ok(targets)

Type guard

from supervision.detection.core import Detections

def has_mask_data(dets: Detections) -> bool:
    """True when Detections is empty or has a populated mask field."""
    return dets.mask is not None or dets.is_empty()

Prevention

When it happens

Trigger: 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.

Common situations: 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).

Related errors


AI-assisted analysis of roboflow/supervision@7f254d9784 (2026-08-15). Data as JSON: /api/errors/6e76772308bc4639. Report an issue: GitHub.