roboflow/supervision · error · ValueError

starting_image_id and starting_annotation_id must be >= 1 (C

Error message

starting_image_id and starting_annotation_id must be >= 1 (COCO spec requires 1-indexed ids); got starting_image_id={starting_image_id}, starting_annotation_id={starting_annotation_id}

What it means

Raised by save_coco_annotations when starting_image_id or starting_annotation_id is less than 1. The COCO specification requires 1-indexed image and annotation ids, so supervision enforces this at the boundary of COCO export. These parameters exist so you can chain multiple splits (train/valid/test) without id collisions; the function returns the next unused ids for exactly that purpose.

Source

Thrown at src/supervision/dataset/formats/coco.py:665

    Example:
        ```python
        import supervision as sv
        from supervision.dataset.formats.coco import save_coco_annotations

        ds = sv.DetectionDataset.from_yolo(
            images_directory_path="train/images",
            annotations_directory_path="train/labels",
            data_yaml_path="data.yaml",
        )
        next_img_id, next_ann_id = save_coco_annotations(
            dataset=ds, annotation_path="out/train/annotations.json"
        )
        # next_img_id and next_ann_id are the first unused ids — pass them
        # to the next split to keep ids globally unique across files.
        ```
    """
    if starting_image_id < 1 or starting_annotation_id < 1:
        raise ValueError(
            "starting_image_id and starting_annotation_id must be >= 1 "
            "(COCO spec requires 1-indexed ids); "
            f"got {starting_image_id=}, {starting_annotation_id=}"
        )
    check_no_basename_collisions(
        image_paths=dataset.image_paths,
        key=lambda image_path: Path(image_path).name,
        output_kind="COCO image",
    )
    Path(annotation_path).parent.mkdir(parents=True, exist_ok=True)
    licenses = [
        {
            "id": 1,
            "url": "https://creativecommons.org/licenses/by/4.0/",
            "name": "CC BY 4.0",
        }
    ]

View on GitHub (pinned to 7f254d9784)

Solutions

  1. Pass starting_image_id=1, starting_annotation_id=1 for the first split (or omit them — the defaults are 1).
  2. For subsequent splits, feed the values returned by the previous save_coco_annotations call into the next one, as shown in the function docstring.
  3. If computing ids yourself, assert ids >= 1 before calling the exporter.

Example fix

// before
next_img, next_ann = 0, 0
for split in splits:
    next_img, next_ann = save_coco_annotations(ds, path, starting_image_id=next_img, starting_annotation_id=next_ann)

// after
next_img, next_ann = 1, 1  # COCO ids are 1-indexed
for split in splits:
    next_img, next_ann = save_coco_annotations(ds, path, starting_image_id=next_img, starting_annotation_id=next_ann)
Defensive patterns

Strategy: validation

Validate before calling

def next_coco_ids(start_img: int, start_ann: int) -> tuple[int, int]:
    """Validate 1-indexed COCO starting ids before export."""
    if start_img < 1 or start_ann < 1:
        raise ValueError(f"COCO ids must be >= 1, got {start_img=}, {start_ann=}")
    return start_img, start_ann

next_img, next_ann = next_coco_ids(1, 1)  # seed, then chain return values

Type guard

def are_valid_coco_start_ids(img_id: int, ann_id: int) -> bool:
    """True when both starting ids satisfy COCO 1-indexing."""
    return isinstance(img_id, int) and isinstance(ann_id, int) and img_id >= 1 and ann_id >= 1

Try / catch

try:
    save_coco_annotations(dataset=ds, annotation_path=p, starting_image_id=i, starting_annotation_id=a)
except ValueError as exc:
    if "1-indexed" in str(exc):
        i, a = 1, 1  # reset to defaults and retry once
    else:
        raise

Prevention

When it happens

Trigger: Calling sv.dataset.formats.coco.save_coco_annotations (or the DetectionDataset.as_coco/save wrapper that forwards these kwargs) with starting_image_id=0, starting_annotation_id=0, or negative values. Typically happens when initializing a counter at 0 before a loop over splits, or when passing the length of a previous file instead of the returned next_id.

Common situations: Exporting a dataset split loop with enumerate(dataset_splits) starting at 0; porting code from a pipeline that used 0-based ids; ignoring the (next_image_id, next_annotation_id) return value and guessing ids manually.

Related errors


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