itasc.contact_analysis

Contact Analysis: pool per-position sources into aggregate tidy tables.

This package hosts the quantifier seam. The stable, napari-free surface that the itasc-aggregate CLI calls and notebooks import is the pipelinebuild_catalogbuild_quantitiesaggregate — re-exported here alongside the catalogue load/save helpers and the quantifier registry. Everything it produces is label-agnostic: tidy CSVs with no subpopulation classification baked in. Reduction to replicate-level summaries and plotting are downstream concerns owned by the data repo, not this package.

The contacts quantifier’s public API (cell-cell edges, T1 events) is also re-exported for stability; see itasc.contact_analysis.contacts.

itasc.contact_analysis.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.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.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.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.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.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:
itasc.contact_analysis.load_catalog(csv_path)[source]

Load CSV catalog records and expose normalized compatibility keys.

Only position_path is required. Uniqueness of the position identity (the combination of classification columns) is not checked here — a catalog may hold more positions than any one run aggregates; the aggregator validates uniqueness over the in-scope subset and refuses with an explanation there (see itasc.contact_analysis.shape_tables.aggregate()).

Return type:

list[dict]

Parameters:

csv_path (Path | str)

itasc.contact_analysis.save_catalog(csv_path, records)[source]

Write catalog records to CSV.

Each row stores the absolute position_path and the position’s files as paths relative to that folder. Records without a known position folder (legacy / hand-made) fall back to paths relative to the CSV file.

Return type:

None

Parameters:
class itasc.contact_analysis.RunConfig(catalog, out_dir=None, quantities=(), params=<factory>)[source]

Bases: object

A parsed run-config. Paths are absolute (resolved against the config dir).

quantities empty means “every available quantifier”; a non-empty tuple selects a subset by quantity_id (dependency producers are pulled in at run time even when omitted). params is the shared build-knob mapping threaded to quantifiers that opt in. out_dir is where the pooled tidy tables land (flat); None defaults to the catalogue root (the positions’ common ancestor).

Parameters:
catalog: Path
out_dir: Path | None = None
quantities: tuple[str, ...] = ()
params: dict
itasc.contact_analysis.load_config(config_path)[source]

Parse the TOML run-config at config_path into a RunConfig.

catalog is required. Relative paths resolve against the config file’s directory. Selected quantities are validated against the registered quantifiers so a typo fails loudly rather than silently computing nothing.

Return type:

RunConfig

Parameters:

config_path (Path | str)

itasc.contact_analysis.available_quantifiers()[source]

Return registered quantifier classes, sorted by display name.

Return type:

list[type[Quantifier]]

itasc.contact_analysis.build_contacts(*, cell_labels_path, output_path, nucleus_labels_path=None, source_path=None, edge_extraction_params=None, progress_cb=None)[source]

Build the canonical contact-analysis HDF5 file from label stacks.

Position-agnostic core: takes an explicit cell-label TIFF (2D+t) and an optional nucleus-label TIFF, writing the HDF5 to output_path. source_path is recorded as provenance only. When nucleus_labels_path is given the cell_id == nucleus_id invariant is enforced; otherwise nucleus validation is skipped.

Return type:

Path

Parameters:
itasc.contact_analysis.build_position_contacts(position_path, output_path, *, cell_tracked_labels_path=None, nucleus_tracked_labels_path=None, edge_extraction_params=None, progress_cb=None)[source]

Staged-layout wrapper around build_contacts().

Resolves the committed <position>/cell_labels.tif and <position>/nucleus_labels.tif labels (what finalize/”commit” writes to the position base folder) when explicit paths are not supplied, then delegates to the position-agnostic core. These are the downstream-stable inputs, not the pre-commit working 3_cell / 2_nucleus tracked labels. Retained for the orchestrator and out-of-repo consumers that pass a position directory.

Return type:

Path

Parameters:
itasc.contact_analysis.ensure_contacts(*, cell_labels_path, output_path, nucleus_labels_path=None, overwrite=False, edge_extraction_params=None, progress_cb=None)[source]

Build the contact-analysis HDF5 only when it is missing (or overwrite).

The .h5 is a pure derived artifact of the label inputs, so callers that only need it present (the visualizer, the batch runner) should go through this guard rather than rebuilding unconditionally. Returns (output_path, built) where built is False when an existing file was reused.

Return type:

tuple[Path, bool]

Parameters:
class itasc.contact_analysis.ContactBatchJob(group_dir, cell_labels, output, nucleus_labels=None)[source]

Bases: object

One discovered contact-analysis build: a cell-labels file and where its output goes, with an optional co-located nucleus-labels file.

Parameters:
  • group_dir (Path)

  • cell_labels (Path)

  • output (Path)

  • nucleus_labels (Path | None)

group_dir: Path
cell_labels: Path
output: Path
nucleus_labels: Path | None = None
class itasc.contact_analysis.ContactBatchResult(job, status, error=None)[source]

Bases: object

Outcome of a single ContactBatchJob.

status is "built" (computed), "skipped" (output already present), or "failed" (error holds the message).

Parameters:
job: ContactBatchJob
status: str
error: str | None = None
itasc.contact_analysis.discover_contact_batch_jobs(root, *, cell_name, h5_name, nucleus_name=None)[source]

Find contact-analysis jobs under root by file name.

Files are discovered recursively, then grouped by position folder — the first directory under root on the path to each match (root itself for files lying directly in it). The named files of one position may live in different subfolders (e.g. pos01/3_cell/ and pos01/2_nucleus/); they are still grouped together.

For a position with exactly one cell-labels file, that file is one job whose output is <position>/<h5_name>; the nucleus is associated only when exactly one nucleus_name file exists anywhere in that position (zero or several → cell-only, “can’t associate → don’t assign”). A position holding several cell-labels files is ambiguous to group, so each falls back to a cell-only job written beside its own cell file. Jobs are sorted by cell path.

Return type:

list[ContactBatchJob]

Parameters:
itasc.contact_analysis.run_contact_batch(jobs, *, overwrite=False, edge_extraction_params=None, progress_cb=None, cancel=None)[source]

Build the contact-analysis HDF5 for each job, collecting per-job outcomes.

A single job’s failure never aborts the run — it is recorded as a failed result and the batch continues. cancel is polled before each job; progress_cb(done, total, label) is reported after each.

Return type:

list[ContactBatchResult]

Parameters:

Submodules

catalog

Catalog of contact-analysis positions for Contact Analysis.

config

The TOML run-config — Contact Analysis's hand-authored knob file.

contacts

Cell-cell contact quantifier: edges, T1 events, neighbourhood & density.

curation

The curation artifact — hand QC exclusions joined onto the measurement tables.

dynamics

Track-dynamics compute core: motion read off tracked label stacks.

frame_interval

Resolve a position's frame interval (seconds per frame).

pipeline

The napari-free orchestration surface for Contact Analysis.

pixel_size

Resolve a position's physical pixel size (µm per pixel).

quantifier

The quantifier seam: per-position compute units, discovered by subclassing.

quantifiers

Quantifier plugins.

records

Qt-free bridge from catalogue records to a quantifier's per-position files.

shape

Shape compute core: per-object morphology + relational nucleus-vs-cell shape.

shape_tables

Aggregated shape tables — index-keyed, materialized views of the products.