COCO ↔ YOLO round trip, measured
Converting between COCO and YOLO preserves geometry almost exactly — a full cycle cannot move a box corner by more than 9 × 10⁻⁴ of a pixel, and a 128-shape sweep gets nowhere past 6 × 10⁻⁴ — and loses everything that is not geometry, because a YOLO row has no field for it. This page separates the three outcomes: preserved, converted into something else, and lost. Every line comes from a test that runs the real exporters and parsers in both directions.
Reproduce it: npx jest src/offline/dataset-io/coco-yolo-round-trip.test.ts. The test prints the measurements quoted here and fails if a converter changes, which is the mechanism that keeps this page true.
How this was measured
A project is exported to COCO, the archive is parsed back in, the result is exported to YOLO, and that archive is parsed back in again — through the same code the application uses, not a simplified stand-in. Then the same trip is run in the opposite direction. The image is 1200 × 800, and no coordinate is a round fraction of it, so a normalisation error cannot hide behind a convenient number.
The trip is measured over the standard layer only — the plain COCO and YOLO files a third-party tool reads. AnnotateIt also writes an annotateit-metadata.json sidecar beside them, which makes its own round trip exact; measuring that instead would have answered a different and much easier question. The last section covers what the sidecar changes.
What is preserved
| Data | Result | Measured |
|---|---|---|
| Bounding box geometry | Preserved | Worst corner 6 × 10⁻⁴ px over a 128-shape sweep, against an arithmetic ceiling of 9 × 10⁻⁴ px |
| Segmentation polygon geometry | Preserved, vertex for vertex | Worst vertex 4 × 10⁻⁴ px over the same sweep, ceiling 6 × 10⁻⁴ px; vertex count unchanged |
| Class identity and class order | Preserved | obj.names index 0 stays the first label; COCO category ids stay 1-based in label order |
| Image association and dimensions | Preserved | COCO stores width/height; YOLO recovers them from the image bytes |
| Images with no objects | Preserved by both | A COCO image record with no annotations; an empty YOLO .txt |
The precision result is worth stating plainly because it contradicts a common worry: normalising to fractions and back does not meaningfully move an annotation. Six decimal places on a 1200-pixel image is a four-ten-thousandth of a pixel. Whatever you lose converting COCO to YOLO, it is not the position of your boxes.
What is converted into something else
These are not losses in the sense of data disappearing, and not preservation either. The object arrives, in a different shape, because the target has no way to say what the source said.
| Data | Becomes | Consequence |
|---|---|---|
| A pixel mask (COCO RLE) | One traced polygon row per connected component | Holes are filled and thin bridges disappear; a two-blob mask with a hole came out as 2 polygon rows |
| An open polyline | A closed ribbon polygon of the stroke width | Measured as an 8 px tall ribbon from a zero-height line, in both COCO and YOLO |
| A rotated box | Its axis-aligned bounds | The angle is gone from both formats; a 90°-rotated 200 × 100 box arrives as 100 × 200 |
| One COCO instance with several rings | One separate annotation per ring | Geometry is right, but the rings are no longer known to be one object — 1 instance became 2 |
| A polygon imported into a detection project | Its bounding box | Chosen by the project type you import into, not by the format |
| The COCO area field | Recomputed from the geometry | A triangle’s 56 700 px² came back as 56 699.88 — recalculated, not carried |
The multi-ring case is the one most likely to surprise. A COCO annotation is allowed to be one object described by several polygons — a car split by a lamp post in front of it. YOLO has no way to group rows, so the object arrives as two objects. If instance identity matters more than format ubiquity, COCO or Datumaro is the export to choose.
Where data is lost
| Data | COCO | YOLO |
|---|---|---|
| Per-object attributes | Kept in an attributes block (the CVAT dialect) | Lost — the row is five numbers |
| iscrowd | A first-class field | Lost |
| Keypoints and poses | person_keypoints records with x/y/visibility | Not offered — YOLO is not available for a keypoint project |
| Mask holes and disjoint detail | Kept exactly, as native RLE | Lost to outline tracing |
| Instance grouping of multiple rings | Kept | Lost |
| Rotation angle | Lost | Lost |
| Text prompts, label colours, hierarchy, task schema | Lost | Lost |
Read across that table and the summary is simple: COCO is the richer of the two, YOLO is the more universally trained-on, and the conversion is lossy in exactly one direction. Going YOLO → COCO invents nothing and loses nothing, because everything a YOLO file can say, COCO can also say. Going COCO → YOLO discards whatever the row grammar cannot express.
The other direction, in detail
| YOLO input | Result in COCO |
|---|---|
| Detection row | Exact absolute pixels in bbox, empty segmentation, iscrowd 0 |
| Segmentation row | A polygon ring in segmentation with a shoelace area |
| Pose row | Rejected on import whenever the dataset declares kpt_shape or flip_idx, in any YAML spelling; an archive declaring neither is genuinely ambiguous |
| OBB row (8 values) | Read as a four-point polygon; the angle is not reconstructed |
| Undeclared class index | Rejected — the row cannot be attributed to a class |
| Coordinates outside 0–1 | Rejected rather than clamped |
What changes with the AnnotateIt sidecar
Every annotated export carries an annotateit-metadata.json file next to the standard ones. The YOLO files remain a plain YOLO dataset — the same five-number rows, readable by any trainer — while re-importing that same archive into AnnotateIt restores the attributes, prompts, label hierarchy, exact shapes and project task schema that the standard layer dropped. Verified in the same test: an attribute that is provably absent from the YOLO rows comes back from the sidecar.
So there are two different questions with two different answers. “What survives COCO → YOLO?” is answered by the tables above. “What survives AnnotateIt → YOLO → AnnotateIt?” is answered by: everything, as long as you keep the sidecar in the archive.
Choosing between them
| If you need | Export |
|---|---|
| An Ultralytics training run with a train/val/test layout | YOLO in split mode |
| Pixel masks that keep their holes | COCO (native RLE) |
| Keypoints and skeletons | COCO person-keypoints |
| Per-object attributes or crowd regions | COCO |
| Rotated boxes, open polylines, or anything with the angle intact | Datumaro |
| A faithful return trip into AnnotateIt | Any format — keep the sidecar |