Source code for medical_image.algorithms.dicom_window

"""
DICOM Windowing Algorithms.

Provides two windowing strategies as Algorithm subclasses:

1. **DicomWindowAlgorithm** — Simple linear WC/WW mapping (DICOM PS3 standard).
2. **GrailWindowAlgorithm** — Automatic intensity windowing using the GRAIL
   perceptual metric (Gabor-filtered mutual information optimisation).

Reference (GRAIL):
    Albiol, Corbi & Albiol (2017), "Automatic intensity windowing of
    mammographic images based on a perceptual metric," Medical Physics 44(4).

Example:
    >>> algo = DicomWindowAlgorithm(window_center=2000, window_width=3000)
    >>> output = image.clone()
    >>> algo(image, output)

    >>> algo = GrailWindowAlgorithm(n_scales=3, n_orientations=6)
    >>> output = image.clone()
    >>> algo(image, output)
    >>> print(algo.grail_a, algo.grail_b)
"""

from typing import Optional

from medical_image.algorithms.algorithm import Algorithm
from medical_image.data.image import Image
from medical_image.process.mammography import MammographyPreprocessing


[docs] class DicomWindowAlgorithm(Algorithm): """ Simple DICOM Window Center / Window Width algorithm. Maps pixel intensities to [0, 255] using the standard formula: output = clamp((pixel - (WC - WW/2)) / WW, 0, 1) * 255 If WC/WW are not provided, they are read from the DICOM header. Falls back to the full dynamic range if unavailable. Args: window_center: Explicit window center (None = read from header). window_width: Explicit window width (None = read from header). device: Torch device. """
[docs] def __init__( self, window_center: Optional[float] = None, window_width: Optional[float] = None, device: str = None, ): super().__init__(device=device) self.window_center = window_center self.window_width = window_width self._window = lambda img, out: MammographyPreprocessing.dicom_window( image=img, output=out, window_center=self.window_center, window_width=self.window_width, device=self.device, )
[docs] def apply(self, image: Image, output: Image) -> Image: """ Apply DICOM windowing to the image. Args: image: Input image (ideally DicomImage with dicom_data). output: Output Image — pixel_data will be in [0, 255]. Returns: The output Image. """ self._window(image, output) return output
[docs] class GrailWindowAlgorithm(Algorithm): """ GRAIL automatic intensity windowing algorithm. Finds optimal lower (a) and upper (b) intensity bounds by maximising a Gabor-filtered mutual information metric between the 12-bit original and 8-bit windowed representations, then applies linear intensity windowing IW(i, a, b) → [0, 255]. After ``apply()``, the optimal bounds are available as ``self.grail_a`` and ``self.grail_b``. Reference: Albiol, Corbi & Albiol (2017), Medical Physics 44(4). Args: n_scales: Number of Gabor frequency scales (default 3). n_orientations: Number of Gabor orientations (default 6). delta: Initial search grid spacing (default 300). k_max: Maximum optimisation iterations (default 3). device: Torch device. """
[docs] def __init__( self, n_scales: int = 3, n_orientations: int = 6, delta: int = 300, k_max: int = 3, device: str = None, ): super().__init__(device=device) self.n_scales = n_scales self.n_orientations = n_orientations self.delta = delta self.k_max = k_max # Populated after apply() self.grail_a: Optional[float] = None self.grail_b: Optional[float] = None
[docs] def apply(self, image: Image, output: Image) -> Image: """ Apply GRAIL windowing to the image. Args: image: Input 12-bit mammogram image. output: Output Image — pixel_data will be in [0, 255]. Returns: The output Image. ``self.grail_a`` and ``self.grail_b`` are set. """ result = MammographyPreprocessing.grail_window( image=image, output=output, n_scales=self.n_scales, n_orientations=self.n_orientations, delta=self.delta, k_max=self.k_max, device=self.device, ) self.grail_a = getattr(result, "grail_a", None) self.grail_b = getattr(result, "grail_b", None) return output