linumpy.intensity.normalization#

Intensity normalization functions for OCT volumes.

This module provides functions for normalizing OCT volume intensities based on agarose background detection.

Functions#

normalize_volume(vol, agarose_mask[, percentile_max])

Normalize volume intensities based on agarose background.

get_agarose_mask(vol[, smoothing_sigma])

Compute agarose mask using Otsu thresholding on a mean projection.

compute_scale_factors(vol, n_serial_slices, ...)

Compute per-Z-plane linear scale factors for percentile-based normalization.

apply_histogram_matching(vol, n_serial_slices, n_bins)

Apply per-section histogram matching to a global reference distribution.

apply_zprofile_smoothing(vol, mask, sigma[, ...])

Remove residual per-Z-plane intensity jitter via a smoothed scalar gain.

Module Contents#

linumpy.intensity.normalization.normalize_volume(vol, agarose_mask, percentile_max=99.9)[source]#

Normalize volume intensities based on agarose background.

Each z-slice is clipped at its per-slice percentile cap and agarose-median floor, then the agarose floor is subtracted per slice (so background goes to exactly 0). The entire volume is then divided by a single global divisor (the maximum per-slice tissue span across all slices), so relative inter-section brightness is preserved.

Parameters:
  • vol (np.ndarray) – Input volume with shape (Z, Y, X).

  • agarose_mask (np.ndarray) – 2D binary mask indicating agarose regions (shape Y, X).

  • percentile_max (float) – Values above this percentile will be clipped per slice. Default 99.9.

Returns:

(normalized_volume, background_thresholds) - normalized_volume: float32 volume in [0, 1] with agarose at 0. - background_thresholds: Array of agarose-median per slice.

Return type:

tuple

linumpy.intensity.normalization.get_agarose_mask(vol, smoothing_sigma=1.0)[source]#

Compute agarose mask using Otsu thresholding on a mean projection.

The agarose is the low-intensity background surrounding the tissue. Uses a Gaussian-smoothed mean projection through Z to get a robust 2D estimate, then thresholds with Otsu.

Parameters:
  • vol (np.ndarray) – 3D volume with shape (Z, Y, X).

  • smoothing_sigma (float) – Gaussian smoothing sigma applied before Otsu thresholding.

Returns:

  • agarose_mask (np.ndarray) – 2D boolean mask (Y, X) – True where agarose is present.

  • threshold (float) – The Otsu threshold used.

Return type:

tuple[numpy.ndarray, float]

linumpy.intensity.normalization.compute_scale_factors(vol, n_serial_slices, smooth_sigma, percentile, min_scale, max_scale)[source]#

Compute per-Z-plane linear scale factors for percentile-based normalization.

Corrects slow acquisition drift (focus changes, laser power) between serial sections while preserving genuine anatomical intensity differences.

Parameters:
  • vol (np.ndarray) – Input volume (Z, Y, X) in [0, 1].

  • n_serial_slices (int or None) – Number of serial sections. None = operate at individual Z-plane level.

  • smooth_sigma (float) – Gaussian smoothing sigma in serial-section units.

  • percentile (float) – Percentile of non-zero voxels used as intensity reference per chunk.

  • min_scale (float) – Clamping range for scale factors.

  • max_scale (float) – Clamping range for scale factors.

Returns:

  • scale_factors (np.ndarray, shape (n_z,))

  • raw_metrics (np.ndarray)

  • smoothed (np.ndarray)

  • boundaries (list of int)

Return type:

tuple[numpy.ndarray, numpy.ndarray, numpy.ndarray, list]

linumpy.intensity.normalization.apply_histogram_matching(vol, n_serial_slices, n_bins, tissue_threshold=0.0, use_gpu=False)[source]#

Apply per-section histogram matching to a global reference distribution.

Corrects section-to-section intensity drift while preserving relative contrast within each section. Voxels at or below tissue_threshold are left unchanged.

Parameters:
  • vol (np.ndarray) – Input volume (Z, Y, X).

  • n_serial_slices (int or None) – Number of serial sections. None = per Z-plane.

  • n_bins (int) – Number of histogram bins.

  • tissue_threshold (float) – Minimum intensity to classify as tissue (default 0.0).

  • use_gpu (bool) – If True, run the per-chunk matching loop on GPU via CuPy. Falls back to CPU silently if CuPy is unavailable. The volume itself is moved to GPU one chunk at a time, so memory usage stays bounded.

Returns:

Histogram-matched volume.

Return type:

np.ndarray

linumpy.intensity.normalization.apply_zprofile_smoothing(vol, mask, sigma, min_tissue_voxels=100)[source]#

Remove residual per-Z-plane intensity jitter via a smoothed scalar gain.

For each Z-plane, computes the tissue mean (over mask), smooths the Z-mean profile with a Gaussian (sigma in Z-plane units), then applies a per-Z multiplicative gain target / observed to align each plane’s tissue mean to the smoothed trend. Background voxels (~mask) are left unchanged.

The correction is bounded in magnitude by the smoothed-vs-observed ratio and acts only on the high-frequency component of the Z-profile, so the smooth depth attenuation and large-scale anatomical variation are preserved. Best applied after apply_histogram_matching to clean up the residual ~1-2% inter-slice step that HM cannot remove.

Parameters:
  • vol (np.ndarray) – Input volume (Z, Y, X).

  • mask (np.ndarray) – Tissue mask (Z, Y, X), bool.

  • sigma (float) – Gaussian smoothing sigma in Z-plane units. Larger = preserves more depth structure but removes less jitter. 2.0-4.0 works well in practice.

  • min_tissue_voxels (int) – Z-planes with fewer tissue voxels are left unchanged (no reliable mean).

Returns:

Volume with per-Z gain applied to tissue voxels.

Return type:

np.ndarray