Labeling Format System
Overview
The labeling format system handles serialization of synthetic annotations to standard computer vision formats. It is built around a single abstract class (IOStrategy) and a registry that maps format names to their implementations. The registry is the only entry point you need when selecting or adding a format.
The code lives under ext/core/io/:
ext/core/io/
├── io_strategy.py # Abstract base class and data structures
├── registry.py # LabelingFormatRegistry
└── strategies/
├── __init__.py # SupportedFormats enum + imports
├── yolo_strategy.py
├── coco_strategy.py
├── pascal_strategy.py
└── cvat_xml_strategy.py
Core Concepts
FormatSpecification
Each strategy declares its capabilities and requirements by returning a FormatSpecification from get_specification(). This tells the pipeline how to drive the strategy: whether to write one file per image or one global file, whether class declarations are needed, which geometry types are expected, and so on.
@dataclass
class FormatSpecification:
single_file_per_image: bool # True for YOLO/Pascal, False for COCO
global_metadata_required: bool # True for COCO
aggregation_strategy: Literal["per_image", "per_batch", "global"]
requires_class_declaration: bool
supports_image_metadata: bool
requires_bbox: bool = False
requires_polygon: bool = False
requires_keypoints: bool = False
requires_depth: bool = False
The pipeline reads this spec and calls the appropriate methods. If you implement a new strategy, this is the first thing to get right.
IOStrategy sequence diagram
For each batch, the pipeline calls these methods in order:
ensure_directories()— create output directories before writing anything.transform_annotation(label, shot_idx, shot_config)— convert a single canonicalLabelobject into a format-specific dict. Called once per annotation.serialize_image_labels(transformed)— used by per-image formats (YOLO, Pascal VOC) to produce file content from a list of transformed annotations for one image. Returns a collection of(file_type, extension, content_string)tuples.aggregate_batch(annotations, batch_metadata)— used by global formats (COCO) to build the full batch structure from all transformed annotations.finalize(aggregated)— called once per batch. For COCO this produces the final JSON. For YOLO it producesdata.yamland the image list.txt. Returns(file_type, extension, content_string)tuples.
File routing is handled by get_subdir_for(shot_id, file_type) and get_filename_for(shot_id, file_type), which the pipeline uses to determine where to write each returned tuple.
Implemented Formats
Ultralytics YOLO
Registry key: "Ultralytics YOLO"
One .txt label file per image. Each line is class_id cx cy w h in normalized coordinates. Additionally produces a data.yaml (dataset config) and a .txt file listing image paths, both at batch finalization.
Directory layout:
<save_path>/
├── images/
│ └── <prefix>_<shot_id>.png
└── labels/
└── <prefix>_<shot_id>.txt
With split set (e.g. "train"):
<save_path>/
└── train/
├── images/
└── labels/
Config keys accepted via format_cfg:
- bbox_precision (int, default 3): decimal places for bbox coordinates.
- split (str, default ""): dataset split name.
COCO (Bounding Box)
Registry key: "COCO"
Produces a single instances.json per batch following the COCO detection format. Images are accumulated during transform_annotation, categories are built during aggregate_batch, and the final JSON is written in finalize.
Directory layout:
<save_path>/
├── images/
│ └── <prefix>_<shot_id>.png
└── instances.json
Config keys:
- bbox_precision (int, default 2): decimal places for bbox coordinates.
- split (str, default ""): dataset split name.
Pascal VOC
Registry key: "Pascal VOC"
One XML annotation file per image. The XML structure follows the standard Pascal VOC schema with <annotation>, <object>, and <bndbox> elements.
Directory layout:
<save_path>/
├── images/
└── annotations/
└── <prefix>_<shot_id>.xml
Config keys:
- split (str, default ""): dataset split name.
- xml_indent (int, default 2): indentation for pretty-printed XML.
CVAT XML (Images)
Registry key: "CVAT XML Images"
Produces a single XML file compatible with CVAT's image annotation format.
Registering a Custom Format
From inside the extension (decorator)
If your strategy lives within the extension package, use the register_strategy decorator:
from ext.core.io.registry import LabelingFormatRegistry
from ext.core.io.io_strategy import IOStrategy, FormatSpecification, StorageSpec
@LabelingFormatRegistry.register_strategy("My Custom Format")
class MyFormatter(IOStrategy):
def __init__(self, write_config, config):
super().__init__(write_config, config)
# read your format-specific keys from config dict
From a Blender script (Python API)
If you want to register a format at runtime from Blender's scripting tab without modifying the extension source, use register_new:
class MyFormatter(IOStrategy):
# ... implement all abstract methods as above ...
pass
LabelingFormatRegistry.register_new("My Custom Format", MyFormatter)
register_new validates that the provided class is a subclass of IOStrategy and raises RuntimeError if it is not. It does not validate that all abstract methods are implemented — that will raise at instantiation time when the pipeline tries to use it.
Format Configuration
Format-specific settings are passed as a plain dict at instantiation time, sourced from the UI config. There is no fixed schema — each strategy reads only the keys it needs via config.get(...).
When implementing a new strategy, document which keys your __init__ reads. Unrecognized keys are silently ignored since strategies use config.get(key) rather than direct access.
Notes
COCO_SEGMENTATIONandCOCO_KEYPOINTSare registered in the registry but not yet implemented (stub classes).- The
aggregation_strategyfield inFormatSpecificationcontrols which pipeline path is taken. A strategy that sets"global"must implementaggregate_batchandfinalizemeaningfully and may raiseNotImplementedErrorinserialize_image_labels(asCOCOFormatterdoes).