Source code for itasc.core.label_store
"""TIFF storage for tracked nucleus label volumes.
Schema: single multipage TIFF — shape (T, Y, X), dtype uint32.
Frames that have not yet been tracked are stored as all-zeros.
"""
from __future__ import annotations
from pathlib import Path
import numpy as np
import tifffile
from itasc.core.tiff import imwrite_grayscale
_LABEL_DTYPE = np.uint32
def _load_stack(path: Path) -> np.ndarray:
"""Load the TIFF as (T, Y, X). Returns empty array if file does not exist.
Tolerates legacy files written with a singleton Z axis: ``(T, 1, Y, X)``
is squeezed to ``(T, Y, X)`` so the in-memory shape always matches the
documented schema.
"""
if not path.exists():
return np.empty((0, 0, 0), dtype=_LABEL_DTYPE)
stack = np.asarray(tifffile.imread(str(path)), dtype=_LABEL_DTYPE)
if stack.ndim == 2:
stack = stack[np.newaxis]
elif stack.ndim == 4 and stack.shape[1] == 1:
stack = stack[:, 0]
return stack
[docs]
def write_full_tracked_stack(path: str | Path, stack: np.ndarray) -> None:
"""Write the entire ``(T, Y, X)`` tracked stack in one encode.
A multipage zlib TIFF cannot be updated in place, so a per-frame writer
would decode and re-encode the whole file on every frame (O(T²) over a
full-stack save). This encodes once (O(T)). A legacy singleton-Z axis
``(T, 1, Y, X)`` is squeezed to match the documented schema.
"""
path = Path(path)
path.parent.mkdir(parents=True, exist_ok=True)
stack = np.asarray(stack, dtype=_LABEL_DTYPE)
if stack.ndim == 4 and stack.shape[1] == 1:
stack = stack[:, 0]
imwrite_grayscale(path, stack, compression="zlib")
[docs]
def read_full_tracked_stack(path: str | Path) -> np.ndarray:
"""Read all tracked frames as a (T, Y, X) uint32 array."""
return _load_stack(Path(path))