Source code for intensity_normalization.util.histogram_tools

"""Process the histograms of MR (brain) images


"""

from __future__ import annotations

__all__ = [
    "get_first_tissue_mode",
    "get_largest_tissue_mode",
    "get_last_tissue_mode",
    "get_tissue_mode",
    "smooth_histogram",
]

import numpy as np
import scipy.signal
import statsmodels.api as sm

import intensity_normalization as intnorm
import intensity_normalization.typing as intnormt


[docs] def smooth_histogram( image: intnormt.ImageLike, / ) -> tuple[intnormt.ImageLike, intnormt.ImageLike]: """Use kernel density estimate to get smooth histogram Args: image: array of image data (like an np.ndarray) Returns: grid: domain of the pdf pdf: kernel density estimate of the pdf of data """ image_vec: np.ndarray = np.asarray(image.flatten(), dtype=np.float64) bandwidth = image_vec.max() / 80 kde = sm.nonparametric.KDEUnivariate(image_vec) kde.fit(kernel="gau", bw=bandwidth, gridsize=80, fft=True) pdf = 100.0 * kde.density grid = kde.support return grid, pdf
[docs] def get_largest_tissue_mode(image: intnormt.ImageLike, /) -> float: """Mode of the largest tissue class Args: image: array of image data (like an np.ndarray) Returns: largest_tissue_mode: value of the largest tissue mode """ grid, pdf = smooth_histogram(image) largest_tissue_mode: float = float(grid[int(np.argmax(pdf))]) return largest_tissue_mode
[docs] def get_last_tissue_mode( image: intnormt.ImageLike, /, *, remove_tail: bool = True, tail_percentage: float = 96.0, ) -> float: """Mode of the highest-intensity tissue class Args: image: array of image data (like an np.ndarray) remove_tail: remove tail from histogram tail_percentage: if remove_tail, use the histogram below this percentage Returns: last_tissue_mode: mode of the highest-intensity tissue class """ if not (0.0 < tail_percentage < 100.0): msg = f"'tail_percentage' must be in (0, 100). Got '{tail_percentage}'." raise ValueError(msg) if remove_tail: threshold: float = float(np.percentile(image, tail_percentage)) valid_mask: intnormt.ImageLike = image <= threshold image = image[valid_mask] grid, pdf = smooth_histogram(image) maxima = scipy.signal.argrelmax(pdf)[0] last_tissue_mode: float = grid[maxima[-1]] return last_tissue_mode
[docs] def get_first_tissue_mode( image: intnormt.ImageLike, /, *, remove_tail: bool = True, tail_percentage: float = 99.0, ) -> float: """Mode of the lowest-intensity tissue class Args: image: array of image data (like an np.ndarray) remove_tail: remove tail from histogram tail_percentage: if remove_tail, use the histogram below this percentage Returns: first_tissue_mode: mode of the lowest-intensity tissue class """ if not (0.0 < tail_percentage < 100.0): msg = f"'tail_percentage' must be in (0, 100). Got '{tail_percentage}'." raise ValueError(msg) if remove_tail: threshold: float = float(np.percentile(image, tail_percentage)) valid_mask: intnormt.ImageLike = image <= threshold image = image[valid_mask] grid, pdf = smooth_histogram(image) maxima = scipy.signal.argrelmax(pdf)[0] first_tissue_mode: float = grid[maxima[0]] return first_tissue_mode
[docs] def get_tissue_mode( image: intnormt.ImageLike, /, *, modality: intnormt.Modality ) -> float: """Find the appropriate tissue mode given a modality""" modality_ = modality.value if modality_ in intnorm.PEAK["last"]: mode = get_last_tissue_mode(image) elif modality_ in intnorm.PEAK["largest"]: mode = get_largest_tissue_mode(image) elif modality_ in intnorm.PEAK["first"]: mode = get_first_tissue_mode(image) else: modalities = ", ".join(intnorm.VALID_PEAKS) msg = f"Modality '{modality}' not valid. Needs to be one of {{{modalities}}}." raise ValueError(msg) return mode