Skip to content

feat(formats): the yolo format becomes the ultralytics dialect, beside yolov5-yaml - #792

Closed
JArmandoAnaya wants to merge 3 commits into
feat/targets-t1-domainfrom
feat/targets-t2-dialects
Closed

feat(formats): the yolo format becomes the ultralytics dialect, beside yolov5-yaml#792
JArmandoAnaya wants to merge 3 commits into
feat/targets-t1-domainfrom
feat/targets-t2-dialects

Conversation

@JArmandoAnaya

Copy link
Copy Markdown
Contributor

Part of the export-targets epic (#784). The single yolo format becomes the ultralytics dialect, a second dialect yolov5-yaml writes the older descriptor grammar for YOLOv7, and the ten trainer targets the epic names are declared on the two exporters. yolo is accepted as an alias of ultralytics for one release and then removed.

The dialect grammar

A dialect is the descriptor grammar; format_name names it. A target is the model a person will train, and it resolves to exactly one dialect.

ultralytics writes data.yaml as path: ., one key per fold present, and names as a mapping from index with no nc. The task is derived from the release, never chosen: segment when it holds any polygon (every polygon as its vertices, every box as its four corners), classify when it holds classification tags and no box or polygon (the class tree <fold>/<class>/<image>, one copy per tag, a directory for every tag-capable class, no yaml), detect otherwise. Its geometry declaration is {bbox, polygon, classification_tag} supported and nothing degraded. It declares yolo26, yolo12, yolo11, yolov10, yolov9, yolov8, yolov6, yolov5, yolov3, family ultralytics-yolo, with the task sets the Ultralytics models page lists and hints (640, 640), letterbox, trainer resizes, augmentation common.

yolov5-yaml writes train: ./images/train, val: ./images/val, test: ./images/test when present, nc, and names as a list; detection only, so a polygon is written as its bounding box and reported degraded. It declares yolov7, family community-yolo. Both dialects share the layout and label arithmetic in formats/_yolo_writer.py, which is not part of the port.

The registry applies ALIASES = {"yolo": "ultralytics"} in pick(), which now answers (plugin, deprecated_alias); the CLI turns the marker into a warning in the next slice. exporters() validates every declaration at the scan: a target promising a geometry its format never writes, or one name declared by two formats, is refused there. validate_targets judges a target against supported ∪ degraded, so TuSimple's {polyline} target validates. _compatibility takes an optional target: a geometry the format writes whole but the target does not carry is a reported drop, consented through allow_lossy; a geometry no annotation can carry is never a row.

Class ids are positions in the frozen schema order; LabelClass carries no number of its own, so a gap is unrepresentable and no refusal exists for it.

Recorded, for the reviewer

  • Today's yolo exporter omitted path: deliberately and wrote nc:, with a smoke test proving the export loads from any working directory. In ultralytics 8.4.124 a relative path that exists is kept as it is (data/utils.py, the check_det_dataset path resolution), and . always exists, so path: . resolves the splits against the working directory of the loading process. The round-trip test and examples/thirty_minute_flow.py therefore load from inside the export directory, and the docs say so. The descriptor is as the epic specifies; the portability the omitted key bought is gone.
  • The classification exporter's layout is images/<fold>/ plus labels.csv, not the class tree the trainer reads, so the classify layout is written by the ultralytics dialect itself.
  • A tag beside a box or polygon is dropped by the derivation while the static declaration calls classification_tag supported (a tags-only release is written as a class tree). The report misstates that one row for mixed releases; consent is still asked because the format is lossy. test_report_agreement.py pins the shape as what it is and compares the dialect per layout.
  • The Exporter port carries no target, so the written layout does not yet depend on which target was named; narrowing the output to a target's task set needs the export seam the next slice wires.
  • The multi-tag case in a class tree copies the image once per tag, which is the only representation that layout has.

Found, not fixed

  • formats/classification/__init__.py's docstring says the kernel enforces no (asset, class) uniqueness for a classification tag; AnnotationService refuses a duplicate with DuplicateClassificationTag.

Stack

Position 2 of the export-targets stack (#784); based on feat/targets-t1-domain (PR #788).

Checks

Targeted: uv run ruff check ., uv run ruff format --check ., uv run mypy src/visionset, uv run lint-imports, uv run pytest tests/formats tests/kernel tests/cli tests/mcp tests/server tests/jobs tests/examples tests/packaging tests/architecture -n 4 (3903 passed, 9 skipped), uv sync --group yolo && VISIONSET_REQUIRE_ULTRALYTICS=1 uv run pytest tests/formats/test_ultralytics_roundtrip.py (3 passed), scripts/export_mcp_tools.py --check, scripts/export_openapi.py (no diff).

Full gate (bash scripts/check.sh) on the final tree:

4526 passed, 33 skipped in 36.65s
All checks passed.

check.sh: PASSED  ran=python,frontend,generated,browser  skipped=docs

An export target is the model a person will train; each exporter now
declares its targets beside the five existing port attributes. The kernel
gains the frozen ExportTarget model with its Task, TargetFamily and
ResizeStrategy vocabularies and the PreprocessingHints a target carries,
pure resolution from a target name to the declaring exporter, and a
validate_targets check holding every target's geometries within its
exporter's own.

ExportTargetNotFound, ExportTargetConflict and InvalidExportTarget are
typed in kernel/errors.py and mapped in server/errors.py — no route
raises them yet; the exact-correspondence test keeps the table total.

Every shipped exporter declares one self-named target, family other,
with no trainer tasks, so discovery keeps returning them under the
widened port. TuSimple's target names the polyline it writes, because
its supported set is empty — everything it carries arrives degraded.
…rrows the compatibility report

validate_targets checks a target's geometries against what the exporter writes at all,
supported and degraded together, and validate_installed applies it across every installed
exporter while refusing a target name two of them declare. _compatibility takes an optional
target: a geometry the format writes whole but the target does not carry is a reported drop,
and a geometry no annotation can carry is never a row.
…e yolov5-yaml

The ultralytics dialect derives its task from the release: segment when it holds a polygon,
classify when it holds only classification tags, detect otherwise. Its data.yaml carries
path: ., one key per fold present, and names as a mapping with no nc. yolov5-yaml writes the
YOLOv5 descriptor grammar for YOLOv7 over the same layout, extracted into _yolo_writer. The
registry accepts yolo as an alias of ultralytics and answers the alias beside the plugin, and
validates every declaration at the scan. The nine Ultralytics targets and yolov7 are declared.
@JArmandoAnaya

Copy link
Copy Markdown
Contributor Author

Closed by GitHub when its base branch was deleted after #788 merged; the same change, rebased onto main, continues as #798.

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.

1 participant