fix(coco): emit 1-indexed category_id in COCO export#2276
Merged
Borda merged 4 commits intoMay 27, 2026
Conversation
as_coco wrote category_id (and categories[].id) starting at 0, but the COCO spec and tools like CVAT require category ids to start at 1, so imports into CVAT failed. save_coco_annotations already enforces 1-indexed image_id/annotation_id, which left category_id as the only 0-indexed inconsistency. Serialize the internal 0-indexed Detections.class_id as class_id + 1 in both classes_to_coco_categories and detections_to_coco_annotations, keeping the category ids consistent. The read path maps categories to internal class ids by name, so as_coco -> from_coco round-trips stay lossless. Closes roboflow#1181
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## develop #2276 +/- ##
=======================================
Coverage 78% 78%
=======================================
Files 66 66
Lines 8410 8412 +2
=======================================
+ Hits 6552 6585 +33
+ Misses 1858 1827 -31 🚀 New features to boost your workflow:
|
Contributor
There was a problem hiding this comment.
Pull request overview
This PR fixes COCO export compliance in DetectionDataset.as_coco() by making exported COCO categories[].id and annotation category_id 1-indexed (instead of 0-indexed), aligning with the COCO spec and tooling expectations (e.g., CVAT).
Changes:
- Updated COCO category serialization to emit
id = class_index + 1. - Updated COCO annotation serialization to emit
category_id = class_id + 1. - Added/updated unit tests to assert 1-indexed IDs on disk while preserving 0-indexed internal
Detections.class_idafter round-trip load.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
src/supervision/dataset/formats/coco.py |
Adjusts COCO export helpers to emit 1-indexed category IDs and documents the mapping behavior. |
tests/dataset/formats/test_coco.py |
Updates existing expectations and adds regression tests to confirm 1-indexed COCO output and lossless round-trip behavior. |
Documents the behavior-observable change from roboflow#2276/roboflow#1181 in the UnReleased section, consistent with project convention used for roboflow#2267. --- Co-authored-by: Claude Code <noreply@anthropic.com>
- test_from_coco_loads_legacy_zero_indexed_category_ids: verifies supervision <=0.28.x files (category_id starting at 0) still load and map to correct internal class_ids via name-based read path - test_save_coco_annotations_rejects_zero_starting_ids: pins ValueError guard at L500-505 for starting_image_id=0 / starting_annotation_id=0 - test_detections_to_coco_annotations_raises_when_class_id_is_none: validates class_id=None guard is reached before +1 arithmetic path - test_coco_round_trip_multi_class_single_image: adversarial round-trip with two classes in one image (vs one class per image in existing test) --- Co-authored-by: Claude Code <noreply@anthropic.com>
…ired Raw COCO category_id values (now 1-based) are stored as class_id in the returned Detections; callers must apply map_detections_class_id before the field is meaningful. load_coco_annotations does this correctly; documents the requirement so external callers are not surprised. --- Co-authored-by: Claude Code <noreply@anthropic.com>
Borda
approved these changes
May 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Before submitting
Description
DetectionDataset.as_coco()wrote COCOcategory_id(andcategories[].id) starting at0. The COCO specification and tools such as CVAT require category ids to start at1, so exported annotations failed to import into CVAT (the problem reported in #1181).save_coco_annotationsalready enforces 1-indexedimage_idandannotation_id("COCO spec requires 1-indexed ids"), which leftcategory_idas the only 0-indexed inconsistency. This PR makescategory_id1-indexed as well, so the exported ids are internally consistent and spec-compliant by default.Type of Change
Motivation and Context
The internal
Detections.class_idis 0-indexed, but COCO category ids are expected to be 1-indexed. Exporting the 0-indexed value directly produced files CVAT rejects. Aligningcategory_idwith the already-1-indexedimage_id/annotation_idfixes CVAT import and matches the COCO spec.Closes #1181
Changes Made
classes_to_coco_categories: emitid = class_index + 1(1-indexed).detections_to_coco_annotations: emitcategory_id = class_id + 1, kept consistent with the category ids above (internalclass_id = k→ COCOcategory_id = k + 1).coco_categories_to_classes/build_coco_class_index_mappingmap categories to sequential internal class ids by name, not by absolute id value, soas_coco→from_cocoround-trips remain lossless.Detections.class_idstays 0-indexed in memory.Testing
pytest tests/dataset/formats/test_coco.py→ 71 passed.ruff checkandruff format --checkclean on the changed files.New/updated tests:
test_classes_to_coco_categories_ids_start_at_one— category ids start at 1.test_detections_to_coco_annotations_category_id_is_one_indexed— internal class_idk→category_idk + 1.test_coco_round_trip_preserves_class_ids_and_writes_one_indexed_categories—as_cocowrites 1-indexed ids on disk, andfrom_cocoreads back the original 0-indexed internal class ids losslessly.test_detections_to_coco_annotationsparametrize cases that asserted the old 0-indexedcategory_id.Additional Notes
This is a behavior change for the exported value of
category_id, but it brings the field in line with the COCO spec and withimage_id/annotation_id, which are already 1-indexed insave_coco_annotations. Since reading is name-based, existing supervision-written datasets still load correctly.