Source code for phenotypic.enhance._focus_edge_laplace
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from phenotypic._core._image import Image
from skimage.filters import laplace
from typing import Annotated, Optional
from ..abc_ import FocusEdge
from phenotypic.sdk_ import NormalizedOutputMixin
from phenotypic.sdk_.typing_ import NdArrayField, NormOut, TuneSpec
[docs]
class FocusEdgeLaplace(NormalizedOutputMixin, FocusEdge):
"""Enhance colony edges in ``detect_mat`` with a discrete Laplacian operator.
Applies a discrete second-derivative Laplacian that responds strongly to
rapid intensity changes, highlighting colony margins and ring-like features
such as swarming fronts. The output is an edge-response map suitable as a
preprocessing step for contour detection, watershed seeding, or separating
touching colonies.
For algorithm details, see :doc:`/explanation/what_enhancement_does`.
Best For:
- Emphasizing colony edges before edge-based or contour-based
segmentation.
- Detecting ring patterns around swarming colonies for motility
phenotyping.
- Generating boundary seeds for watershed segmentation when colonies
are touching.
Consider Also:
- :class:`FocusEdgeHessian` for multi-scale ridge and edge detection
with additional control over scale and background suppression.
- :class:`SharpenEdgeGauss` for edge enhancement that retains the
original intensity profile rather than producing a pure edge map.
- :class:`FocusEdgePhase` for contrast-invariant edge detection under
uneven illumination.
Args:
kernel_size: Size of the Laplacian convolution kernel in pixels.
Smaller values (3) capture fine colony edges but amplify noise;
larger values (5--7) smooth noise and emphasize broader colony
boundaries. Default: 3.
mask: Boolean or 0/1 array restricting processing to a region of
interest (e.g., the circular plate area). ``None`` processes the
full image. Default: ``None``.
norm: Output range policy applied to the raw Laplacian response.
``"rescale"`` (default) linearly remaps the full signed response
onto [0, 1], preserving the bipolar edge structure. ``"clip"``
saturates the negative lobe to 0, discarding roughly half the edge
response; ``None`` passes the raw signed values through untouched.
Default: ``"rescale"``.
Returns:
Image: Input image with ``detect_mat`` replaced by the Laplacian edge
response, range-guarded into [0, 1] per ``norm``. ``rgb`` and ``gray``
are unchanged.
Note:
Changed in 0.18.0: the raw Laplacian is signed by construction (on the
synthetic yeast plate it spans roughly [-1.52, +1.48] with ~114k
negative pixels), which violated the ``detect_mat`` [0, 1] contract.
Output is now range-guarded, defaulting to ``norm="rescale"`` so the
full bipolar edge response is preserved inside [0, 1] rather than
having its negative half clipped away. This is a breaking change:
downstream thresholds tuned against the old raw response must be
re-tuned. Pass ``norm=None`` to recover the pre-0.18.0 signed output.
See Also:
:doc:`/tutorials/notebooks/03_enhancing_before_detection` for a visual
walkthrough of edge enhancement on plate images.
:doc:`/explanation/what_enhancement_does` for how edge-response maps
fit into the pipeline model.
"""
kernel_size: Annotated[Optional[int], TuneSpec(3, 7, step=2)] = 3
mask: NdArrayField | None = None
norm: NormOut = "rescale"
def _operate(self, image: Image) -> Image:
response = laplace(
image=image.detect_mat[:],
ksize=self.kernel_size,
mask=self.mask,
)
image.detect_mat[:] = self._apply_norm(response)
return image