{"record":{"id":"0a13574a2a1b9011","repo":"roboflow/supervision","slug":"f1score-metric-requires-class-id-and-confidence","errorCode":null,"errorMessage":"F1Score metric requires `class_id` and `confidence` on predictions.","messagePattern":"F1Score metric requires `class_id` and `confidence` on predictions\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/supervision/metrics/f1_score.py","lineNumber":186,"sourceCode":"            prediction_size_mask = np.ones(len(predictions), dtype=bool)\n            target_size_mask = np.ones(len(targets), dtype=bool)\n            if size_category != ObjectSizeCategory.ANY:\n                if len(predictions) > 0:\n                    prediction_size_mask = (\n                        get_detection_size_category(predictions, self._metric_target)\n                        == size_category.value\n                    )\n                if len(targets) > 0:\n                    target_size_mask = (\n                        get_detection_size_category(targets, self._metric_target)\n                        == size_category.value\n                    )\n\n            if len(targets) == 0 and len(predictions) > 0:\n                # Only predictions are present (e.g. a background image); every\n                # prediction is a false positive.\n                if predictions.class_id is None or predictions.confidence is None:\n                    raise ValueError(\n                        \"F1Score metric requires `class_id` and `confidence` \"\n                        \"on predictions.\"\n                    )\n                prediction_class_ids = np.asarray(predictions.class_id, dtype=np.int32)[\n                    prediction_size_mask\n                ]\n                prediction_confidence = np.asarray(\n                    predictions.confidence, dtype=np.float32\n                )[prediction_size_mask]\n                if len(prediction_class_ids) == 0:\n                    continue\n                stats.append(\n                    (\n                        np.zeros(\n                            (len(prediction_class_ids), iou_thresholds.size),\n                            dtype=np.bool_,\n                        ),\n                        np.zeros(","sourceCodeStart":168,"sourceCodeEnd":204,"githubUrl":"https://github.com/roboflow/supervision/blob/7f254d9784d4c37e0f03cd89ddee164c8db099c0/src/supervision/metrics/f1_score.py#L168-L204","documentation":"Raised by F1Score.update() when an image contains only predictions and no targets (e.g. a background image with false positives), but the predictions lack class_id or confidence. The metric needs class_id to bucket false positives per class and confidence to rank predictions across thresholds. Without these fields, per-class F1 statistics cannot be accumulated.","triggerScenarios":"Calling F1Score().update(targets=empty_detections, predictions=Detections(xyxy=..., class_id=None)) or predictions without a confidence array, on an image where len(targets)==0 and len(predictions)>0.","commonSituations":"Hand-built Detections objects (e.g. from a custom model connector) that omit confidence; predictions crafted from trackers that drop confidence; test fixtures with empty target sets but populated predictions.","solutions":["Attach class_id and confidence to the predictions Detections: Detections(xyxy=..., class_id=np.array([0]), confidence=np.array([0.9]))","If your model connector drops these fields, keep them: most from_* connectors populate both automatically","Skip images with no targets before calling update() if you do not want background images scored"],"exampleFix":"# before\npreds = sv.Detections(xyxy=boxes)  # no class_id / confidence\nf1.update(targets=sv.Detections.empty(), predictions=preds)\n\n# after\npreds = sv.Detections(\n    xyxy=boxes,\n    class_id=np.zeros(len(boxes), dtype=np.int32),\n    confidence=scores,\n)\nf1.update(targets=sv.Detections.empty(), predictions=preds)","handlingStrategy":"validation","validationCode":"def has_f1_fields(preds: sv.Detections) -> bool:\n    return preds.class_id is not None and preds.confidence is not None\n\nif not has_f1_fields(predictions):\n    raise ValueError('predictions need class_id and confidence before F1')","typeGuard":"def is_f1_ready(dets: sv.Detections) -> bool:\n    \"\"\"True when class_id and confidence are populated.\"\"\"\n    return dets.class_id is not None and dets.confidence is not None","tryCatchPattern":"try:\n    f1.update(targets=targets, predictions=predictions)\nexcept ValueError as e:\n    if 'class_id and confidence' in str(e):\n        predictions.class_id = predictions.class_id or np.zeros(len(predictions), dtype=np.int32)\n    else:\n        raise","preventionTips":["Always construct predictions via model connectors (from_ultralytics etc.) which fill class_id and confidence","Write a small assert helper before evaluation loops: assert preds.class_id is not None and preds.confidence is not None"],"tags":["metrics","f1-score","validation","detections"],"backgroundTag":null,"analyzedSha":"7f254d9784d4c37e0f03cd89ddee164c8db099c0","analyzedAt":"2026-08-15T05:13:01.950Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}