roboflow/supervision · error · ValueError

edges is a dict but class_id is None; KeyPoints must have cl

Error message

edges is a dict but class_id is None; KeyPoints must have class_id set.

What it means

On the main matching path (image has at least one target), Precision pairs predictions with targets by class. Both sides therefore need class_id. The error fires during compute() when either predictions.class_id or targets.class_id is None.

Source

Thrown at src/supervision/key_points/annotators.py:234

            ...     thickness=3,
            ...     edges={0: [(1, 2), (1, 3)], 1: [(1, 2)]},
            ... )
            >>> result = annotator.annotate(image.copy(), key_points)

            ```
        """
        if len(key_points) == 0:
            return scene

        for detection_index, xy in enumerate(key_points.xy):
            if isinstance(self.edges, dict):
                class_id = (
                    int(key_points.class_id[detection_index])
                    if key_points.class_id is not None
                    else None
                )
                if class_id is None:
                    raise ValueError(
                        "edges is a dict but class_id is None; "
                        "KeyPoints must have class_id set."
                    )
                if class_id not in self.edges:
                    raise ValueError(f"No edges defined for class_id={class_id}.")
                edges = self.edges[class_id]
            elif self.edges:
                edges = self.edges
            else:
                _looked_up = SKELETONS_BY_VERTEX_COUNT.get(len(xy))
                if not _looked_up:
                    logger.warning("No skeleton found with %d vertices", len(xy))
                    continue
                edges = _looked_up

            for edge in edges:
                idx_a, idx_b = _validate_edge_indices(edge=edge, vertex_count=len(xy))
                xy_a = xy[idx_a]

View on GitHub (pinned to 7f254d9784)

Solutions

  1. Set class_id on both predictions and targets: np.zeros(N, dtype=int) if your task is single-class/class-agnostic
  2. Re-check any filtering/transformation step (get_by_class_id, slicing) that may have produced class_id-less Detections
  3. If loading annotations, use sv.Detections.from_coco/from_pascal_voc which preserve class ids, or fix the parser

Example fix

# before
targets = sv.Detections(xyxy=np.array([[30.0, 30.0, 100.0, 100.0]]))  # no class_id
precision.update(predictions=[preds], targets=[targets])
precision.compute()  # -> ValueError

# after
targets = sv.Detections(
    xyxy=np.array([[30.0, 30.0, 100.0, 100.0]]),
    class_id=np.array([0]),
)
precision.update(predictions=[preds], targets=[targets])
Defensive patterns

Strategy: validation

Validate before calling

for d in predictions_list + targets_list:
    assert d.class_id is not None, 'Precision needs class_id on every Detections'

Type guard

def has_class_id(detections: sv.Detections) -> bool:
    """True when Detections carry class ids for class-aware matching."""
    return detections.class_id is not None

Prevention

When it happens

Trigger: Calling precision.update() where target Detections were built without class_id (manual construction, or a loader path that dropped class ids) or predictions lack class_id, then compute(). Unlike the background path, prediction confidence is not required here (it is checked separately when predictions are non-empty).

Common situations: Ground-truth built from plain annotation files parsed by hand (boxes + no ids); custom pipelines that merge/transform Detections and lose class_id; evaluating a class-agnostic detector that emits no ids.

Related errors


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