Source code for omni.preprocessing

"""Image preprocessing functions.
"""
import numpy as np
import nibabel as nib
import scipy.stats as stats
from skimage.exposure import equalize_adapthist
from omni.interfaces.common import normalize


[docs]def saturate(img: nib.Nifti1Image, n: float = 0.01) -> nib.Nifti1Image: """Similar behavior to MATLAB imadjust. Saturates bottom/top n of data. Parameters ---------- img: nib.Nifti1Image Image to saturate. n: float Percentage (as decimal) of voxels to saturate. Returns ------- nib.Nifti1Image Saturated image. """ # get image data img_data = img.get_fdata() # sort data sorted_data = np.sort(img_data.ravel()) # get sorted data size img_size = sorted_data.shape[0] # get low/high voxel count (we always round up/down for lower/upper bound) low_count = int(np.ceil(n * img_size)) high_count = int(np.floor((1 - n) * img_size)) # get the image value at low and high counts low_value = sorted_data[low_count] high_value = sorted_data[high_count] # clip the original image by the low/high values clipped_data = np.clip(img_data, low_value, high_value) # create new image with modified data, but same affine and header mod_img = nib.Nifti1Image(clipped_data, img.affine, img.header) # return modified image return mod_img
[docs]def equalize(img: nib.Nifti1Image, precision: int = 4) -> nib.Nifti1Image: """Applies histogram equalization to the image. Parameters ---------- img: nib.Nifti1Image Image to histogram equalize. precision: int Number of decimal places to estimate historgram bins. Returns ------- nib.Nifti1Image Historgram equalized image. """ # get image data img_data = img.get_fdata() # get image shape img_shape = img.shape # get maximum of data max_scale = int(img_data.ravel().max()) min_scale = int(img_data.ravel().min()) # set precision by multiplying image rnd_data = (img_data.ravel() * (10**precision)).astype("int") max_val = max_scale * (10**precision) min_val = min_scale * (10**precision) # generate pdf data_histogram, _ = np.histogram(rnd_data, bins=max_val, range=(min_val, max_val)) # make histogram equalization map data_cdf = np.cumsum(data_histogram) cdf_min = data_cdf[np.where(data_cdf > 0)[0].min()] voxel_count = rnd_data.shape[0] voxel_map = (data_cdf - cdf_min) * (max_val / (voxel_count - 1)) voxel_map = np.clip(voxel_map, 0, max_val).astype("int") # ensure > 0 and convert to ints # scale the original image for precision scaled_data = (img_data * (10**precision)).astype("int").ravel() # map values onto voxel map equalized_data = (np.take(voxel_map, scaled_data, mode="clip").astype("float") / (10**precision)).reshape( img_shape ) # create new image with modified data, but same affine and header mod_img = nib.Nifti1Image(equalized_data, img.affine, img.header) # return modified image return mod_img
[docs]def localized_contrast_enhance(img: nib.Nifti1Image, mask: nib.Nifti1Image, nfrac: float = 0.05) -> nib.Nifti1Image: """Localized histogram equalization. Applies histogram equalization to image, but scales it so that the range of values inside the mask are enhanced. Parameters ---------- img: nib.Nifti1Image Image to contrast enhance. mask: nib.Nifti1Image Image containing region to constrast enhance. nfrac: float Fraction of voxels to use inside mask. Returns ------- nib.Nifti1Image LCE image. """ # get data and flatten img_data = (img.get_fdata()).flatten() mask_data = (mask.get_fdata() > 0.5).flatten() # Get a list of unique values in the data set, sorted in ascending order unique_data_vals = np.unique(img_data) # A vector that indicates what each value of the # input data set is mapped to initial_map_values = np.linspace(0, 1, unique_data_vals.size) # An initial mapping initial_interp_values = np.interp(img_data, unique_data_vals, initial_map_values) # Get the interpolated values within the supplied binary mask initial_mask_interp_values = initial_interp_values[mask_data] initial_mask_interp_values.sort(axis=0) # Sort the values in the mask in ascending order # Get the N% value in the mask. This is used along with the median # mask value to define the 'linear' range of the histogram normalizing # curve n_frac_value = initial_mask_interp_values[int(np.round(nfrac * initial_mask_interp_values.size))] # Get the median value in the mask median_value = np.median(initial_mask_interp_values) # Get transformed data transformed_data = normalize( stats.norm.cdf(initial_interp_values, loc=median_value, scale=abs(n_frac_value - median_value)) ) # return a new Nifti1Image return nib.Nifti1Image(transformed_data.reshape(*img.shape), img.affine, img.header)
[docs]def clahe(img: nib.Nifti1Image, clip_limit: float = 0.02) -> nib.Nifti1Image: """Contrast Limited Adaptive Histogram Equalization A simple wrapper around the CLAHE algorithm from skimage. Parameters ---------- img: nib.Nifti1Image input image Returns ------- nib.Nifti1Image output image """ return nib.Nifti1Image( equalize_adapthist(normalize(img.get_fdata()), kernel_size=img.shape, clip_limit=clip_limit), img.affine )
[docs]def normalization(img: nib.Nifti1Image) -> nib.Nifti1Image: """Normalizes an image from 0 to 1 Parameters ---------- img: nib.Nifti1Image Image to normalize. Returns ------- nib.Nifti1Image Normalized image. """ # get image data data = img.get_fdata() # return normalized image return nib.Nifti1Image(normalize(data), img.affine, img.header)