Repository navigation
Conversation
biahub track now writes tracks inside the image plate FOV as <plate>/<row>/<col>/<fov>/tracks.geff (GEFF, zarr v3) plus a tracks CSV. - viscy_data.tracks.read_fov_tracks: read <root>/<fov>/tracks.geff when present and return the ultrack CSV layout (track_id, t, [z], y, x, id, parent_track_id, parent_id; parent_* rebuilt from edges when not stored), otherwise fall back to exactly one *.csv as before. Replaces the three duplicated CSV readers in cell_index, dynaclr index and triplet. - ExperimentEntry.tracks_path is optional and defaults to data_path. - geff is an optional viscy-data extra ([tracks]), lazily imported; dynaclr depends on viscy-data[tracks]. - airtable prepare: copy each FOV's tracks.geff + tracks CSV into the VAST plate for in-plate datasets instead of rsyncing a separate tracking.zarr (unchanged for older datasets); tracks_path no longer required for the parquet readiness report. - Docs: build-cell-index recipe and airtable README. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…plate labels `biahub track` as merged (Biohub/biahub#413) still writes `tracks.geff` by default, but a run that names its tracked label (`output_label`) writes `tracks_<name>.geff` / `tracks_<name>_<fov>.csv`, and the FOV now also holds the label images: `labels/<model>` from `biahub segment` and `labels/tracks_<model>` from track, which the GEFF's `seg_id` indexes. - `read_fov_tracks` reads exactly one `tracks_<name>.geff` when there is no `tracks.geff` (several raise), before the CSV fallback. - airtable `prepare`: in-plate detection matches `tracks*.geff`; the copy step carries the GEFF(s), the CSV and `labels/` into the VAST FOV, since `biahub concatenate` copies image arrays only. - Docs name the layout; tests cover the named GEFF and the labels include. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
ieivanov
marked this pull request as ready for review
October 24, 2026 01:01
Contributor
Author
|
@edyoshikun and team, Biohub/biahub#413 merged so new reconstructions will have tracking in geff format. We've still kept the .csv files as legacy format. I suggest merging this PR which will allow you to read the new label arrays and geff tracks. |
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
biahub tracknow writes tracks inside the image plate FOV:<plate>/<row>/<col>/<fov>/tracks.geff(a GEFF graph, zarr v3, geff >= 1.3) and, for now, alsotracks_<row>_<col>_<fov>.csv. This PR lets DynaCLR read them, with no separatetracking.zarr.Pairs with Biohub/biahub#413 (merged; closes Biohub/biahub#360 / #361). Updated to its merged layout: the FOV also holds the label images (
labels/<model>frombiahub segment,labels/tracks_<model>frombiahub track,(T, 1, Z, Y, X)uint32), the GEFF'sseg_idis thetrack_idpainted into the tracked one, and a track run that names its tracked label (output_label) writestracks_<name>.geff/tracks_<name>_<fov>.csvinstead oftracks.geff/tracks_<fov>.csv.Changes
viscy_data.tracks.read_fov_tracks(tracks_root, fov_path)(new): reads<root>/<fov>/tracks.geffwhen it exists and returns the same DataFrame as the ultrack CSV:track_id, t, [z], y, x, id, parent_track_id, parent_id.id.parent_idis the source node of the incoming edge (-1for roots).parent_track_idis read from the node property if stored. Otherwise it is rebuilt as in ultrack'sto_tracks_layer: the track of the parent of the track's first node, constant over the track,-1for root tracks.geff.core_io.read_to_memory, no networkx). The track property name is taken fromtrack_node_props["tracklet"]when present.tracks.geff, exactly onetracks_<name>.geffis read (several raise); without any GEFF it falls back to the existing exactly-one-*.csvbehavior, with the same error messages.cell_index._build_experiment_tracks,dynaclr.data.index._load_experiment_fovsandTripletDataModule._align_tracks_tables_with_positions.ExperimentEntry.tracks_pathis now optional and defaults todata_path. Empty andnullvalues also default todata_path. The parquet schema still has thetracks_pathcolumn, filled with the resolved path.geffis an optional extra:viscy-data[tracks], lazily imported, with a clearImportError. It is also included in[all]and the test group.dynaclrdepends onviscy-data[tracks,triplet], because its pyproject says required runtime deps should not sit behind extras.prepare:*/*/*/tracks*.geff),01_concatenate.shcopies each FOV's GEFF(s),tracks_*.csvandlabels/(the segmentation and tracked label images the GEFF links to) into the same FOV of the VAST plate (biahub concatenateonly copies image arrays). Notracking.zarris created in that case.prepare statusreportstracking: in-plate.tracks_pathis dropped from the parquet-readiness required fields.build-cell-index.mdrecipe gains a "Where tracks are read from" section. The airtable README is updated.Note on
uv.lockThe lock on
mainwas already out of date (uv lock --checkfails on main). It still had orphaned entries such as napari, ultrack and an editablenapari-geffpath. Re-locking removes those and bumps tracksdata from a git rev to rc11; the same happens when re-lockingmainwith no changes. The only change caused by this PR is geff 1.2.0 -> 1.3.1.Test plan
packages/viscy-data/tests/test_tracks.py:write_arrays(zarr_format=3), with a division and shuffled node order. It is checked against the equivalent ultrack CSV in 2D and 3D, withparent_track_idboth derived and stored.track_idraises.tracks_pathdefaults todata_path.build_timelapse_cell_indexfrom an in-plate GEFF withouttracks_path, including the lineage across the division.applications/airtable/tests/test_prepare.py(concatenate script variants, in-plate detection incl.tracks_<name>.geff, thelabels/include). The generated in-plate copy step was also run with bash on a synthetic NFS/VAST layout with a named GEFF and two label images: GEFF, CSV andlabels/arrive, the image array does not.pytest packages/viscy-data/tests: all pass (2 skips: pycocotools, CUDA).pytest applications/airtable/tests: 85 passed.pytest applications/dynaclr/tests/{test_index,test_experiment,test_flat_parquet_e2e,test_dataset}.py: 104 passed.biahub trackoutput (2 FOVs of 2026_08_11_A549_SEC61B_DENV_debug/v8, tracked with feat(segment,track)!: segment and track write into the plate; labels and tracks.geff (#360, #361) biahub#413):read_fov_tracksontracks.geffequals the CSV for both FOVs.🤖 Generated with Claude Code