{"record":{"id":"10903b104eea2a92","repo":"roboflow/supervision","slug":"f1score-metric-requires-class-id-on-both-predict","errorCode":null,"errorMessage":"F1Score metric requires `class_id` on both predictions and targets.","messagePattern":"F1Score metric requires `class_id` on both predictions and targets\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/supervision/metrics/f1_score.py","lineNumber":215,"sourceCode":"                    continue\n                stats.append(\n                    (\n                        np.zeros(\n                            (len(prediction_class_ids), iou_thresholds.size),\n                            dtype=np.bool_,\n                        ),\n                        np.zeros(\n                            (len(prediction_class_ids), iou_thresholds.size),\n                            dtype=np.bool_,\n                        ),\n                        prediction_confidence,\n                        prediction_class_ids,\n                        np.zeros((0,), dtype=np.int32),\n                    )\n                )\n            elif len(targets) > 0:\n                if predictions.class_id is None or targets.class_id is None:\n                    raise ValueError(\n                        \"F1Score metric requires `class_id` on both predictions \"\n                        \"and targets.\"\n                    )\n                if len(predictions) == 0:\n                    target_class_ids = np.asarray(targets.class_id, dtype=np.int32)[\n                        target_size_mask\n                    ]\n                    if len(target_class_ids) == 0:\n                        continue\n                    stats.append(\n                        (\n                            np.zeros((0, iou_thresholds.size), dtype=bool),\n                            np.zeros((0, iou_thresholds.size), dtype=bool),\n                            np.zeros((0,), dtype=np.float32),\n                            np.zeros((0,), dtype=int),\n                            target_class_ids,\n                        )\n                    )","sourceCodeStart":197,"sourceCodeEnd":233,"githubUrl":"https://github.com/roboflow/supervision/blob/7f254d9784d4c37e0f03cd89ddee164c8db099c0/src/supervision/metrics/f1_score.py#L197-L233","documentation":"Raised by F1Score.update() when both predictions and targets are present for an image, but either side is missing class_id. Class identity is required on both sides to decide whether a match is a true positive or a class-confused mismatch, and to group per-class statistics. The check runs before any IoU matching is done.","triggerScenarios":"Calling F1Score().update() with predictions.class_id is None and len(targets) > 0, or with targets.class_id is None, on any image pair where both arrays are non-empty.","commonSituations":"Ground-truth Detections built manually for evaluation datasets that omit class_id; using detections from a segmentation model connector that only fills xyxy/mask; mixing connector outputs with different field conventions.","solutions":["Set class_id on both Detections: np.array of int class ids aligned with xyxy rows","If labels are strings, map them to integer ids first (e.g. via a {name: id} dict) before constructing Detections","Prefer built-in loaders (DetectionDataset / from_* connectors) which always populate class_id"],"exampleFix":"# before\ntargets = sv.Detections(xyxy=gt_boxes)  # class_id missing\nf1.update(targets=targets, predictions=preds)\n\n# after\ntargets = sv.Detections(\n    xyxy=gt_boxes,\n    class_id=gt_class_ids,\n)\nf1.update(targets=targets, predictions=preds)","handlingStrategy":"validation","validationCode":"if predictions.class_id is None or targets.class_id is None:\n    raise ValueError('Both targets and predictions need class_id for F1Score')\nf1.update(targets=targets, predictions=predictions)","typeGuard":"def both_classified(dets_a: sv.Detections, dets_b: sv.Detections) -> bool:\n    \"\"\"True when both Detections carry class_id.\"\"\"\n    return dets_a.class_id is not None and dets_b.class_id is not None","tryCatchPattern":"try:\n    f1.update(targets=targets, predictions=predictions)\nexcept ValueError as e:\n    if 'class_id on both' in str(e):\n        # fill missing side with a single neutral class\n        if targets.class_id is None:\n            targets.class_id = np.zeros(len(targets), dtype=np.int32)\n        if predictions.class_id is None:\n            predictions.class_id = np.zeros(len(predictions), dtype=np.int32)\n    else:\n        raise","preventionTips":["Load ground truth with sv.DetectionDataset (COCO/YOLO/VOC) so class_id is always set","Standardize a to_detections() helper in your pipeline that guarantees class_id"],"tags":["metrics","f1-score","validation","class-id"],"backgroundTag":null,"analyzedSha":"7f254d9784d4c37e0f03cd89ddee164c8db099c0","analyzedAt":"2026-08-15T05:13:01.950Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}