roboflow/supervision · error · ValueError

MeanAverageRecall metric requires `class_id` on both predict

Error message

MeanAverageRecall metric requires `class_id` on both predictions and targets.

What it means

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.

Source

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

    ) -> MeanAverageRecallResult:
        if size_category != ObjectSizeCategory.ANY:
            # Recall is unaffected by false-positive bookkeeping, and out-of-bucket
            # predictions must still consume top-K rank slots, so bucket-filtering
            # the targets is all the size handling mAR needs.
            targets_list = [
                self._filter_detections_by_size(targets, size_category)
                for targets in targets_list
            ]

        iou_thresholds = np.linspace(0.5, 0.95, 10, dtype=np.float32)
        stats: list[Any] = []

        for predictions, targets in zip(predictions_list, targets_list):
            prediction_contents = self._detections_content(predictions)
            target_contents = self._detections_content(targets)
            if len(targets) > 0:
                if predictions.class_id is None or targets.class_id is None:
                    raise ValueError(
                        "MeanAverageRecall metric requires `class_id` on both "
                        "predictions and targets."
                    )
                if len(predictions) == 0:
                    target_class_ids = np.asarray(targets.class_id, dtype=np.int32)
                    if len(target_class_ids) == 0:
                        continue
                    stats.append(
                        (
                            np.zeros((0, iou_thresholds.size), dtype=bool),
                            np.zeros((0, iou_thresholds.size), dtype=bool),
                            np.zeros((0,), dtype=int),
                            np.zeros((0,), dtype=int),
                            target_class_ids,
                        )
                    )

                else:

View on GitHub (pinned to 7f254d9784)

Solutions

  1. Always pass class_id (np.int32/int64 array) when constructing sv.Detections for MAR evaluation
  2. For class-agnostic evaluation, assign a constant class_id=np.zeros(N, dtype=int) to both predictions and targets
  3. If using a model connector, ensure it maps class names to ids (e.g. via the model's names dict) before update()
  4. Validate before update(): if targets are non-empty, require class_id is not None on both sides

Example fix

# before
preds = sv.Detections(xyxy=boxes, confidence=confs)  # class_id missing
mar.update(preds, targets)

# after
preds = sv.Detections(xyxy=boxes, confidence=confs,
                      class_id=class_ids)  # required by MAR
targets = sv.Detections(xyxy=gt_boxes, class_id=gt_class_ids)
mar.update(preds, targets)
Defensive patterns

Strategy: validation

Validate before calling

from supervision.detection.core import Detections

def ids_ready(preds: Detections, tgts: Detections) -> bool:
    """MAR needs class_id whenever targets are non-empty."""
    if len(tgts) == 0:
        return True
    return preds.class_id is not None and tgts.class_id is not None

Type guard

from supervision.detection.core import Detections
from typing import Optional
import numpy as np

def has_class_id(dets: Detections) -> bool:
    """True when Detections carries a non-None class_id array."""
    return dets.class_id is not None

Prevention

When it happens

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

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

Related errors


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