Skip to content

fix(formats): dropped is not degraded, and one word for both was a lie (#158) - #165

Merged
JArmandoAnaya merged 1 commit into
mainfrom
feat/beta2-158-degraded-geometries
Jul 31, 2026
Merged

fix(formats): dropped is not degraded, and one word for both was a lie (#158)#165
JArmandoAnaya merged 1 commit into
mainfrom
feat/beta2-158-degraded-geometries

Conversation

@JArmandoAnaya

@JArmandoAnaya JArmandoAnaya commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Closes #158. First of the 0.0.1-beta.2 defect fixes, and the one that silently corrupts a training set.

The defect

check_export reported polygon classes as excluded from a YOLO or VOC export. Both exporters then wrote those polygons anyway, as their axis-aligned bounding boxes. A user consented to losing two annotations and received two extra boxes in their training labels under the polygon's own class name — every row well-formed, every class index real, every coordinate in range, and not what the report said.

Exporter.supported_geometries carried one meaning and was read with two intents, both defensible:

So this was never a typo. The model was missing a state: there was no vocabulary for carried, but degraded.

The decision

Option B, argued on the issue: give the port a third state rather than delete the conversion. Option A would have made the report true by removing polygon-derived boxes from YOLO and VOC, which is a real detection-training use.

What changed

Exporter new degraded_geometries{polygon} for yolo/voc, empty for coco (writes a polygon as a polygon) and dummy (writes nothing). Disjoint from supported_geometries; supported wins if a plugin says both.
ClassCompatibility supported: boolstatus: supported / degraded / dropped. supported and carried survive as properties, so no report carries a pair that disagree.
ExportCompatibility excluded_annotations/excluded_assets count dropped only; new degraded_annotations/degraded_assets; new degraded property beside excluded.
reason says what actually happens: "yolo writes a polygon as its bounding box; the shape is lost" versus "yolo cannot place a classification_tag and drops it".
compatible unchanged in meaning — false for degraded or dropped, so the allow_lossy gate did not move. Only the accounting became true.
GET /formats publishes degraded_geometries, because geometries: ["bbox"] alone reads as the whole answer and for yolo it left out that a polygon is written at all.
visionset export two lines instead of one — what disappears and what arrives coarser are different decisions.

The kernel does not know how a plugin reduces a geometry, so the specific sentence is written only when the format's own declarations make it inevitable (it reduces a shape and can write a box). Anything else gets the general sentence rather than a guess.

The guard, which is the point

tests/formats/test_report_agreement.py (new) exports one release holding 3 boxes, 2 polygons and 1 tag through every installed exporter and counts what each actually wrote — label rows resolved through data.yaml's own index, <object> elements by <name>, COCO instances by category_id. It asserts:

  • excluded_annotations equals everything held minus everything on disk — arithmetic over the artifacts, never a restated expectation;
  • a dropped class contributes nothing, a degraded or supported class contributes all of its annotations;
  • a written class's reason does not say "cannot".

Verified by mutation: restoring yolo's old declaration (degraded_geometries = frozenset()) turns three named tests red, including the two acceptance-criterion ones.

COUNTERS plus a declared NON_WRITING set must cover every registered format, so a fourth exporter either gets compared against its own output or is deliberately exempted — criterion 5.

Ledger

  • No migration. FORMAT_VERSION stays 12; VERSION stays 0.0.1b1.
  • openapi.json and frontend/ui-core/src/generated/api.ts regenerated together, never hand-edited. This is the milestone's one permitted spec move.
  • Kernel purity and the delivery-sibling contract both still KEPT.
  • Breaking for API clients: ClassCompatibilityOut.supportedstatus, and ExportCompatibilityOut gains two fields. Noted in CHANGELOG.md.

Checks run locally

  • uv run pytest tests/ — exit 0, 2059 collected
  • uv run mypy src/visionset — no issues, 116 files
  • uv run ruff check / ruff format — clean
  • uv run lint-imports — 2 contracts kept, 0 broken
  • pnpm -r build, pnpm test (711 annotator + 107 ui-core vitest, 22 node:test), pnpm -r lint, pnpm -r typecheck — all green

Docs

docs/releases.md gains "Dropped is not degraded, and one word for both was a lie" with the argument and the guard, so it is not re-litigated; docs/tutorial.md shows both CLI lines; CHANGELOG.md gains an Unreleased entry.

#158)

`check_export` reported polygon classes as excluded from a YOLO or VOC export
while both exporters wrote those polygons anyway, as their axis-aligned bounding
boxes. A user was told two annotations would be lost, consented, and received two
extra boxes in their training labels under the polygon's own class name — well
formed in every way a validator can check, and not what the report promised.

`Exporter.supported_geometries` carried one meaning and was read with two intents,
each internally consistent: `_compatibility` read "not supported" as *absent from
the output*; the exporters read it as *convert to something I can write*, which is
the capability #62 and #64 deliberately included. The model had no word for
**carried, but reduced**.

Option B, as decided on the issue:

- `Exporter` gains `degraded_geometries` — `{polygon}` for yolo and voc, empty for
  coco (which writes a polygon as a polygon) and dummy (which writes nothing).
- `ClassCompatibility.supported: bool` becomes `status`, one of `supported` /
  `degraded` / `dropped`; `supported` and `carried` survive as properties, so no
  report can carry a pair that disagree.
- `excluded_annotations` / `excluded_assets` count **dropped only**, with
  `degraded_annotations` / `degraded_assets` beside them. Every `reason` now says
  what happens to that class rather than "cannot write" for a class that is
  written.
- `compatible` is still false for either, so the `allow_lossy` gate did not move.
  Only the accounting became true.

The guard ships with it, and it is the part that was missing: a new
`tests/formats/test_report_agreement.py` exports one release holding all three
geometries through **every installed format** and counts the annotations in the
written label files, XML documents and COCO JSON — asserting the report against
the bytes rather than against an expected number. Verified by mutation: restoring
yolo's old declaration turns three named tests red. A fourth exporter either lands
a counter there or is declared non-writing, so the divergence cannot come back
unnoticed.

`GET /formats` publishes `degraded_geometries` for the same reason at that
surface. `openapi.json` and the generated client are regenerated; `FORMAT_VERSION`
stays 12 and no migration is added. `docs/releases.md` records the distinction and
the argument, and `docs/tutorial.md` shows the two CLI lines.

Closes #158
@JArmandoAnaya
JArmandoAnaya merged commit 4523ccb into main Jul 31, 2026
12 checks passed
@JArmandoAnaya
JArmandoAnaya deleted the feat/beta2-158-degraded-geometries branch July 31, 2026 23:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

formats: the export report says polygons are not carried while YOLO and VOC write them as bounding boxes

1 participant