Source code for phenotypic.enhance._focus_edge_frangi

from __future__ import annotations
from typing import Annotated, Iterable, TYPE_CHECKING

if TYPE_CHECKING:
    from phenotypic._core._image import Image

from pydantic import field_validator
from skimage.filters import frangi

from phenotypic.abc_ import FocusEdge
from phenotypic.sdk_.typing_ import TuneSpec


[docs] class FocusEdgeFrangi(FocusEdge): """Enhance elongated structures in ``detect_mat`` using multi-scale Frangi vesselness filtering. Computes Hessian matrix eigenvalues at each sigma scale and combines them into a vesselness score that responds strongly to ridge-like features — hyphae, mycelial branches, biofilm edges — while suppressing blob-like colonies and flat agar background. The output is a [0, 1] probability-like map that typically feeds into :class:`FilamentousFungiDetector` or a hysteresis threshold. For algorithm details see :doc:`/explanation/filamentous_fungi_algorithm`. Best For: - Filamentous fungi (*Neurospora*, *Aspergillus*) with branching hyphae resolved to 2--8 px wide at the imaging scale. - Mycelial networks and biofilm ridge structures that global intensity thresholding misses. - Pre-filtering before :class:`FilamentousFungiDetector` to produce a clean hyphal evidence map. - Plates where hyphae span a range of widths and a multi-sigma sweep is needed to cover all branch generations. Consider Also: - :class:`FocusEdgeMeijering` for very fine, isolated filaments where the analytic alpha optimum and simpler parameterisation are preferred. - :class:`FocusEdgeSato` when continuous tubular structures with different eigenvalue-ratio behaviour are the target. - :class:`FocusEdgePhase` for contrast-invariant edge enhancement on plates with uneven illumination. - :class:`StructureSmoothing` for anisotropic pre-smoothing along hyphal orientation before ridge detection. Args: sigmas: Gaussian standard deviations (pixels) at which the Hessian is evaluated. Each sigma responds most strongly to ridges whose cross-sectional half-width is approximately that value. Include sigmas spanning the full range of expected hyphal widths; the per-pixel maximum across all scales is taken, so additional sigmas can only raise the response. A reasonable starting point for agar plate scans at 600 dpi, where hyphae typically appear 2--8 px wide, is ``(0.5, 1, 1.5)`` to ``(1, 2, 3, 4)``; extend the upper bound for mature thick mycelium or coarser imaging resolution. Default: ``(0.5, 1, 1.5)``. alpha: Plate-likeness sensitivity in the vesselness formula. Controls how strongly the filter penalises structures that deviate from a purely elongated ridge. In 2-D images this parameter has no numerical effect because the plate-sensitivity ratio is undefined and omitted from the 2-D vesselness formula; it is included only for compatibility with 3-D use. Typical range: 0.1--1.0. Default: 0.5. beta: Blob-likeness sensitivity. Lower values make the filter more permissive of rounded or imperfect ridges (useful at branching junctions and thick hyphal segments); higher values restrict responses to more purely elongated structures. Typical range: 0.1--1.0. Default: 0.5. gamma: Background suppression threshold based on the Hessian Frobenius norm. ``None`` (default) uses half the maximum Hessian norm per scale, adapting to the actual contrast in each image. An explicit positive value provides a fixed threshold useful when comparing results across images with different illumination. Default: ``None``. black_ridges: Polarity of the target ridges. ``False`` (default) detects bright ridges on a dark background, matching the ``detect_mat`` convention where hyphae appear bright. ``True`` detects dark ridges on a bright background (e.g. transmitted-light microscopy). Default: ``False``. Returns: Image: Input image with ``detect_mat`` replaced by the [0, 1] vesselness response map. ``rgb`` and ``gray`` are unchanged. References: [1] A. F. Frangi, W. J. Niessen, K. L. Vincken, and M. A. Viergever, "Multiscale vessel enhancement filtering," in *Proc. MICCAI*, 1998, pp. 130--137. See Also: :doc:`/tutorials/notebooks/10_detecting_filamentous_fungi` for a visual walkthrough of filamentous fungi detection. :doc:`/explanation/filamentous_fungi_algorithm` for the theory behind Hessian-based vesselness filtering. """ sigmas: tuple[float, ...] = (0.5, 1, 1.5) alpha: float = 0.5 beta: Annotated[float, TuneSpec(0.1, 1.0)] = 0.5 gamma: float | None = None black_ridges: bool = False @field_validator("sigmas", mode="before") @classmethod def _coerce_sigmas(cls, sigmas: Iterable[float]) -> tuple[float, ...]: """Coerce any iterable of sigmas to a tuple. Reproduces the pre-migration ``__setattr__`` override, which normalized ``sigmas`` (passed as a list or other iterable) to a tuple before storing it. """ return tuple(sigmas) def _operate(self, image: Image) -> Image: image.detect_mat[:] = frangi( image=image.detect_mat[:], sigmas=self.sigmas, alpha=self.alpha, beta=self.beta, gamma=self.gamma, black_ridges=self.black_ridges, ) return image