FAQ

Formats

Converting between dataset formats without corrupting them

A conversion checklist that catches the silent failures: coordinate conventions, class index drift, lost negatives and dropped metadata.

Format conversion is the most routine thing in a data pipeline and one of the most common sources of a training run that mysteriously underperforms. The failures are quiet: nothing raises, the files look right, the model just learns less than it should.

The four things that go wrong

Across detection, segmentation and text, nearly every corrupted conversion falls into one of four buckets. Knowing them turns debugging from a search into a checklist.

  1. Coordinate convention. Corner versus centre, absolute versus normalised, and which corner is the origin. A corner/centre mix-up shifts each box by half its size, so it looks fine on small objects.
  2. Class index drift. Non-contiguous source ids, alphabetical reordering, or an off-by-one from a background class that one format reserves and the other does not.
  3. Lost negatives. Images with no objects vanish, because the converter iterates over annotations rather than over images. Your model loses its only signal about what absence looks like.
  4. Dropped metadata. Licence, source URL, split assignment, difficulty flags and per-sample provenance are not part of the geometry, so converters routinely discard them. You notice when someone asks whether you are allowed to ship the model.
Left: a radar image of a ship with a tilted green box, its corners labelled A to D and its centre and angle marked on x and y axes. Right: an XML rotated_bndbox element with centre x 276, centre y 109, width 29, height 153, theta 46.59, and the coordinates of points A, B, C and D.
A rotated ship box in SSDD and the XML that stores it. The same box is written twice: as a centre, width, height and direction angle θ (here 46.59), and as four corner points A to D. Detectors expect one encoding or the other, so a converter must know which it is reading, and how the angle is measured. Image: Figure 5 of the article · CC BY 4.0 · from Zhang et al. 2021, Remote Sensing, “SAR Ship Detection Dataset (SSDD): Official Release and Comprehensive Data Analysis”, doi:10.3390/rs13183690

Iterate over images, not annotations

This single change prevents the negatives bug. Build an index from image id to its annotations first, then walk the image list — so an image with an empty annotation list is a normal case rather than an absent one.

Driving the loop from images has one side effect: an annotation whose image id matches no image is never visited, so it silently disappears from the output. Building the index first gives you the place to catch those orphan references with an explicit assertion, which is how you catch a truncated or mismatched annotation file before it costs you a training run.

the shape every converter should have
from collections import defaultdict

by_image = defaultdict(list)
for a in annotations:
    by_image[a["image_id"]].append(a)

image_ids = {i["id"] for i in images}
orphans = [a for a in annotations if a["image_id"] not in image_ids]
assert not orphans, f"{len(orphans)} annotations reference missing images"

empty = 0
for im in images:                       # drive the loop from images
    anns = by_image.get(im["id"], [])   # empty is valid, not a skip
    if not anns:
        empty += 1
    write_target_format(im, anns)       # must handle the empty case

print(f"{len(images)} images written, {empty} with no objects")
The same object in three conventions. A corner/centre mix-up shifts a box by half its size, so a wrong conversion looks fine on small objects and is badly wrong on large ones.

Round-trip, then compare

The strongest cheap check is a round trip. Convert A to B, convert B back to A, and compare against the original. Anything that does not survive the journey is something your conversion is losing.

You will not get an exact match, and that is informative. Float precision will differ after normalising and denormalising — compare with a tolerance of a pixel or so. Fields the target format cannot represent will come back missing, which tells you exactly what metadata to carry alongside rather than inside. Counts, though, should match exactly: same number of images, same number of objects per image, same class histogram.

CheckExpectation
Image countidentical
Object count per imageidentical, including zeros
Class histogramidentical after remapping
Box coordinateswithin ~1px after round trip
Total mask areawithin a fraction of a percent
Split assignmentidentical — never re-split in a converter
A diagram with COCO at the centre. Arrows run into COCO from ADE20K, Cityscapes, CVAT, OID, VOC and VOCSEGM, and out of COCO to VOCCALIB; COCO is linked both ways with YOLO, TDG and TDGSEGM, and TDG leads on to FRCNN and SSD.
The conversions one open-source toolset supports, all routed through COCO as its main representation. Only YOLO and the tool's own TDG formats convert both ways, so only they can be round-trip checked here; Pascal VOC goes in, and only a 'VOC-like' calibration format comes back out, so a VOC-to-YOLO job takes two hops through COCO. Image: ISSResearch/Dataset-Converters · MIT repository

Draw it before you trust it

Numeric checks catch convention errors but not perceptual ones. After converting, render five or ten random samples with their new annotations drawn on and look at them. Deliberately include the image with the most objects, the one with the largest object, and one with no objects at all — the three cases where conventions break.

For text conversions the equivalent is printing the fully rendered training string, special tokens and all, for the longest and shortest examples in your set. Both checks take a couple of minutes and catch the class of problem that otherwise surfaces as a disappointing eval.

Keep the original

Convert into a new directory and never in place. Keep the source data, the conversion script and its version together, so the derived set is reproducible rather than merely present.

Write a short manifest next to the output: source path and checksum, script version, date, class mapping, and the count summary your converter printed. When a model trained six months ago needs explaining, that file is the difference between an answer and a shrug.

Questions people ask

What is the safest intermediate format to convert through?

COCO, for detection and segmentation. It represents more than YOLO or VOC can — polygons, crowd regions, keypoints, captions, per-image licences — so converting through it loses less than going through a narrower format. Just remember it cannot represent VOC's difficult flag natively.

How do I keep licence information through a conversion?

COCO has a licenses list and a per-image license id, which makes it one of the few mainstream annotation formats with somewhere to put it (Open Images also lists a licence per image). For formats without a slot, write a sidecar manifest keyed by filename and keep it with the data. Per-sample licensing matters most for mixed-source sets, where a single dataset-level licence is simply wrong.

Should I convert everything up front or on the fly?

Convert once, up front, for anything you will train on repeatedly — it is faster and the result is auditable. Convert on the fly only when the source is genuinely changing underneath you, and then log the conversion counts each run so a drift shows up.

My converted dataset trains worse than the original. Where do I look first?

In order: whether negatives survived (compare image counts, not annotation counts), whether the class mapping is identical, whether the split was preserved, then coordinate conventions on the largest objects. Those four account for most of it, and all are checkable in minutes against the source.

Stop hunting. Start searching.

Tens of thousands of curated datasets, searchable in plain language — with the licence and size checked before you download anything.

Search Dataset Finder →