Source code for itasc.core.imageops

# src/itasc/core/imageops.py
"""Generic, deterministic image operations shared across ITASC pieces.

Pure functions with no ITASC dependencies, usable by segmentation, tracking,
and any other consumer without pulling in a heavier subpackage.
"""
from __future__ import annotations

import numpy as np
from skimage.filters import threshold_local


[docs] def residual(frame: np.ndarray, window: int, strength: float = 1.0) -> np.ndarray: """Local-mean-subtracted residual: ``clip(frame - strength*localmean(frame), 0)``. Flattens each map's per-nucleus offset so a single global threshold works everywhere while staying ~0 in flat background. ``window`` is forced odd. ``strength`` blends between the raw map and the fully-flattened residual: ``1.0`` subtracts the whole local background (default), ``0.0`` subtracts nothing so the result is the raw (non-negative) map, and values in between partially flatten. Lowering it trades uniform-threshold behaviour for keeping more of the original signal where the background is already flat. """ window = int(window) | 1 frame = np.asarray(frame, dtype=np.float32) if strength == 0.0: # No background subtraction requested (the default): the result is just # the non-negative raw map, so skip the O(Y·X) gaussian entirely. This # matters because ``CellDivergenceParams.fg_strength`` defaults to 0.0, # so every default-config frame — including live previews — hit this. return np.clip(frame, 0.0, None) local_mean = threshold_local(frame, block_size=window, method="gaussian") return np.clip(frame - strength * local_mean, 0.0, None).astype(np.float32)