Import tracks you already have¶
If tracking was done somewhere else, mosaic can read and convert the files for further analysis. Three steps: declare where the files are, scan, convert.
mosaic sources add -m dataset.yaml --kind tracks \
--path /data/trex_out --patterns '*.npz' --src-format trex_npz
mosaic scan -m dataset.yaml --kind tracks
mosaic convert-tracks -m dataset.yaml
That writes tracks/<variant>/<group>__<sequence>.parquet, one per entry, and a row
per entry in the tracks index. From here every feature in the library reads them.
The same thing from Python:
from mosaic.core.dataset import open_dataset
ds = open_dataset("dataset.yaml")
ds.scan_tracks()
ds.convert_all_tracks()
--source-format converts the raw files of one format only, and --json reports the
tracks variants the conversion wrote into.
Convert as a recipe step¶
The conversion can be the first step of a recipe, so one request converts the tracks and computes on them. Scanning stays a separate command: a conversion step whose format has no scanned file is refused when the recipe is planned.
{"id": "tracks", "type": "op", "kind": "convert-tracks",
"params": {"source_format": "trex_npz"}},
{"id": "speed", "type": "feature", "feature": "speed-angvel",
"inputs": ["tracks"], "tracks": {"step": "tracks"}}
params takes the converter's settings, and refuses one the converter does not
declare. One step converts one format. The tracks reference pins the variant the
step writes, which the plan names before the conversion runs. It is the variant of the
uploaded files: a promoted correction converts into a variant of its own, which a
recipe names by tracks_run_id like any other.
On a dataset with no tracks yet, the plan cannot know which entries the conversion
will write, so a step whose identity covers its entries, such as extract-templates,
shows as unresolved until the conversion has run. Each step names its run when it
starts.
The formats mosaic reads¶
Eight converters are registered. --src-format names one, and the choice also fixes
which schema the output carries.
--src-format |
Reads | Emits |
|---|---|---|
trex_npz |
TRex per-individual .npz that records cm_per_pixel (TRex 2.0.0 and later) |
trex_v2 |
trex_npz_scaled |
The same export from before TRex recorded that factor, with the factor supplied | trex_v2 |
trex_npz_cm |
The same export where the factor is not recoverable, kept in centimeters | mosaic_cm_v1 |
sleap_analysis_h5 |
SLEAP HDF5 from sleap-convert --format analysis |
mosaic_v1 |
deeplabcut |
DeepLabCut .csv / .h5, single- and multi-animal — and Lightning Pose, which writes the same shape |
mosaic_v1 |
ultralytics_tracks |
Ultralytics tracker predictions parquet | mosaic_v1 |
calms21_npy |
CalMS21 .npy arrays |
mosaic_v1 |
calms21_json |
The .json spelling of CalMS21 |
mosaic_v1 |
The track formats reference lists each with its parameters, and the schema families explain why the three TRex rows differ.
Several recipes over one dataset¶
A dataset can hold more than one tracks variant: the same entries converted by
different recipes, each in its own tracks/<variant>/ directory. Different entries
carrying different variants enables comparison of tracking results.
A variant is named <kind>.<version>-<digest>, such as
convert-trex_npz.0.3-6bb5efbf05, and that name is its directory under tracks/.
When an entry carries exactly one variant, features resolve it automatically. When it
carries more than one recipe, mosaic raises an error naming them, and you name the one
to read. Rows converted before mosaic recorded variants do not count, because a named
variant supersedes them.
from mosaic.behavior.feature_library import SpeedAngvel
ds.run_feature(SpeedAngvel(), tracks_run_id="convert-trex_npz.0.3-6bb5efbf05")
ds.load_tracks("day1", "trial01", run_id="convert-trex_npz.0.3-6bb5efbf05")
build_manifest and load_values in mosaic.core.pipeline take tracks_run_id=
too. load_tracks and drop_entries take the same value as run_id=. An empty
string, "", names the tables converted before mosaic recorded variants, whose index
rows have no run_id.
To stop an entry resolving to a variant, drop that variant's index rows. The parquet
stays on disk unless you also pass delete_files=True:
from mosaic.core.scope import Scope
ds.drop_entries(
scope=Scope(entries=[("day1", "trial01")]),
run_id="convert-trex_npz.0.3-6bb5efbf05",
)
A corrected track set¶
A track set you corrected by hand, such as TRex output with its identity swaps fixed,
cannot be recomputed. It becomes source data. promote_correction copies it into
tracks_raw/ as the entry's next revision, corrected.rev1, corrected.rev2, and
indexes it:
from mosaic.core.pipeline.promotion import promote_correction
report = promote_correction(
ds, "day1", "trial01", "/data/fixed/trial01_fish0.npz",
src_format="trex_npz", derived_from="trex.0.2-abc123def0",
)
report.blocked # what the promotion would make stale
That call only reports. Pass apply=True to copy and index the file, then run
ds.convert_all_tracks(). A promotion is blocked while anything built from the entry's
raw tracks exists, its converted table included. force=True proceeds and deletes
nothing, leaving those results stale until you re-run them. Pass a list of paths for a
correction spread over several files. derived_from names the tracker run you
corrected, which lets mosaic sweep-tracking reclaim that run's working directory
without waiting out its retention window. promote_correction raises ValueError
when derived_from names a run that tracked a media variant,
whatever force says, because that run's output is in the variant's pixels and frames.
Only the newest revision converts, and it converts into a variant of its own beside
the uncorrected one. Name it as above, or drop the uncorrected variant's rows. In the
tracks index, which read_tracks_index(ds) from mosaic.core.pipeline.tracks_index
returns, the correction's row has a source_abs_path whose file name begins
corrected.rev<N>, such as corrected.rev1.npz.
If your format is not on the list¶
Write a converter — it is one class and one method. See Write a converter.