roboflow/supervision · error · ValueError

class_id is required for CreateML export, but the provided D

Error message

class_id is required for CreateML export, but the provided Detections has class_id=None.

What it means

Raised by detections_to_createml_annotations when detections.class_id is None. CreateML annotations label every box via classes[class_id], so a class-less Detections cannot be exported. Unlike the COCO path (per-detection check), this check is on the whole array up front.

Source

Thrown at src/supervision/dataset/formats/createml.py:264

        ```pycon
        >>> import numpy as np
        >>> import supervision as sv
        >>> from supervision.dataset.formats.createml import (
        ...     detections_to_createml_annotations,
        ... )
        >>> detections = sv.Detections(
        ...     xyxy=np.array([[40, 40, 60, 60]], dtype=np.float32),
        ...     class_id=np.array([0], dtype=int),
        ... )
        >>> detections_to_createml_annotations(detections, classes=["dog"])
        [{'label': 'dog', 'coordinates':
          {'x': 50.0, 'y': 50.0, 'width': 20.0, 'height': 20.0}}]

        ```
    """
    class_ids = detections.class_id
    if class_ids is None:
        raise ValueError(
            "class_id is required for CreateML export, but the provided "
            "Detections has class_id=None."
        )
    annotations: list[CreateMLDict] = []
    for xyxy, class_id in zip(detections.xyxy, class_ids):
        x_min, y_min, x_max, y_max = (float(value) for value in xyxy)
        annotations.append(
            {
                "label": classes[int(class_id)],
                "coordinates": {
                    "x": (x_min + x_max) / 2,
                    "y": (y_min + y_max) / 2,
                    "width": x_max - x_min,
                    "height": y_max - y_min,
                },
            }
        )
    return annotations

View on GitHub (pinned to 7f254d9784)

Solutions

  1. Attach class_id when constructing Detections: sv.Detections(xyxy=boxes, class_id=np.zeros(len(boxes), dtype=int)).
  2. Re-run inference with a multi-class model that emits class_id.
  3. Guard before export: if detections.class_id is None: raise your own error with context.

Example fix

// before
detections = sv.Detections(xyxy=boxes)
detections_to_createml_annotations(detections, classes=["dog"])

// after
detections = sv.Detections(xyxy=boxes, class_id=np.zeros(len(boxes), dtype=int))
detections_to_createml_annotations(detections, classes=["dog"])
Defensive patterns

Strategy: type-guard

Validate before calling

if detections.class_id is None:
    detections = sv.Detections(
        xyxy=detections.xyxy,
        confidence=detections.confidence,
        class_id=np.zeros(len(detections), dtype=int),  # single-class default
    )

Type guard

def has_class_id(dets: sv.Detections) -> bool:
    """True when class_id is present and index-aligned with xyxy."""
    return dets.class_id is not None and len(dets.class_id) == len(dets.xyxy)

Try / catch

try:
    save_createml_annotations(dataset=ds, annotation_path=p)
except ValueError as exc:
    if "class_id" in str(exc):
        raise RuntimeError("Detections are class-agnostic; assign class ids first") from exc
    raise

Prevention

When it happens

Trigger: Building sv.Detections(xyxy=..., confidence=...) without class_id, then calling detections_to_createml_annotations or save_createml_annotations (DetectionDataset.as_createml).

Common situations: Detector outputs that omit class_id (class-agnostic NMS); custom box lists for dataset conversion; slicing/filtering Detections and dropping the class_id field.

Related errors


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