{"record":{"id":"df37df9f80706464","repo":"roboflow/supervision","slug":"edges-is-a-dict-but-class-id-is-none-keypoints-mu","errorCode":null,"errorMessage":"edges is a dict but class_id is None; KeyPoints must have class_id set.","messagePattern":"edges is a dict but class_id is None; KeyPoints must have class_id set\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/supervision/key_points/annotators.py","lineNumber":234,"sourceCode":"            ...     thickness=3,\n            ...     edges={0: [(1, 2), (1, 3)], 1: [(1, 2)]},\n            ... )\n            >>> result = annotator.annotate(image.copy(), key_points)\n\n            ```\n        \"\"\"\n        if len(key_points) == 0:\n            return scene\n\n        for detection_index, xy in enumerate(key_points.xy):\n            if isinstance(self.edges, dict):\n                class_id = (\n                    int(key_points.class_id[detection_index])\n                    if key_points.class_id is not None\n                    else None\n                )\n                if class_id is None:\n                    raise ValueError(\n                        \"edges is a dict but class_id is None; \"\n                        \"KeyPoints must have class_id set.\"\n                    )\n                if class_id not in self.edges:\n                    raise ValueError(f\"No edges defined for class_id={class_id}.\")\n                edges = self.edges[class_id]\n            elif self.edges:\n                edges = self.edges\n            else:\n                _looked_up = SKELETONS_BY_VERTEX_COUNT.get(len(xy))\n                if not _looked_up:\n                    logger.warning(\"No skeleton found with %d vertices\", len(xy))\n                    continue\n                edges = _looked_up\n\n            for edge in edges:\n                idx_a, idx_b = _validate_edge_indices(edge=edge, vertex_count=len(xy))\n                xy_a = xy[idx_a]","sourceCodeStart":216,"sourceCodeEnd":252,"githubUrl":"https://github.com/roboflow/supervision/blob/7f254d9784d4c37e0f03cd89ddee164c8db099c0/src/supervision/key_points/annotators.py#L216-L252","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Set class_id on both predictions and targets: np.zeros(N, dtype=int) if your task is single-class/class-agnostic","Re-check any filtering/transformation step (get_by_class_id, slicing) that may have produced class_id-less Detections","If loading annotations, use sv.Detections.from_coco/from_pascal_voc which preserve class ids, or fix the parser"],"exampleFix":"# before\ntargets = sv.Detections(xyxy=np.array([[30.0, 30.0, 100.0, 100.0]]))  # no class_id\nprecision.update(predictions=[preds], targets=[targets])\nprecision.compute()  # -> ValueError\n\n# after\ntargets = sv.Detections(\n    xyxy=np.array([[30.0, 30.0, 100.0, 100.0]]),\n    class_id=np.array([0]),\n)\nprecision.update(predictions=[preds], targets=[targets])","handlingStrategy":"validation","validationCode":"for d in predictions_list + targets_list:\n    assert d.class_id is not None, 'Precision needs class_id on every Detections'","typeGuard":"def has_class_id(detections: sv.Detections) -> bool:\n    \"\"\"True when Detections carry class ids for class-aware matching.\"\"\"\n    return detections.class_id is not None","tryCatchPattern":null,"preventionTips":["Single-class tasks: default class_id=np.zeros(N, dtype=int) at construction","Use supervision loaders (from_coco/from_pascal_voc) for ground truth so class ids are preserved"],"tags":["metrics","precision","class-id","input-validation"],"backgroundTag":null,"analyzedSha":"7f254d9784d4c37e0f03cd89ddee164c8db099c0","analyzedAt":"2026-08-15T05:13:01.950Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}