Source code for ranch.filtering.morphology

from typing import overload

import numpy as np
from scipy import ndimage

from .. import structures as struct
from . import filters

__all__ = [
    "StructuringElements",
    "closing",
    "dilation",
    "erosion",
    "filters",
    "gradient",
    "laplacian",
    "opening",
]


[docs] class StructuringElements: """Helpers to instantiate structuring elements"""
[docs] @staticmethod def segment(lg: int, filled: bool = True) -> np.ndarray: """TODO""" se = np.ones(lg, dtype=np.bool) if not filled: se[1:-1] = False return se
[docs] @staticmethod def disk(r: int, filled: bool = True) -> np.ndarray: """TODO""" x = np.arange(2 * r + 1) - r X, Y = np.meshgrid(x, x) if filled: return (X**2 + Y**2) <= r**2 return (X**2 + Y**2) == r**2
[docs] @staticmethod def ellipse(a: int, b: int, theta: float, filled: bool = True) -> np.ndarray: """TODO""" raise NotImplementedError()
[docs] @staticmethod def square(c: int, filled: bool = True) -> np.ndarray: """TODO""" return StructuringElements.rectangle(c, c)
[docs] @staticmethod def rectangle(a: int, b: int, filled: bool = True) -> np.ndarray: """TODO""" se = np.ones((a, b), dtype=np.bool) if filled: se[1:-1, 1:-1] = False return se
[docs] @staticmethod def ball(r: int, filled: bool = True) -> np.ndarray: """TODO""" x = np.arange(2 * r + 1) - r X, Y, Z = np.meshgrid(x, x, x) if filled: return (X**2 + Y**2 + Z**2) <= r**2 return (X**2 + Y**2 + Z**2) == r**2
[docs] @staticmethod def ellipsoid( rx: int, ry: int, rz: int, theta: float = 0.0, phi: float = 0.0, filled: bool = True, ) -> np.ndarray: # TODO """TODO""" r = max(rx, ry, rz) x = np.arange(2 * r + 1) - r Z, Y, X = np.meshgrid(x, x, x, indexing="ij") if filled: return (X**2 / rx**2 + Y**2 / ry**2 + Z**2 / rz**2) <= 1 return (X**2 / rx**2 + Y**2 / ry**2 + Z**2 / rz**2) == 1
[docs] @staticmethod def cube(c: int, filled=True) -> np.ndarray: """TODO""" return StructuringElements.cuboid(c, c, c, filled=filled)
[docs] @staticmethod def cuboid(a: int, b: int, c: int, filled: bool = True) -> np.ndarray: """TODO""" se = np.ones(a, b, c, dtype=np.bool) if not filled: se[1:-1, 1:-1, 1:-1] = False return se
@overload def dilation(input: "struct.Cube", se: np.ndarray) -> "struct.Cube": ... @overload def dilation(input: "struct.Map", se: np.ndarray) -> "struct.Map": ... @overload def dilation(input: "struct.Profile", se: np.ndarray) -> "struct.Profile": ...
[docs] def dilation(input: "struct.Struct", se: np.ndarray) -> "struct.Struct": """TODO""" if isinstance(input, struct.Cube): if se.ndim == 1: return input.filter_pixels(se, filtering_mode="max", padding_mode="reflect") if se.ndim == 2: return input.filter_channels( se, filtering_mode="max", padding_mode="reflect" ) if se.ndim == 3: return input.filter(se, filtering_mode="max", padding_mode="reflect") raise ValueError( f"Structuring element se must have 1, 2 or 3 dimensions, not {se.ndim}, for input of type Cube" ) elif isinstance(input, struct.Map): if se.ndim == 2: return input.filter(se, filtering_mode="max", padding_mode="reflect") raise ValueError( f"Structuring element se must have 2 dimensions, not {se.ndim}, for input of type Map" ) elif isinstance(input, struct.Profile): if se.ndim == 1: return input.filter(se, filtering_mode="max", padding_mode="reflect") raise ValueError( f"Structuring element se must have 1 dimension, not {se.ndim}, for input of type Profile" ) else: raise ValueError( f"input must an instance of Cube, Map or Profile, not {type(input)}" )
@overload def erosion(input: "struct.Cube", se: np.ndarray) -> "struct.Cube": ... @overload def erosion(input: "struct.Map", se: np.ndarray) -> "struct.Map": ... @overload def erosion(input: "struct.Profile", se: np.ndarray) -> "struct.Profile": ...
[docs] def erosion(input: "struct.Struct", se: np.ndarray) -> "struct.Struct": """TODO""" if isinstance(input, struct.Cube): if se.ndim == 1: return input.filter_pixels(se, filtering_mode="min", padding_mode="reflect") if se.ndim == 2: return input.filter_channels( se, filtering_mode="min", padding_mode="reflect" ) if se.ndim == 3: return input.filter(se, "min") raise ValueError( f"Structuring element se must have 1, 2 or 3 dimensions, not {se.ndim}, for input of type Cube" ) elif isinstance(input, struct.Map): if se.ndim == 2: return input.filter(se, filtering_mode="min", padding_mode="reflect") raise ValueError( f"Structuring element se must have 2 dimensions, not {se.ndim}, for input of type Map" ) elif isinstance(input, struct.Profile): if se.ndim == 1: return input.filter(se, filtering_mode="min", padding_mode="reflect") raise ValueError( f"Structuring element se must have 1 dimension, not {se.ndim}, for input of type Profile" ) else: raise ValueError( f"input must an instance of Cube, Map or Profile, not {type(input)}" )
@overload def closing(input: "struct.Cube", se: np.ndarray) -> "struct.Cube": ... @overload def closing(input: "struct.Map", se: np.ndarray) -> "struct.Map": ... @overload def closing(input: "struct.Profile", se: np.ndarray) -> "struct.Profile": ...
[docs] def closing(input: "struct.Struct", se: np.ndarray) -> "struct.Struct": """TODO""" return erosion(dilation(input, se), se)
@overload def opening(input: "struct.Cube", se: np.ndarray) -> "struct.Cube": ... @overload def opening(input: "struct.Map", se: np.ndarray) -> "struct.Map": ... @overload def opening(input: "struct.Profile", se: np.ndarray) -> "struct.Profile": ...
[docs] def opening(input: "struct.Struct", se: np.ndarray) -> "struct.Struct": """TODO""" return dilation(erosion(input, se), se)
@overload def gradient(input: "struct.Cube", se: np.ndarray) -> "struct.Cube": ... @overload def gradient(input: "struct.Map", se: np.ndarray) -> "struct.Map": ... @overload def gradient(input: "struct.Profile", se: np.ndarray) -> "struct.Profile": ...
[docs] def gradient(input: "struct.Struct", se: np.ndarray) -> "struct.Struct": """TODO""" return dilation(input, se) - erosion(input, se)
@overload def laplacian(input: "struct.Cube", se: np.ndarray) -> "struct.Cube": ... @overload def laplacian(input: "struct.Map", se: np.ndarray) -> "struct.Map": ... @overload def laplacian(input: "struct.Profile", se: np.ndarray) -> "struct.Profile": ...
[docs] def laplacian(input: "struct.Struct", se: np.ndarray) -> "struct.Struct": """TODO""" return dilation(input, se) + erosion(input, se) - 2 * input