{"record":{"id":"ccc65f4ecf635aa4","repo":"roboflow/supervision","slug":"detections-must-include-class-id-for-coco-export","errorCode":null,"errorMessage":"Detections must include class_id for COCO export.","messagePattern":"Detections must include class_id for COCO export\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/supervision/dataset/formats/coco.py","lineNumber":305,"sourceCode":"        ... )\n        >>> detections = Detections(\n        ...     xyxy=np.array([[0, 0, 10, 10]], dtype=np.float32),\n        ...     class_id=np.array([0], dtype=int),\n        ... )\n        >>> annotations, next_id = detections_to_coco_annotations(\n        ...     detections=detections, image_id=1, annotation_id=1\n        ... )\n        >>> annotations[0][\"category_id\"]\n        1\n        >>> next_id\n        2\n\n        ```\n    \"\"\"\n    coco_annotations: list[CocoDict] = []\n    for xyxy, mask, _, class_id, _, data in detections:\n        if class_id is None:\n            raise ValueError(\"Detections must include class_id for COCO export.\")\n        box_width, box_height = xyxy[2] - xyxy[0], xyxy[3] - xyxy[1]\n        segmentation: list[list[float]] | dict[str, list[int]] = []\n        if mask is not None:\n            mask_bool = mask\n            if \"iscrowd\" in data:\n                iscrowd = int(np.asarray(data[\"iscrowd\"]).item())\n            else:\n                iscrowd = int(\n                    contains_holes(mask=mask_bool)\n                    or contains_multiple_segments(mask=mask_bool)\n                )\n\n            if iscrowd:\n                segmentation = {\n                    \"counts\": cast(\n                        list[int], mask_to_rle(mask=mask_bool, compressed=False)\n                    ),\n                    \"size\": list(mask.shape[:2]),","sourceCodeStart":287,"sourceCodeEnd":323,"githubUrl":"https://github.com/roboflow/supervision/blob/7f254d9784d4c37e0f03cd89ddee164c8db099c0/src/supervision/dataset/formats/coco.py#L287-L323","documentation":"Raised by detections_to_coco_annotations when iterating Detections and a detection has class_id=None. COCO annotations require a category_id for every object, so a Detections without class_id cannot be serialized to the COCO format. The check runs per-detection inside the export loop.","triggerScenarios":"Calling sv.Detections with xyxy/confidence but no class_id (e.g. raw model output from a detector run in NMS-only mode, or a manually built Detections), then passing it to save_coco_annotations / detections_to_coco_annotations / DetectionDataset save path that ultimately writes COCO.","commonSituations":"Using detections produced by a tracker or a model that drops class_id; constructing Detections from custom boxes for dataset conversion; filtering detections and losing the class_id array.","solutions":["Provide class_id when constructing Detections: sv.Detections(xyxy=boxes, confidence=conf, class_id=class_ids).","If all boxes belong to one known class, synthesize class_id=np.zeros(len(boxes), dtype=int).","Check detections.class_id is not None before calling the COCO export and fail early with your own message."],"exampleFix":"// before\ndetections = sv.Detections(xyxy=boxes, confidence=conf)\nsave_coco_annotations(dataset=ds, annotation_path=\"out.json\")  # ds built from class_id-less detections\n\n// after\ndetections = sv.Detections(xyxy=boxes, confidence=conf, class_id=np.zeros(len(boxes), dtype=int))","handlingStrategy":"type-guard","validationCode":"if detections.class_id is None or len(detections) == 0 and detections.class_id is None:\n    raise ValueError(\"Attach class_id before COCO export\")\n# single-class fallback:\n# detections = sv.Detections(xyxy=detections.xyxy, confidence=detections.confidence,\n#                            class_id=np.zeros(len(detections), dtype=int))","typeGuard":"def has_class_id(dets: sv.Detections) -> bool:\n    \"\"\"True when every detection carries a class id.\"\"\"\n    return dets.class_id is not None and len(dets.class_id) == len(dets)","tryCatchPattern":"try:\n    save_coco_annotations(dataset=ds, annotation_path=p)\nexcept ValueError as exc:\n    if \"class_id\" in str(exc):\n        raise RuntimeError(\"Model produced class-agnostic detections; map them to a class first\") from exc\n    raise","preventionTips":["Always construct Detections with class_id alongside xyxy.","After slicing/filtering Detections, re-check that class_id survived (it is index-aligned).","For class-agnostic pipelines, default to class_id=zeros and a single-element classes list."],"tags":["coco","detections","class-id","dataset-export"],"backgroundTag":null,"analyzedSha":"7f254d9784d4c37e0f03cd89ddee164c8db099c0","analyzedAt":"2026-08-15T05:13:01.950Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}