Source code for nltools.mask

"""
NeuroLearn Mask Classes
=======================

Classes to represent masks

"""

__all__ = ["create_sphere", "expand_mask", "collapse_mask", "roi_to_brain"]
__author__ = ["Luke Chang", "Sam Greydanus"]
__license__ = "MIT"

import os
import nibabel as nib
from nltools.prefs import MNI_Template, resolve_mni_path
import pandas as pd
import numpy as np
import warnings
from nilearn.masking import intersect_masks


[docs]def create_sphere(coordinates, radius=5, mask=None): """Generate a set of spheres in the brain mask space Args: radius: vector of radius. Will create multiple spheres if len(radius) > 1 centers: a vector of sphere centers of the form [px, py, pz] or [[px1, py1, pz1], ..., [pxn, pyn, pzn]] """ from nltools.data import Brain_Data if mask is not None: if not isinstance(mask, nib.Nifti1Image): if isinstance(mask, str): if os.path.isfile(mask): mask = nib.load(mask) else: raise ValueError( "mask is not a nibabel instance or a valid " "file name" ) else: mask = nib.load(resolve_mni_path(MNI_Template)["mask"]) def sphere(r, p, mask): """create a sphere of given radius at some point p in the brain mask Args: r: radius of the sphere p: point (in coordinates of the brain mask) of the center of the sphere """ dims = mask.shape m = [dims[0] / 2, dims[1] / 2, dims[2] / 2] x, y, z = np.ogrid[ -m[0] : dims[0] - m[0], -m[1] : dims[1] - m[1], -m[2] : dims[2] - m[2] ] mask_r = x * x + y * y + z * z <= r * r activation = np.zeros(dims) activation[mask_r] = 1 translation_affine = np.array( [ [1, 0, 0, p[0] - m[0]], [0, 1, 0, p[1] - m[1]], [0, 0, 1, p[2] - m[2]], [0, 0, 0, 1], ] ) return nib.Nifti1Image(activation, affine=translation_affine) if any(isinstance(i, list) for i in coordinates): if isinstance(radius, list): if len(radius) != len(coordinates): raise ValueError( "Make sure length of radius list matches" "length of coordinate list." ) elif isinstance(radius, int): radius = [radius] * len(coordinates) out = Brain_Data( nib.Nifti1Image(np.zeros_like(mask.get_fdata()), affine=mask.affine), mask=mask, ) for r, c in zip(radius, coordinates): out = out + Brain_Data(sphere(r, c, mask), mask=mask) else: out = Brain_Data(sphere(radius, coordinates, mask), mask=mask) out = out.to_nifti() out.get_fdata()[out.get_fdata() > 0.5] = 1 out.get_fdata()[out.get_fdata() < 0.5] = 0 return out
[docs]def expand_mask(mask, custom_mask=None): """expand a mask with multiple integers into separate binary masks Args: mask: nibabel or Brain_Data instance custom_mask: nibabel instance or string to file path; optional Returns: out: Brain_Data instance of multiple binary masks """ from nltools.data import Brain_Data if isinstance(mask, nib.Nifti1Image): mask = Brain_Data(mask, mask=custom_mask) if not isinstance(mask, Brain_Data): raise ValueError("Make sure mask is a nibabel or Brain_Data instance.") mask.data = np.round(mask.data).astype(int) tmp = [] for i in np.nonzero(np.unique(mask.data))[0]: tmp.append((mask.data == i) * 1) out = mask.empty() out.data = np.array(tmp) return out
[docs]def collapse_mask(mask, auto_label=True, custom_mask=None): """collapse separate masks into one mask with multiple integers overlapping areas are ignored Args: mask: nibabel or Brain_Data instance custom_mask: nibabel instance or string to file path; optional Returns: out: Brain_Data instance of a mask with different integers indicating different masks """ from nltools.data import Brain_Data if not isinstance(mask, Brain_Data): if isinstance(mask, nib.Nifti1Image): mask = Brain_Data(mask, mask=custom_mask) else: raise ValueError("Make sure mask is a nibabel or Brain_Data " "instance.") if len(mask.shape()) > 1: if len(mask) > 1: out = mask.empty() # Create list of masks and find any overlaps m_list = [] for x in range(len(mask)): m_list.append(mask[x].to_nifti()) intersect = intersect_masks(m_list, threshold=1, connected=False) intersect = Brain_Data( nib.Nifti1Image(np.abs(intersect.get_fdata() - 1), intersect.affine), mask=custom_mask, ) merge = [] if auto_label: # Combine all masks into sequential order # ignoring any areas of overlap for i in range(len(m_list)): merge.append( np.multiply( Brain_Data(m_list[i], mask=custom_mask).data, intersect.data ) * (i + 1) ) out.data = np.sum(np.array(merge).T, 1).astype(int) else: # Collapse masks using value as label for i in range(len(m_list)): merge.append( np.multiply( Brain_Data(m_list[i], mask=custom_mask).data, intersect.data ) ) out.data = np.sum(np.array(merge).T, 1) return out else: warnings.warn("Doesn't need to be collapased")
[docs]def roi_to_brain(data, mask_x): """This function will create convert an expanded binary mask of ROIs (see expand_mask) based on a vector of of values. The dataframe of values must correspond to ROI numbers. This is useful for populating a parcellation scheme by a vector of Values Args: data: Pandas series, dataframe, list, np.array of ROI by observation mask_x: an expanded binary mask Returns: out: (Brain_Data) Brain_Data instance where each ROI is now populated with a value """ from nltools.data import Brain_Data if not isinstance(data, (pd.Series, pd.DataFrame)): if isinstance(data, list): data = pd.Series(data) elif isinstance(data, np.ndarray): if len(data.shape) == 1: data = pd.Series(data) elif len(data.shape) == 2: data = pd.DataFrame(data) if data.shape[0] != len(mask_x): if data.shape[1] == len(mask_x): data = data.T else: raise ValueError( "Data must have the same number of rows as rois in mask" ) else: raise NotImplementedError else: raise ValueError("Data must be a pandas series or data frame.") if len(mask_x) != data.shape[0]: raise ValueError("Data must have the same number of rows as mask has ROIs.") if isinstance(data, pd.Series): out = mask_x[0].copy() out.data = np.zeros(out.data.shape) for roi in range(len(mask_x)): out.data[np.where(mask_x.data[roi, :])] = data[roi] return out else: out = mask_x.copy() out.data = np.ones((data.shape[1], out.data.shape[1])) for roi in range(len(mask_x)): roi_data = np.reshape(data.iloc[roi, :].values, (-1, 1)) out.data[:, mask_x[roi].data == 1] = np.repeat( roi_data.T, np.sum(mask_x[roi].data == 1), axis=0 ).T return out