Source code for linumpy.segmentation.brain
#! /usr/bin/env python
"""Brain segmentation utilities."""
import numpy as np
import SimpleITK as sitk
from scipy.ndimage import binary_erosion, binary_fill_holes
[docs]
def segment_oct_3d(vol: np.ndarray, k: int = 5, use_log: bool = True, threshold_method: str = "otsu") -> np.ndarray:
"""To segment an S-OCT brain in 3D using thresholding and morphological watershed.
Parameters
----------
vol
The OCT brain to segment
k
Median smoothing kernel size in pixel
use_log
Transform the pixel intensity with a log before computing mask
threshold_method
'ostu', 'triangle'
Returns
-------
ndarray
The brain mask
"""
vol_p = np.copy(vol)
if use_log:
vol_p[vol > 0] = np.log(vol_p[vol > 0])
# Creating a sitk image + smoothing
img = sitk.GetImageFromArray(vol_p)
img = sitk.Median(img, [k, k, k])
# Segmenting using an Otsu threshold
if threshold_method == "otsu":
marker_img = ~sitk.OtsuThreshold(img)
elif threshold_method == "triangle":
marker_img = ~sitk.TriangleThreshold(img)
else:
marker_img = ~sitk.OtsuThreshold(img)
# Using a watershed algorithm to optimize the mask
ws = sitk.MorphologicalWatershedFromMarkers(img, marker_img)
# Separating into foreground / background
seg = sitk.ConnectedComponent(ws != ws[0, 0, 0])
# Filling holes and returning the mask
mask = fill_holes_2d_and_3d(sitk.GetArrayFromImage(seg))
return mask
[docs]
def fill_holes_2d_and_3d(mask: np.ndarray) -> np.ndarray:
"""Fill holes in a 2D or 3D mask.
Parameters
----------
mask
The mask to fill
Returns
-------
ndarray
The filled mask
"""
# Filling holes and returning the mask
mask = binary_fill_holes(mask)
# Fill holes (in 2D)
nx, ny, nz = mask.shape
for x in range(nx):
mask[x, :, :] = binary_fill_holes(mask[x, :, :])
for y in range(ny):
mask[:, y, :] = binary_fill_holes(mask[:, y, :])
for z in range(nz):
mask[:, :, z] = binary_fill_holes(mask[:, :, z])
# Refill holes in 3D (in case some were missed)
mask = binary_fill_holes(mask)
return mask
[docs]
def remove_bottom(mask: np.ndarray, k: int = 10, axis: int = 2, inverse: bool = False, fill_holes: bool = False) -> np.ndarray:
"""Remove the bottom side of the mask.
Parameters
----------
mask
Mask to modify. The 3rd axis is assumed to be the dimension direction to modify.
k
Number of pixel to erode
axis
Axis to erode
inverse
Inverse the operation
fill_holes
Fill holes in the mask
Returns
-------
ndarray
Modified mask
"""
assert axis >= 0 and axis <= 2, "axis must be between 0 and 2"
kernel: np.ndarray = np.empty(0)
if axis == 0:
kernel = np.zeros((2 * k, 1, 1), dtype=bool)
elif axis == 1:
kernel = np.zeros((1, 2 * k, 1), dtype=bool)
else: # axis == 2
kernel = np.zeros((1, 1, 2 * k), dtype=bool)
if inverse:
kernel[0:k] = True
else:
kernel[k::] = True
if fill_holes:
mask_p = binary_erosion(fill_holes_2d_and_3d(mask), kernel)
mask_p = mask_p * mask
else:
mask_p = binary_erosion(mask, kernel)
return mask_p