Source code for itasc.contact_analysis.quantifiers.cell_dynamics
"""Cell-dynamics quantifier — the registry adapter over the dynamics core.
Wraps :mod:`itasc.contact_analysis.dynamics` so the studio can build
and read per-cell motion through the generic :class:`Quantifier` interface. Its
persistence is a multi-table ``4_contact_analysis/cell_dynamics.h5``;
:meth:`object_table` exposes the per-frame instantaneous table (the only
``(frame, cell_id)`` table) to the plotting backend. The cell twin of
:mod:`.nucleus_dynamics` — they share the label-agnostic core and differ only by
which label field they read and their output filename.
"""
from __future__ import annotations
from collections.abc import Callable, Mapping
from pathlib import Path
import numpy as np
from itasc.contact_analysis.dynamics import (
TrackDynamics,
build_track_dynamics,
read_instantaneous_table,
read_track_dynamics,
)
from itasc.contact_analysis.dynamics.kinematics import instantaneous_table
from itasc.contact_analysis.dynamics.trajectories import extract_trajectories
from itasc.contact_analysis.quantifier import PositionInputs, Quantifier
[docs]
class CellDynamicsQuantifier(Quantifier):
"""Quantifies per-cell motion (speed, persistence, MSD, collective) from cell labels."""
quantity_id = "cell_dynamics"
display_name = "Cell dynamics"
requires = ("cell_labels_path",)
# Pixel size (µm/px) + frame interval (s/frame) are global build params, set
# once in the Parameters panel and applied to every position, so each output
# lands in physical units. They gate the build (unset ⇒ not buildable) and
# reach :meth:`build` via the stamped ``PositionInputs``, not the params dict.
required_build_params = {
"pixel_size_um": "pixel size (µm/px)",
"time_interval_s": "frame interval (s)",
}
default_output_name = "cell_dynamics.h5"
# The object_table is the per-(frame, cell_id) instantaneous motion table. The
# per-track / per-tissue / curve sub-tables are separate views (tracks / frames
# / dac_curves) and are not this quantifier's object_table.
table_keys = ("frame", "cell_id")
[docs]
def build(
self,
inputs: PositionInputs,
output_path: Path,
*,
params: dict | None = None,
progress_cb: Callable[[int, int, str], None] | None = None,
) -> Path:
return build_track_dynamics(
inputs.cell_labels_path,
output_path,
pixel_size_um=inputs.pixel_size_um,
time_interval_s=inputs.time_interval_s,
source_path=inputs.position_dir,
params=params,
quantity_id=self.quantity_id,
progress_cb=progress_cb,
)
[docs]
def read(self, output_path: Path) -> TrackDynamics:
return read_track_dynamics(output_path)
[docs]
def object_table(self, output_path: Path) -> Mapping[str, np.ndarray]:
return read_instantaneous_table(output_path)
[docs]
def compute_object_table(
self, inputs: PositionInputs, *, params: dict | None = None
) -> Mapping[str, np.ndarray]:
trajectories = extract_trajectories(
inputs.cell_labels_path, pixel_size_um=inputs.pixel_size_um
)
return instantaneous_table(trajectories, time_interval_s=inputs.time_interval_s)