linumpy.intensity.bias_field#

N4 bias field correction for serial OCT stacks.

Provides CPU-based N4 correction via SimpleITK and helpers to run it per serial section in parallel via multiprocessing.

Typical two-pass usage:

from linumpy.intensity.bias_field import compute_tissue_mask, n4_correct_per_section, n4_correct

mask = compute_tissue_mask(vol)
vol_ps, _ = n4_correct_per_section(vol, n_serial_slices=50, mask=mask, n_processes=48)
vol_out, _ = n4_correct(vol_ps, mask)

Functions#

compute_tissue_mask(vol[, smoothing_sigma, ...])

Return a 3-D boolean mask where True indicates tissue (not agarose).

n4_correct(vol[, mask, shrink_factor, n_iterations, ...])

Run N4 bias field correction on a 3-D volume.

apply_bias_field(vol, bias_field[, floor])

Divide vol element-wise by bias_field, guarding against near-zero divisors.

n4_correct_per_section(vol, n_serial_slices[, mask, ...])

Run N4 bias field correction independently on each serial section.

Module Contents#

linumpy.intensity.bias_field.compute_tissue_mask(vol, smoothing_sigma=2.0, n_serial_slices=1, closing_radius=3, z_closing_sections=2, smoothing_sigma_z=1.0, use_gpu=False)[source]#

Return a 3-D boolean mask where True indicates tissue (not agarose).

The volume is lightly smoothed with an anisotropic 3-D Gaussian (smoothing_sigma in XY, smoothing_sigma_z in Z) and a single Otsu threshold is computed per serial section from the smoothed voxel histogram (background-zero voxels excluded). The threshold is then applied per voxel, so the mask follows tissue shape through Z and correctly handles oblique sections (e.g. 45° acquisitions), where the tissue footprint shifts across Z within a section.

Each Z-plane is post-processed with hole-filling and morphological closing to remove internal speckle (e.g. dark white-matter or ventricle voxels falling below the Otsu threshold). Finally the stacked 3-D mask is closed along Z to bridge step artifacts at section boundaries.

Parameters:
  • vol (np.ndarray) – 3-D volume (Z, Y, X), any float dtype.

  • smoothing_sigma (float) – Gaussian smoothing sigma in XY (pixels) before thresholding.

  • n_serial_slices (int) – Number of serial sections in the volume. When 1 (default), one global Otsu threshold is used.

  • closing_radius (int) – Radius (pixels) of the 2-D disk used for morphological closing on each Z-plane mask. 0 disables 2-D closing.

  • z_closing_sections (int) – Number of adjacent sections to bridge with a 3-D closing pass on the stacked mask. 0 disables Z-direction closing.

  • smoothing_sigma_z (float) – Gaussian smoothing sigma along Z (voxels) before thresholding. Small values (1-2) denoise without blurring oblique edges.

  • use_gpu (bool) – If True, run the dominant 3-D gaussian_filter on GPU via CuPy (Z-chunked for memory safety). Falls back to CPU silently if CuPy is unavailable. Otsu and morphology stay on CPU.

Returns:

Boolean array of shape (Z, Y, X) – True where tissue is present.

Return type:

np.ndarray

linumpy.intensity.bias_field.n4_correct(vol, mask=None, *, shrink_factor=4, n_iterations=None, spline_distance_mm=10.0, voxel_size_mm=(1.0, 1.0, 1.0), backend='cpu', out=None, bias_out=None)[source]#

Run N4 bias field correction on a 3-D volume.

The N4 fit is performed on a spatially downsampled copy (shrink_factor); the bias field is then upsampled back to full resolution before division.

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

  • mask (np.ndarray or None) – Boolean tissue mask (Z, Y, X) – same shape as vol. A full-volume mask is used when None.

  • shrink_factor (int) – Isotropic spatial downsampling factor for the N4 fit.

  • n_iterations (list of int or None) – Max iterations per fitting level; its length sets the number of fitting levels. Defaults to [50, 50, 50, 50] (4 levels).

  • spline_distance_mm (float) – Approximate distance (in mm) between B-spline control-point knots.

  • voxel_size_mm (3-tuple of float) – Voxel size (z, y, x) in mm – sets physical spacing for SimpleITK.

  • backend ({"cpu", "gpu", "auto"}) – Backend selector. "cpu" (default) uses SimpleITK’s N4 implementation. "gpu" dispatches to linumpy.gpu.n4.n4_correct_gpu() (CuPy-accelerated when CUDA is available, NumPy fallback otherwise). "auto" picks "gpu" when CuPy + CUDA are available and "cpu" otherwise.

  • out (np.ndarray, optional) – Destination buffers (GPU backend only). When provided, the N4 driver writes its full-resolution outputs directly into these buffers instead of allocating fresh arrays, saving up to two full-volume float32 allocations. out may safely alias the input vol – the host buffer is not read after the initial H2D upload.

  • bias_out (np.ndarray, optional) – Destination buffers (GPU backend only). When provided, the N4 driver writes its full-resolution outputs directly into these buffers instead of allocating fresh arrays, saving up to two full-volume float32 allocations. out may safely alias the input vol – the host buffer is not read after the initial H2D upload.

Returns:

  • corrected (np.ndarray) – Bias-corrected float32 volume, same shape as vol.

  • bias_field (np.ndarray) – Estimated bias field (multiplicative), float32, same shape as vol.

Return type:

tuple[numpy.ndarray, numpy.ndarray]

linumpy.intensity.bias_field.apply_bias_field(vol, bias_field, floor=1e-06)[source]#

Divide vol element-wise by bias_field, guarding against near-zero divisors.

Parameters:
  • vol (np.ndarray) – Input volume, any shape.

  • bias_field (np.ndarray) – Multiplicative bias field, same shape as vol.

  • floor (float) – Minimum divisor value (prevents division by zero).

Returns:

Corrected float32 array.

Return type:

np.ndarray

linumpy.intensity.bias_field.n4_correct_per_section(vol, n_serial_slices, mask=None, *, n_processes=1, **kwargs)[source]#

Run N4 bias field correction independently on each serial section.

Splits the volume along Z into n_serial_slices chunks and corrects each chunk independently (serial sections have independent optical attenuation). Chunks are dispatched to a multiprocessing.Pool when n_processes > 1.

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

  • n_serial_slices (int) – Number of serial tissue sections stacked along Z.

  • mask (np.ndarray or None) – Boolean tissue mask (Z, Y, X). Sliced alongside vol.

  • n_processes (int) – Number of parallel worker processes. 1 runs serially.

  • **kwargs – Extra keyword arguments forwarded to n4_correct() (e.g. shrink_factor, spline_distance_mm).

Returns:

  • corrected (np.ndarray) – Bias-corrected float32 volume, same shape as vol.

  • bias_field (np.ndarray) – Per-section bias field stitched into a single (Z, Y, X) array.

Return type:

tuple[numpy.ndarray, numpy.ndarray]