How to Export Detection Results to COCO Format with Class Mapping in Python

Use the save_coco_annotations function from supervision.dataset.formats.coco combined with build_coco_class_index_mapping and map_detections_class_id to remap class indices before serialization, ensuring your COCO JSON follows a custom category ordering.

The Roboflow Supervision library provides a complete toolkit for converting detection datasets into the COCO format while controlling class index mappings. When working with custom models that output non-sequential class IDs, you need a reliable way to export detection results to COCO format with class mapping to ensure compatibility with downstream evaluation tools and datasets that expect contiguous zero-based indices.

Core Functions for COCO Export

The COCO export pipeline in Supervision is built around three primary utilities located in src/supervision/dataset/formats/coco.py:

  • save_coco_annotations (lines 331-384): Orchestrates the entire export process, building COCO headers (info, licenses, categories, images) and writing the final JSON file.
  • detections_to_coco_annotations (lines 158-185): Converts individual Detections objects into COCO annotation dictionaries, handling bounding boxes, masks, crowd flags, and polygon approximation.
  • build_coco_class_index_mapping (lines 34-44): Creates a mapping dictionary that translates original class IDs to target indices based on a specified class name order.

For remapping operations, Supervision provides map_detections_class_id in src/supervision/dataset/utils.py (lines 16-19), which rewrites class_id values in Detections objects according to a user-supplied dictionary.

Step-by-Step Workflow

To export detection results to COCO format with class mapping, follow this sequence:

  1. Prepare your dataset as a DetectionDataset object that yields (image_path, image, detections) tuples.
  2. Define target class ordering by supplying a list of class names in your desired sequence.
  3. Build the mapping using build_coco_class_index_mapping to create a dictionary translating {source_class_id: target_index}.
  4. Remap detections by applying map_detections_class_id to each Detections object before export.
  5. Execute the export by calling save_coco_annotations with your remapped dataset and output path.

Practical Code Examples

Basic Export Without Mapping

For datasets where class IDs are already sequential and match your target ordering, use the direct export method:

from supervision.dataset.core import DetectionDataset
from supervision.dataset.formats.coco import save_coco_annotations

# Assuming my_dataset is a DetectionDataset instance

save_coco_annotations(
    dataset=my_dataset,
    annotation_path="exports/my_dataset_coco.json"
)

Under the hood, save_coco_annotations iterates over the dataset (lines 352-364), calls detections_to_coco_annotations for each image (lines 158-185), and assembles the standard COCO structure before writing via save_json_file (line 384).

Export with Custom Class Mapping

When your model outputs class IDs [0, 1, 2] but you need the COCO file to list categories in the order ["person", "car", "dog"], implement a remapping wrapper:

from supervision.dataset.core import DetectionDataset
from supervision.dataset.formats.coco import (
    save_coco_annotations,
    build_coco_class_index_mapping,
)
from supervision.dataset.utils import map_detections_class_id

# Define your desired category order

target_classes = ["person", "car", "dog"]
source_classes = my_dataset.classes  # Original order from your dataset

# Create mapping from original IDs to target indices

class_mapping = build_coco_class_index_mapping(
    coco_categories=[{"id": i, "name": name} for i, name in enumerate(source_classes)],
    target_classes=target_classes,
)

# Create a wrapper dataset that remaps class IDs on-the-fly

class MappedDataset(DetectionDataset):
    def __iter__(self):
        for image_path, image, detections in super().__iter__():
            detections = map_detections_class_id(
                source_to_target_mapping=class_mapping,
                detections=detections,
            )
            yield image_path, image, detections

mapped_dataset = MappedDataset(
    images_dir=my_dataset.images_dir,
    annotations=my_dataset.annotations,
    classes=target_classes,  # Updated class list for the exporter

)

# Export with remapped indices

save_coco_annotations(
    dataset=mapped_dataset,
    annotation_path="exports/custom_mapped_coco.json"
)

The build_coco_class_index_mapping function constructs the translation dictionary (lines 34-44), while map_detections_class_id applies the transformation to each detection instance (lines 16-19 in dataset/utils.py). The exporter then uses these remapped IDs when setting category_id in the final JSON (line 210).

Controlling Polygon Approximation

When exporting instance segmentation masks, you can fine-tune polygon generation parameters:

save_coco_annotations(
    dataset=my_dataset,
    annotation_path="exports/precise_coco.json",
    min_image_area_percentage=0.0,  # Include masks of any size

    max_image_area_percentage=1.0,  # No upper size limit

    approximation_percentage=0.95,  # Higher fidelity (default is 0.75)

)

These parameters are forwarded to approximate_mask_with_polygons inside detections_to_coco_annotations (lines 186-195), controlling which masks are included based on image area percentage and how closely polygons follow mask boundaries.

Summary

  • Supervision provides a complete COCO export pipeline through src/supervision/dataset/formats/coco.py, handling bounding boxes, masks, and metadata.
  • Use build_coco_class_index_mapping to create translation dictionaries between source model IDs and target COCO indices.
  • Apply map_detections_class_id to transform Detections objects before export when working with non-sequential class IDs.
  • The save_coco_annotations function automatically handles COCO structure generation, polygon approximation, and JSON serialization.
  • All functionality operates on the standard DetectionDataset protocol, making it compatible with any detection pipeline that produces Supervision Detections objects.

Frequently Asked Questions

What is the difference between source class IDs and target class indices in COCO export?

Source class IDs are the integer labels output by your specific model (which may be arbitrary or non-contiguous), while target class indices are the sequential integers (0, 1, 2...) required by the COCO format specification for the category_id field. The build_coco_class_index_mapping function bridges this gap by creating a dictionary that maps your specific model's IDs to the standard zero-based indices expected by evaluation tools.

How do I handle missing classes when building a class mapping?

If your target class list contains categories not present in the source dataset, build_coco_class_index_mapping will assign them indices in the target ordering but no annotations will reference those IDs in the exported JSON. This is valid COCO format—the categories array lists all possible classes, while annotations only reference the ones present. Ensure your target class list represents your complete ontology, not just the classes visible in the current dataset split.

Can I export instance segmentation masks to COCO format using Supervision?

Yes. When your Detections objects contain a mask attribute (NumPy arrays of shape (N, H, W)), detections_to_coco_annotations automatically converts these to polygon format using approximate_mask_with_polygons (lines 186-195). The resulting COCO JSON includes RLE or polygon segmentation data in the segmentation field of each annotation, compatible with the COCO API and standard evaluation scripts.

Why are my exported class IDs not sequential in the COCO JSON?

If you observe non-sequential category_id values in the output, you likely skipped the remapping step and passed the dataset directly to save_coco_annotations. The exporter preserves original class IDs by default. To enforce sequential ordering, you must use build_coco_class_index_mapping to create a mapping dictionary and apply it via map_detections_class_id before export, as shown in the custom mapping example above.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →