Decided 2026-08-15:
Classification exports emit one entry per (image, tag) pair — an image with N tags appears N
times, once per tag.
Found while implementing it: there is nowhere to put the rule. Every installed exporter has an
explicit branch that drops a classification tag.
| exporter |
supported_geometries |
what it does with a tag |
coco |
{BBOX, POLYGON} |
_annotation() returns None, row skipped |
yolo |
{BBOX}, degraded {POLYGON} |
_as_box() returns None, _rows() continues |
voc |
{BBOX}, degraded {POLYGON} |
_as_box(): "a classification tag: no location, and VOC's <object> is a location" |
| the five lane formats |
{POLYLINE} |
not written |
dummy |
every geometry |
declares it, writes nothing at all |
The only tag-supporting exporter in the repo is a fake inside tests/cli/test_export_commands.py.
Three tests pin today's behaviour: test_a_classification_tag_produces_no_object_at_all (voc),
…_produces_no_row_at_all (yolo), …_makes_the_release_need_consent (coco).
So the rule cannot be violated by code that does not exist. It is really the specification for a
format nobody has written: an image-classification layout whose content is tags — a folder tree
per class, or a labels.csv.
What it takes
- A module under
src/visionset/formats/, supported_geometries including
GeometryType.CLASSIFICATION_TAG, registered in [project.entry-points."visionset.formats"].
- One row per
(image, tag). This already agrees with the pre-export report, which counts
annotations rather than images (ReleaseService._compatibility, keyed
(label_class, geometry)) — a per-image emitter is the one that would disagree with it.
tests/formats/test_report_agreement.py is a hard gate: a new exporter either lands a counter
there or is declared as one that writes nothing, and
test_every_installed_exporter_is_accounted_for fails until somebody chooses. Its counters read
rows back off disk.
- The report has no per-image line today, so "how many images were duplicated by multiple tags"
needs a home if it is wanted.
Worth deciding at pickup: a new format, or a second task file inside coco. The second changes
bytes for existing consumers and breaks the three pinning tests, so it is the more expensive of the
two despite looking smaller.
Decided 2026-08-15:
Found while implementing it: there is nowhere to put the rule. Every installed exporter has an
explicit branch that drops a classification tag.
supported_geometriescoco{BBOX, POLYGON}_annotation()returnsNone, row skippedyolo{BBOX}, degraded{POLYGON}_as_box()returnsNone,_rows()continuesvoc{BBOX}, degraded{POLYGON}_as_box(): "a classification tag: no location, and VOC's<object>is a location"{POLYLINE}dummyThe only tag-supporting exporter in the repo is a fake inside
tests/cli/test_export_commands.py.Three tests pin today's behaviour:
test_a_classification_tag_produces_no_object_at_all(voc),…_produces_no_row_at_all(yolo),…_makes_the_release_need_consent(coco).So the rule cannot be violated by code that does not exist. It is really the specification for a
format nobody has written: an image-classification layout whose content is tags — a folder tree
per class, or a
labels.csv.What it takes
src/visionset/formats/,supported_geometriesincludingGeometryType.CLASSIFICATION_TAG, registered in[project.entry-points."visionset.formats"].(image, tag). This already agrees with the pre-export report, which countsannotations rather than images (
ReleaseService._compatibility, keyed(label_class, geometry)) — a per-image emitter is the one that would disagree with it.tests/formats/test_report_agreement.pyis a hard gate: a new exporter either lands a counterthere or is declared as one that writes nothing, and
test_every_installed_exporter_is_accounted_forfails until somebody chooses. Its counters readrows back off disk.
needs a home if it is wanted.
Worth deciding at pickup: a new format, or a second task file inside
coco. The second changesbytes for existing consumers and breaks the three pinning tests, so it is the more expensive of the
two despite looking smaller.