Write a converter for your format¶
A track converter reads one raw file and returns one schema-valid table: per frame,
per individual, with frame, time, id, group, sequence, X, Y. Keypoints
(poseX* / poseY*) are optional. mosaic writes the result to
tracks/<variant>/<group>__<sequence>.parquet and indexes it.
Check the track formats reference first — eight converters ship, and one may already read your files.
Two worked examples¶
Both notebooks define a converter, run it, and check the result. Each covers a different hard case, and together they are the fastest way in.
- collective-motion-shiners.ipynb — several files make one
recording, and neither identity nor frame numbering survives the joins. Shows
merges_per_sequence, a custom schema lifting one forbidden column, and coordinates converted back to pixels. - collective-motion-zebrafish.ipynb — a tracker that locates no keypoints at all, and metadata that disagrees with its own trajectories.
track_converter_template.py is a skeleton to copy;
deeplabcut.py is the smallest shipped converter.
Three questions¶
- How do files and sequences correspond? One file, one sequence is the
default. For one file holding several sequences, set
enumerable = Trueand implementenumerate_sequences, and pass--multi-sequences-per-fileon the source — without the flag the file collapses into a single entry, silently. For several files making one sequence, setmerges_per_sequence = Trueand overridesequence_from_stem. The two are mutually exclusive. - Does your tracker measure something
mosaic_v1forbids? Velocity, speed, heading and the weighted centroid belong to features. If your tracker genuinely measures one, declare a schema thatextends="mosaic_v1"andallowsexactly that column; otherwise emitmosaic_v1and let a feature derive it. - Are your coordinates already video pixels?
mosaic_v1says they are, and nothing checks it — a scaled table validates cleanly and every downstream distance is wrong by a factor nobody recorded. Convert in the converter, reading the factor from the file rather than from a parameter.
What a tracker reports, and in what units has the rules behind all three.
Declare output_schema¶
Set it on the class. It defaults to the legacy trex_v1, and forgetting it fails
quietly: a mosaic_v1-shaped table is recorded as trex_v1, and the error surfaces
much later, at the first feature run mixing those entries with real mosaic_v1
ones. Say mosaic_v1.
Shipping it inside mosaic¶
mosaic discovers nothing on its own — no entry-point scan, no import hook — so where your module lives decides what can reach it.
In a notebook or your own package, import it before the first call naming the
format. This is Python API only: a fresh mosaic process never imports it, so the
CLI refuses the format by name.
In the tree, add the module to core/track_library/ and make two
edits to its __init__.py — from . import myformat, and "myformat" in
__all__. The import line alone fails lint with F401. The CLI then works:
mosaic sources add -m dataset.yaml --kind tracks --path /data/myformat_out \
--patterns '*.npz' --src-format myformat_npz
mosaic scan -m dataset.yaml --kind tracks
mosaic convert-tracks -m dataset.yaml
--patterns must cover your extension. It defaults to *.npy, *.h5, *.csv, so
a .npz format matches nothing and converts zero rows without failing.
What the tests then check¶
tests/core/track_library/test_track_converters.py is parametrized
over the registry, so an in-tree converter inherits five assertions the moment
its import line lands: no entry-identity field reaches the parameter digest,
version is non-empty, the registry key equals the declared src_format, a
merging format overrides sequence_from_stem, and no format claims both
enumerable and merges_per_sequence. Add a per-format test beside them,
converting a synthetic fixture written into tmp_path.