roboflow/supervision · error · ValueError

MeanAverageRecall with `MetricTarget.MASKS` requires detecti

Error message

MeanAverageRecall with `MetricTarget.MASKS` requires detections to include masks.

What it means

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.

Source

Thrown at src/supervision/metrics/mean_average_recall.py:687

        return result_recall

    def _detections_content(
        self, detections: Detections
    ) -> npt.NDArray[Any] | CompactMask:
        """Return boxes, masks or oriented bounding boxes from detections.

        For the mask target this may return a
        :class:`~supervision.detection.compact_mask.CompactMask` rather than a
        dense boolean array when the detections carry compact masks.
        """
        if self._metric_target == MetricTarget.BOXES:
            return cast(npt.NDArray[Any], detections.xyxy)
        if self._metric_target == MetricTarget.MASKS:
            if detections.mask is not None:
                # detections.mask is NDArray[bool] | CompactMask; return as-is.
                return detections.mask
            if len(detections) > 0:
                raise ValueError(
                    "MeanAverageRecall with `MetricTarget.MASKS` requires "
                    "detections to include masks."
                )
            return self._make_empty_content()
        if self._metric_target == MetricTarget.ORIENTED_BOUNDING_BOXES:
            obb = detections.data.get(ORIENTED_BOX_COORDINATES)
            if obb is not None and len(obb) > 0:
                result_obb: npt.NDArray[np.float32] = np.array(obb, dtype=np.float32)
                return result_obb
            return self._make_empty_content()
        raise ValueError(f"Invalid metric target: {self._metric_target}")

    def _make_empty_content(self) -> npt.NDArray[Any]:
        if self._metric_target == MetricTarget.BOXES:
            empty_boxes: npt.NDArray[np.float32] = np.empty((0, 4), dtype=np.float32)
            return empty_boxes

        if self._metric_target == MetricTarget.MASKS:

View on GitHub (pinned to 7f254d9784)

Solutions

  1. If evaluating boxes, keep the default metric_target=MetricTarget.BOXES
  2. 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
  3. When hand-building Detections, pass mask=np.array([H,W,N] boolean) explicitly for both predictions and targets
  4. Verify per-image before update: if metric target is MASKS, assert detections.mask is not None or detections.is_empty()

Example fix

# before
mar = sv.MeanAverageRecall(metric_target=sv.MetricTarget.MASKS)
preds = sv.Detections(xyxy=boxes, confidence=confs, class_id=ids)  # no mask
mar.update(preds, targets)

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

Strategy: validation

Validate before calling

from supervision.detection.core import Detections

def masks_ready(dets: Detections) -> bool:
    """Non-empty Detections must carry masks for MASKS-target evaluation."""
    return dets.is_empty() or dets.mask is not None

Type guard

from supervision.detection.core import Detections

def has_masks(dets: Detections) -> bool:
    """True when Detections is empty or carries a mask array."""
    return dets.mask is not None or len(dets) == 0

Prevention

When it happens

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

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

Related errors


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