itasc.contact_analysis.pipeline

The napari-free orchestration surface for Contact Analysis.

Three composable functions thread the existing headless stages — discovery, per-position build, aggregate — into one pipeline the CLI, notebooks, and (during the napari parallel-run) the Qt studio all drive:

catalog = build_catalog(root, cell_name=…, nucleus_name=…, out_csv=…) build_quantities(catalog) # one .build() per (quantifier, position) tables = aggregate(catalog, out_dir) # pooled, index-keyed CSVs (flat)

Or, end to end from a TOML run-config: run("config.toml").

Everything produced is label-agnostic tidy CSVs: there is no classification step and no plot/figure export — a subpopulation classification and any plots are a downstream, dataset-specific concern, computed from these tables.

This module composes — it owns no compute. Discovery lives in catalog, the per-position units in quantifier, the record→inputs bridge in records, and pooling in shape_tables. The only orchestration that previously lived nowhere but the napari studio — the per-position build loop — is build_quantities().

Backend-only (no Qt / napari): the standalone wheel and headless batch runs use it unchanged.

Functions

aggregate(catalog[, out_dir, params, quantities])

Pool every built product across catalog into the index-keyed tables.

author_config(out_dir, records, *[, ...])

Write catalog.csv + config.toml into out_dir; return the config path.

build_catalog(root, *[, cell_name, ...])

Discover the positions under root and return them as catalogue records.

build_quantities(catalog, *[, quantifiers, ...])

Build and persist every producer quantity for every position in catalog.

run(config_path, *[, progress_cb, build])

Run the whole pipeline from a TOML run-config: the "author once, then run".

select_quantifiers(quantities)

Instantiate the quantifiers a run should build for the selected quantities.

itasc.contact_analysis.pipeline.author_config(out_dir, records, *, tables_dir=None, quantities=(), params=None, catalog_name='catalog.csv', config_name='config.toml')[source]

Write catalog.csv + config.toml into out_dir; return the config path.

The composition point behind the studio’s “Save config…” / “Run”: persist the in-memory records to a catalog CSV, then author a run-config beside it that points at that CSV (a relative catalog key, so the folder stays relocatable). tables_dir is written as the config’s out_dir (where the flat pooled tables land); None leaves it unset (defaults to the catalogue root at run time). run(author_config(...)) reproduces the UI’s run headlessly. Creates out_dir if missing.

Return type:

Path

Parameters:
itasc.contact_analysis.pipeline.build_catalog(root, *, cell_name=None, nucleus_name=None, out_csv=None)[source]

Discover the positions under root and return them as catalogue records.

Wraps discover_catalog_entries(): a position is any folder holding at least one named input (cell and/or nucleus labels). When out_csv is given, the discovered skeleton is written there (save_catalog()) for an analyst to fill in condition / date / notes before building.

The records carry the discovered paths but no metadata; that is fine for an immediate build (which keys only on the inputs) and is the editable skeleton when persisted.

Return type:

list[dict]

Parameters:
itasc.contact_analysis.pipeline.build_quantities(catalog, *, quantifiers=None, params=None, progress_cb=None)[source]

Build and persist every producer quantity for every position in catalog.

THE extracted build loop (previously trapped in the napari studio): one job per (quantifier, position) where the quantifier can_build() the position’s inputs; each job calls .build(inputs, output_path, params=...), overwriting an existing artifact. Positions lacking a quantifier’s inputs are skipped.

Only producers — quantifiers whose produces another quantifier’s requires names (in practice just contacts, writing contact_analysis.h5) — persist a per-position artifact here. Every other (cheap) quantity is never built to disk per-position: it is instead computed in memory, straight from a position’s raw inputs, inside aggregate() / build_table() via compute_object_table(). A normal run therefore writes only contact_analysis.h5 per position; the pooled tables are the only place the cheap quantities live.

quantifiers defaults to one instance of every registered quantifier (available_quantifiers()); it is filtered down to producers before planning, so passing non-producers here is harmless (they are simply skipped). params (the shared build knobs — z-score shuffles, density FOV, pixel size, frame interval) is threaded only into quantifiers that opt in via wants_build_params; the rest keep their own params schema clean. A quantifier whose required_build_params params does not satisfy is skipped whole (mirrors the studio greying the metric out), so “build everything” stays usable when an optional knob like the density FOV is unset. progress_cb is called (done, total, position_name) before each build. Exceptions propagate — a failed build aborts the run rather than being silently swallowed.

Return type:

None

Parameters:
itasc.contact_analysis.pipeline.select_quantifiers(quantities)[source]

Instantiate the quantifiers a run should build for the selected quantities.

Empty quantities selects every registered quantifier. A non-empty list selects those quantity_ids plus, transitively, any producer whose produces field a selected (or pulled-in) quantifier requires — so asking for a contacts-derived metric silently brings contacts along instead of leaving it unbuildable. Order follows registration; the build loop re-sorts by dependency.

Return type:

list[Quantifier]

Parameters:

quantities (Sequence[str])

itasc.contact_analysis.pipeline.aggregate(catalog, out_dir=None, *, params=None, quantities=None)[source]

Pool every built product across catalog into the index-keyed tables.

Thin pass-through to itasc.contact_analysis.shape_tables.aggregate(): returns the table name → written CSV path map. The tables are written flat under out_dir (<out_dir>/<name>.csv); out_dir defaults to the catalogue root (the common ancestor of the positions). params carries the shared build knobs (pixel size, frame interval, FOV area, …) so a param-gated cheap quantity (e.g. cell shape needing pixel_size_um) pools with the same knobs a normal build would have used. quantities restricts which tables are written (None = all); see shape_tables.aggregate().

Return type:

dict[str, Path]

Parameters:
itasc.contact_analysis.pipeline.run(config_path, *, progress_cb=None, build=True)[source]

Run the whole pipeline from a TOML run-config: the “author once, then run”.

Loads the RunConfig, then threads its choices through the stages: load the catalog CSV, build the selected quantities — of which only producers (contacts) actually persist a per-position artifact, see build_quantities() — and aggregate into the flat measurement tables under the config’s out_dir (default: the catalogue root), where the cheap quantities are computed in memory. The same quantities selection restricts which tables aggregate writes (empty = all), so a pool-only run honors the choice too. Returns the table name → written CSV path map. The optional progress_cb is forwarded to the build stage.

Set build to False for pool-only: skip the per-position build stage entirely and go straight to aggregate. The producer’s .build overwrites its .h5 unconditionally (it is not guarded like ensure_contacts), so a plain run recomputes every position’s contacts even when the artifact already exists. build=False reads the existing .h5 via each quantifier’s compute_object_table and computes the cheap quantities in memory — a load-and-pool with no recompute. Positions missing their .h5 simply contribute nothing (scope them out before calling).

Return type:

dict[str, Path]

Parameters: