Skull Stripping 3D MRI Brain Image

Skull Stripping 3D MRI Brain Image

What is Skull Stripping?

Skull stripping, also known as brain extraction, is a preprocessing step that removes the skull and other non-brain tissues from MRI scans. It's a crucial step in the analysis of many MRI neurological images, such as tissue classification and image registration.

Skull stripping is a key area of study in brain image processing applications. It's a preliminary step in many medical applications, as it increases the speed and accuracy of diagnosis.

Skull stripping involves segmenting brain tissue (cortex and cerebellum) from the surrounding region (skull and nonbrain area). It's a preprocessing step for the cortical surface reconstruction process. This procedure takes an intensity-normalized image and deforms a tessellated ellipsoidal template into the shape of the inner surface of the skull.

What are we going to do exactly with the 3D MRI Brain images?

I was researching on the early detection of the Alzheimer's Disease. Therefore, I was working on the 3D MRI images of the human brain. Therefore, let's say, I have the raw 3D MRI brain image like below.

RAW Brain MRI Image in 3D from ADNI

The output would be like below.

  1. I want to get the brain mask that has been applied in the skull stripping process on my raw 3D image.

    Brain mask that has been applied on the raw image

  2. I want the preprocessed skull-stripped image that only has the brain portion excluding any non-brain tissues.

    Skull Stripped 3D image after preprocessing

Skull stripping, also known as brain extraction or skull-stripped segmentation, is a necessary step in human brain-related research for several reasons:

  1. Isolation of Brain Tissue: The skull contains bones and other tissues that are not relevant to many types of brain analysis, such as MRI or fMRI studies. Removing the skull allows researchers to focus specifically on the brain tissue itself, which is the primary object of study in neuroimaging research.

  2. Improved Accuracy: Skull stripping improves the accuracy of brain imaging analysis by removing non-brain tissues that can interfere with the interpretation of results. This interference can occur due to artifacts or inaccuracies introduced by including non-brain tissues in the analysis.

  3. Reduced Computational Load: Including the skull in the analysis increases the computational load required for processing brain images. By removing the skull, researchers can streamline the analysis process, making it more efficient and reducing computational resources.

  4. Enhanced Visualization: Skull stripping improves the visualization of brain structures in neuroimaging data. It allows for clearer and more precise rendering of brain images, which is essential for accurately identifying and studying various brain regions and structures.

  5. Standardization: Skull stripping helps standardize neuroimaging data processing pipelines across different studies and research groups. By employing consistent skull stripping techniques, researchers can ensure comparability and reproducibility of results, facilitating collaboration and meta-analyses in the field.

Overall, skull stripping is a critical preprocessing step in human brain-related research, enabling more accurate analysis, improved visualization, and enhanced comparability of neuroimaging data.

How can Skull Stripping be performed using Python?

There are many ways to apply skull stripping to 3D MRI images. There are also a lot of Python modules that help us to do that.

However, in this article, I am going to use a very simple code to apply the skull stripping on a basic level using ANTsPy.

For this, we are going to use a helper file that would help us scale up our process.

Let me give it a name, helpers.py.

Helpers

import matplotlib.pyplot as plt

from ipywidgets import interact
import numpy as np
import SimpleITK as sitk
import cv2

def explore_3D_array(arr: np.ndarray, cmap: str = 'gray'):
  """
  Given a 3D array with shape (Z,X,Y) This function will create an interactive
  widget to check out all the 2D arrays with shape (X,Y) inside the 3D array. 
  The purpose of this function to visual inspect the 2D arrays in the image. 

  Args:
    arr : 3D array with shape (Z,X,Y) that represents the volume of a MRI image
    cmap : Which color map use to plot the slices in matplotlib.pyplot
  """

  def fn(SLICE):
    plt.figure(figsize=(7,7))
    plt.imshow(arr[SLICE, :, :], cmap=cmap)

  interact(fn, SLICE=(0, arr.shape[0]-1))


def explore_3D_array_comparison(arr_before: np.ndarray, arr_after: np.ndarray, cmap: str = 'gray'):
  """
  Given two 3D arrays with shape (Z,X,Y) This function will create an interactive
  widget to check out all the 2D arrays with shape (X,Y) inside the 3D arrays.
  The purpose of this function to visual compare the 2D arrays after some transformation. 

  Args:
    arr_before : 3D array with shape (Z,X,Y) that represents the volume of a MRI image, before any transform
    arr_after : 3D array with shape (Z,X,Y) that represents the volume of a MRI image, after some transform    
    cmap : Which color map use to plot the slices in matplotlib.pyplot
  """

  assert arr_after.shape == arr_before.shape

  def fn(SLICE):
    fig, (ax1, ax2) = plt.subplots(1, 2, sharex='col', sharey='row', figsize=(10,10))

    ax1.set_title('Before', fontsize=15)
    ax1.imshow(arr_before[SLICE, :, :], cmap=cmap)

    ax2.set_title('After', fontsize=15)
    ax2.imshow(arr_after[SLICE, :, :], cmap=cmap)

    plt.tight_layout()

  interact(fn, SLICE=(0, arr_before.shape[0]-1))


def show_sitk_img_info(img: sitk.Image):
  """
  Given a sitk.Image instance prints the information about the MRI image contained.

  Args:
    img : instance of the sitk.Image to check out
  """
  pixel_type = img.GetPixelIDTypeAsString()
  origin = img.GetOrigin()
  dimensions = img.GetSize()
  spacing = img.GetSpacing()
  direction = img.GetDirection()

  info = {'Pixel Type' : pixel_type, 'Dimensions': dimensions, 'Spacing': spacing, 'Origin': origin,  'Direction' : direction}
  for k,v in info.items():
    print(f' {k} : {v}')


def add_suffix_to_filename(filename: str, suffix:str) -> str:
  """
  Takes a NIfTI filename and appends a suffix.

  Args:
      filename : NIfTI filename
      suffix : suffix to append

  Returns:
      str : filename after append the suffix
  """
  if filename.endswith('.nii'):
      result = filename.replace('.nii', f'_{suffix}.nii')
      return result
  elif filename.endswith('.nii.gz'):
      result = filename.replace('.nii.gz', f'_{suffix}.nii.gz')
      return result
  else:
      raise RuntimeError('filename with unknown extension')


def rescale_linear(array: np.ndarray, new_min: int, new_max: int):
  """Rescale an array linearly."""
  minimum, maximum = np.min(array), np.max(array)
  m = (new_max - new_min) / (maximum - minimum)
  b = new_min - m * minimum
  return m * array + b


def explore_3D_array_with_mask_contour(arr: np.ndarray, mask: np.ndarray, thickness: int = 1):
  """
  Given a 3D array with shape (Z,X,Y) This function will create an interactive
  widget to check out all the 2D arrays with shape (X,Y) inside the 3D array. The binary
  mask provided will be used to overlay contours of the region of interest over the 
  array. The purpose of this function is to visual inspect the region delimited by the mask.

  Args:
    arr : 3D array with shape (Z,X,Y) that represents the volume of a MRI image
    mask : binary mask to obtain the region of interest
  """
  assert arr.shape == mask.shape

  _arr = rescale_linear(arr,0,1)
  _mask = rescale_linear(mask,0,1)
  _mask = _mask.astype(np.uint8)

  def fn(SLICE):
    arr_rgb = cv2.cvtColor(_arr[SLICE, :, :], cv2.COLOR_GRAY2RGB)
    contours, _ = cv2.findContours(_mask[SLICE, :, :], cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

    arr_with_contours = cv2.drawContours(arr_rgb, contours, -1, (0,1,0), thickness)

    plt.figure(figsize=(7,7))
    plt.imshow(arr_with_contours)

  interact(fn, SLICE=(0, arr.shape[0]-1))

Additional required modules and libraries

We also need some modules and Python libraries so that we can create our model based on them.

I can simply create a text (.txt) file named requirements.txt where I can simply write down all the individual module/library names in separate lines.

ipykernel
ants
ipywidgets
matplotlib
opencv-python
jupyter
notebook
antspyx
SimpleITK
antspynet

Notebook

We can use VSCode or any other Python IDEs for writing our code. But I like to use the Jupyter Notebook as we can run different segments of our code separately. Also, all of the outputs stay in the same notebook.

You can also use the Google Colab notebook for free! Kaggle Notebook can also be used for free.

If you use the Google Colab, then you need to mount the drive to Notebook so that you can directly access all the files from your Google Drive.

# Connect Google Drive with Colab
from google.colab import drive
drive.mount('/content/drive')

Now we can simply install all the required modules/libraries that are listed on our requirements.txt file.

# Install necessary components
! pip install -r requirements.txt

This will install all the packages/modules that are listed on that specific text file. If you need more modules to install, you can simply add the name in that text file. If you need a specific version of a library, then you can also do that in the text file.

Output
Requirement already satisfied: ipykernel in /usr/local/lib/python3.10/dist-packages (5.5.6) Collecting ants Downloading ants-0.0.7.tar.gz (10 kB) Preparing metadata (setup.py) ... done Requirement already satisfied: ipywidgets in /usr/local/lib/python3.10/dist-packages (7.7.1) Requirement already satisfied: matplotlib in /usr/local/lib/python3.10/dist-packages (3.7.1) Requirement already satisfied: opencv-python in /usr/local/lib/python3.10/dist-packages (4.8.0.76) Collecting jupyter Downloading jupyter-1.0.0-py2.py3-none-any.whl (2.7 kB) Requirement already satisfied: notebook in /usr/local/lib/python3.10/dist-packages (6.5.5) Collecting antspyx Downloading antspyx-0.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (345.9 MB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 345.9/345.9 MB 4.4 MB/s eta 0:00:00 Collecting SimpleITK Downloading SimpleITK-2.3.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (52.7 MB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 52.7/52.7 MB 10.0 MB/s eta 0:00:00 Collecting antspynet Downloading antspynet-0.2.3-py3-none-any.whl (185 kB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 186.0/186.0 kB 28.8 MB/s eta 0:00:00 Requirement already satisfied: ipython-genutils in /usr/local/lib/python3.10/dist-packages (from ipykernel) (0.2.0) Requirement already satisfied: ipython>=5.0.0 in /usr/local/lib/python3.10/dist-packages (from ipykernel) (7.34.0) Requirement already satisfied: traitlets>=4.1.0 in /usr/local/lib/python3.10/dist-packages (from ipykernel) (5.7.1) Requirement already satisfied: jupyter-client in /usr/local/lib/python3.10/dist-packages (from ipykernel) (6.1.12) Requirement already satisfied: tornado>=4.2 in /usr/local/lib/python3.10/dist-packages (from ipykernel) (6.3.2) Collecting Django>=1.10.2 (from ants) Downloading Django-5.0.1-py3-none-any.whl (8.1 MB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 8.1/8.1 MB 80.9 MB/s eta 0:00:00 Collecting gevent>=1.1.1 (from ants) Downloading gevent-23.9.1-cp310-cp310-manylinux_2_28_x86_64.whl (6.4 MB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 6.4/6.4 MB 82.0 MB/s eta 0:00:00 Requirement already satisfied: widgetsnbextension~=3.6.0 in /usr/local/lib/python3.10/dist-packages (from ipywidgets) (3.6.6) Requirement already satisfied: jupyterlab-widgets>=1.0.0 in /usr/local/lib/python3.10/dist-packages (from ipywidgets) (3.0.9) Requirement already satisfied: contourpy>=1.0.1 in /usr/local/lib/python3.10/dist-packages (from matplotlib) (1.2.0) Requirement already satisfied: cycler>=0.10 in /usr/local/lib/python3.10/dist-packages (from matplotlib) (0.12.1) Requirement already satisfied: fonttools>=4.22.0 in /usr/local/lib/python3.10/dist-packages (from matplotlib) (4.47.0) Requirement already satisfied: kiwisolver>=1.0.1 in /usr/local/lib/python3.10/dist-packages (from matplotlib) (1.4.5) Requirement already satisfied: numpy>=1.20 in /usr/local/lib/python3.10/dist-packages (from matplotlib) (1.23.5) Requirement already satisfied: packaging>=20.0 in /usr/local/lib/python3.10/dist-packages (from matplotlib) (23.2) Requirement already satisfied: pillow>=6.2.0 in /usr/local/lib/python3.10/dist-packages (from matplotlib) (9.4.0) Requirement already satisfied: pyparsing>=2.3.1 in /usr/local/lib/python3.10/dist-packages (from matplotlib) (3.1.1) Requirement already satisfied: python-dateutil>=2.7 in /usr/local/lib/python3.10/dist-packages (from matplotlib) (2.8.2) Collecting qtconsole (from jupyter) Downloading qtconsole-5.5.1-py3-none-any.whl (123 kB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 123.4/123.4 kB 19.5 MB/s eta 0:00:00 Requirement already satisfied: jupyter-console in /usr/local/lib/python3.10/dist-packages (from jupyter) (6.1.0) Requirement already satisfied: nbconvert in /usr/local/lib/python3.10/dist-packages (from jupyter) (6.5.4) Requirement already satisfied: jinja2 in /usr/local/lib/python3.10/dist-packages (from notebook) (3.1.2) Requirement already satisfied: pyzmq<25,>=17 in /usr/local/lib/python3.10/dist-packages (from notebook) (23.2.1) Requirement already satisfied: argon2-cffi in /usr/local/lib/python3.10/dist-packages (from notebook) (23.1.0) Requirement already satisfied: jupyter-core>=4.6.1 in /usr/local/lib/python3.10/dist-packages (from notebook) (5.7.0) Requirement already satisfied: nbformat in /usr/local/lib/python3.10/dist-packages (from notebook) (5.9.2) Requirement already satisfied: nest-asyncio>=1.5 in /usr/local/lib/python3.10/dist-packages (from notebook) (1.5.8) Requirement already satisfied: Send2Trash>=1.8.0 in /usr/local/lib/python3.10/dist-packages (from notebook) (1.8.2) Requirement already satisfied: terminado>=0.8.3 in /usr/local/lib/python3.10/dist-packages (from notebook) (0.18.0) Requirement already satisfied: prometheus-client in /usr/local/lib/python3.10/dist-packages (from notebook) (0.19.0) Requirement already satisfied: nbclassic>=0.4.7 in /usr/local/lib/python3.10/dist-packages (from notebook) (1.0.0) Requirement already satisfied: pandas in /usr/local/lib/python3.10/dist-packages (from antspyx) (1.5.3) Requirement already satisfied: scipy in /usr/local/lib/python3.10/dist-packages (from antspyx) (1.11.4) Requirement already satisfied: scikit-image in /usr/local/lib/python3.10/dist-packages (from antspyx) (0.19.3) Requirement already satisfied: scikit-learn in /usr/local/lib/python3.10/dist-packages (from antspyx) (1.2.2) Requirement already satisfied: statsmodels in /usr/local/lib/python3.10/dist-packages (from antspyx) (0.14.1) Requirement already satisfied: webcolors in /usr/local/lib/python3.10/dist-packages (from antspyx) (1.13) Requirement already satisfied: pyyaml in /usr/local/lib/python3.10/dist-packages (from antspyx) (6.0.1) Collecting chart-studio (from antspyx) Downloading chart_studio-1.1.0-py3-none-any.whl (64 kB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 64.4/64.4 kB 10.9 MB/s eta 0:00:00 Requirement already satisfied: nibabel in /usr/local/lib/python3.10/dist-packages (from antspyx) (4.0.2) Requirement already satisfied: keras in /usr/local/lib/python3.10/dist-packages (from antspynet) (2.15.0) Requirement already satisfied: tensorflow in /usr/local/lib/python3.10/dist-packages (from antspynet) (2.15.0) Requirement already satisfied: tensorflow-probability in /usr/local/lib/python3.10/dist-packages (from antspynet) (0.22.0) Requirement already satisfied: requests in /usr/local/lib/python3.10/dist-packages (from antspynet) (2.31.0) Collecting asgiref<4,>=3.7.0 (from Django>=1.10.2->ants) Downloading asgiref-3.7.2-py3-none-any.whl (24 kB) Requirement already satisfied: sqlparse>=0.3.1 in /usr/local/lib/python3.10/dist-packages (from Django>=1.10.2->ants) (0.4.4) Collecting zope.event (from gevent>=1.1.1->ants) Downloading zope.event-5.0-py3-none-any.whl (6.8 kB) Collecting zope.interface (from gevent>=1.1.1->ants) Downloading zope.interface-6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl (247 kB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 247.1/247.1 kB 35.9 MB/s eta 0:00:00 Requirement already satisfied: greenlet>=2.0.0 in /usr/local/lib/python3.10/dist-packages (from gevent>=1.1.1->ants) (3.0.3) Requirement already satisfied: setuptools>=18.5 in /usr/local/lib/python3.10/dist-packages (from ipython>=5.0.0->ipykernel) (67.7.2) Collecting jedi>=0.16 (from ipython>=5.0.0->ipykernel) Downloading jedi-0.19.1-py2.py3-none-any.whl (1.6 MB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.6/1.6 MB 71.1 MB/s eta 0:00:00 Requirement already satisfied: decorator in /usr/local/lib/python3.10/dist-packages (from ipython>=5.0.0->ipykernel) (4.4.2) Requirement already satisfied: pickleshare in /usr/local/lib/python3.10/dist-packages (from ipython>=5.0.0->ipykernel) (0.7.5) Requirement already satisfied: prompt-toolkit!=3.0.0,!=3.0.1,<3.1.0,>=2.0.0 in /usr/local/lib/python3.10/dist-packages (from ipython>=5.0.0->ipykernel) (3.0.43) Requirement already satisfied: pygments in /usr/local/lib/python3.10/dist-packages (from ipython>=5.0.0->ipykernel) (2.16.1) Requirement already satisfied: backcall in /usr/local/lib/python3.10/dist-packages (from ipython>=5.0.0->ipykernel) (0.2.0) Requirement already satisfied: matplotlib-inline in /usr/local/lib/python3.10/dist-packages (from ipython>=5.0.0->ipykernel) (0.1.6) Requirement already satisfied: pexpect>4.3 in /usr/local/lib/python3.10/dist-packages (from ipython>=5.0.0->ipykernel) (4.9.0) Requirement already satisfied: platformdirs>=2.5 in /usr/local/lib/python3.10/dist-packages (from jupyter-core>=4.6.1->notebook) (4.1.0) Requirement already satisfied: jupyter-server>=1.8 in /usr/local/lib/python3.10/dist-packages (from nbclassic>=0.4.7->notebook) (1.24.0) Requirement already satisfied: notebook-shim>=0.2.3 in /usr/local/lib/python3.10/dist-packages (from nbclassic>=0.4.7->notebook) (0.2.3) Requirement already satisfied: lxml in /usr/local/lib/python3.10/dist-packages (from nbconvert->jupyter) (4.9.4) Requirement already satisfied: beautifulsoup4 in /usr/local/lib/python3.10/dist-packages (from nbconvert->jupyter) (4.11.2) Requirement already satisfied: bleach in /usr/local/lib/python3.10/dist-packages (from nbconvert->jupyter) (6.1.0) Requirement already satisfied: defusedxml in /usr/local/lib/python3.10/dist-packages (from nbconvert->jupyter) (0.7.1) Requirement already satisfied: entrypoints>=0.2.2 in /usr/local/lib/python3.10/dist-packages (from nbconvert->jupyter) (0.4) Requirement already satisfied: jupyterlab-pygments in /usr/local/lib/python3.10/dist-packages (from nbconvert->jupyter) (0.3.0) Requirement already satisfied: MarkupSafe>=2.0 in /usr/local/lib/python3.10/dist-packages (from nbconvert->jupyter) (2.1.3) Requirement already satisfied: mistune<2,>=0.8.1 in /usr/local/lib/python3.10/dist-packages (from nbconvert->jupyter) (0.8.4) Requirement already satisfied: nbclient>=0.5.0 in /usr/local/lib/python3.10/dist-packages (from nbconvert->jupyter) (0.9.0) Requirement already satisfied: pandocfilters>=1.4.1 in /usr/local/lib/python3.10/dist-packages (from nbconvert->jupyter) (1.5.0) Requirement already satisfied: tinycss2 in /usr/local/lib/python3.10/dist-packages (from nbconvert->jupyter) (1.2.1) Requirement already satisfied: fastjsonschema in /usr/local/lib/python3.10/dist-packages (from nbformat->notebook) (2.19.1) Requirement already satisfied: jsonschema>=2.6 in /usr/local/lib/python3.10/dist-packages (from nbformat->notebook) (4.19.2) Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.10/dist-packages (from python-dateutil>=2.7->matplotlib) (1.16.0) Requirement already satisfied: ptyprocess in /usr/local/lib/python3.10/dist-packages (from terminado>=0.8.3->notebook) (0.7.0) Requirement already satisfied: argon2-cffi-bindings in /usr/local/lib/python3.10/dist-packages (from argon2-cffi->notebook) (21.2.0) Requirement already satisfied: plotly in /usr/local/lib/python3.10/dist-packages (from chart-studio->antspyx) (5.15.0) Collecting retrying>=1.3.3 (from chart-studio->antspyx) Downloading retrying-1.3.4-py3-none-any.whl (11 kB) Requirement already satisfied: pytz>=2020.1 in /usr/local/lib/python3.10/dist-packages (from pandas->antspyx) (2023.3.post1) Collecting qtpy>=2.4.0 (from qtconsole->jupyter) Downloading QtPy-2.4.1-py3-none-any.whl (93 kB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 93.5/93.5 kB 15.1 MB/s eta 0:00:00 Requirement already satisfied: charset-normalizer<4,>=2 in /usr/local/lib/python3.10/dist-packages (from requests->antspynet) (3.3.2) Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.10/dist-packages (from requests->antspynet) (3.6) Requirement already satisfied: urllib3<3,>=1.21.1 in /usr/local/lib/python3.10/dist-packages (from requests->antspynet) (2.0.7) Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.10/dist-packages (from requests->antspynet) (2023.11.17) Requirement already satisfied: networkx>=2.2 in /usr/local/lib/python3.10/dist-packages (from scikit-image->antspyx) (3.2.1) Requirement already satisfied: imageio>=2.4.1 in /usr/local/lib/python3.10/dist-packages (from scikit-image->antspyx) (2.31.6) Requirement already satisfied: tifffile>=2019.7.26 in /usr/local/lib/python3.10/dist-packages (from scikit-image->antspyx) (2023.12.9) Requirement already satisfied: PyWavelets>=1.1.1 in /usr/local/lib/python3.10/dist-packages (from scikit-image->antspyx) (1.5.0) Requirement already satisfied: joblib>=1.1.1 in /usr/local/lib/python3.10/dist-packages (from scikit-learn->antspyx) (1.3.2) Requirement already satisfied: threadpoolctl>=2.0.0 in /usr/local/lib/python3.10/dist-packages (from scikit-learn->antspyx) (3.2.0) Requirement already satisfied: patsy>=0.5.4 in /usr/local/lib/python3.10/dist-packages (from statsmodels->antspyx) (0.5.5) Requirement already satisfied: absl-py>=1.0.0 in /usr/local/lib/python3.10/dist-packages (from tensorflow->antspynet) (1.4.0) Requirement already satisfied: astunparse>=1.6.0 in /usr/local/lib/python3.10/dist-packages (from tensorflow->antspynet) (1.6.3) Requirement already satisfied: flatbuffers>=23.5.26 in /usr/local/lib/python3.10/dist-packages (from tensorflow->antspynet) (23.5.26) Requirement already satisfied: gast!=0.5.0,!=0.5.1,!=0.5.2,>=0.2.1 in /usr/local/lib/python3.10/dist-packages (from tensorflow->antspynet) (0.5.4) Requirement already satisfied: google-pasta>=0.1.1 in /usr/local/lib/python3.10/dist-packages (from tensorflow->antspynet) (0.2.0) Requirement already satisfied: h5py>=2.9.0 in /usr/local/lib/python3.10/dist-packages (from tensorflow->antspynet) (3.9.0) Requirement already satisfied: libclang>=13.0.0 in /usr/local/lib/python3.10/dist-packages (from tensorflow->antspynet) (16.0.6) Requirement already satisfied: ml-dtypes~=0.2.0 in /usr/local/lib/python3.10/dist-packages (from tensorflow->antspynet) (0.2.0) Requirement already satisfied: opt-einsum>=2.3.2 in /usr/local/lib/python3.10/dist-packages (from tensorflow->antspynet) (3.3.0) Requirement already satisfied: protobuf!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<5.0.0dev,>=3.20.3 in /usr/local/lib/python3.10/dist-packages (from tensorflow->antspynet) (3.20.3) Requirement already satisfied: termcolor>=1.1.0 in /usr/local/lib/python3.10/dist-packages (from tensorflow->antspynet) (2.4.0) Requirement already satisfied: typing-extensions>=3.6.6 in /usr/local/lib/python3.10/dist-packages (from tensorflow->antspynet) (4.5.0) Requirement already satisfied: wrapt<1.15,>=1.11.0 in /usr/local/lib/python3.10/dist-packages (from tensorflow->antspynet) (1.14.1) Requirement already satisfied: tensorflow-io-gcs-filesystem>=0.23.1 in /usr/local/lib/python3.10/dist-packages (from tensorflow->antspynet) (0.35.0) Requirement already satisfied: grpcio<2.0,>=1.24.3 in /usr/local/lib/python3.10/dist-packages (from tensorflow->antspynet) (1.60.0) Requirement already satisfied: tensorboard<2.16,>=2.15 in /usr/local/lib/python3.10/dist-packages (from tensorflow->antspynet) (2.15.1) Requirement already satisfied: tensorflow-estimator<2.16,>=2.15.0 in /usr/local/lib/python3.10/dist-packages (from tensorflow->antspynet) (2.15.0) Requirement already satisfied: cloudpickle>=1.3 in /usr/local/lib/python3.10/dist-packages (from tensorflow-probability->antspynet) (2.2.1) Requirement already satisfied: dm-tree in /usr/local/lib/python3.10/dist-packages (from tensorflow-probability->antspynet) (0.1.8) Requirement already satisfied: wheel<1.0,>=0.23.0 in /usr/local/lib/python3.10/dist-packages (from astunparse>=1.6.0->tensorflow->antspynet) (0.42.0) Requirement already satisfied: parso<0.9.0,>=0.8.3 in /usr/local/lib/python3.10/dist-packages (from jedi>=0.16->ipython>=5.0.0->ipykernel) (0.8.3) Requirement already satisfied: attrs>=22.2.0 in /usr/local/lib/python3.10/dist-packages (from jsonschema>=2.6->nbformat->notebook) (23.2.0) Requirement already satisfied: jsonschema-specifications>=2023.03.6 in /usr/local/lib/python3.10/dist-packages (from jsonschema>=2.6->nbformat->notebook) (2023.12.1) Requirement already satisfied: referencing>=0.28.4 in /usr/local/lib/python3.10/dist-packages (from jsonschema>=2.6->nbformat->notebook) (0.32.0) Requirement already satisfied: rpds-py>=0.7.1 in /usr/local/lib/python3.10/dist-packages (from jsonschema>=2.6->nbformat->notebook) (0.16.2) Requirement already satisfied: anyio<4,>=3.1.0 in /usr/local/lib/python3.10/dist-packages (from jupyter-server>=1.8->nbclassic>=0.4.7->notebook) (3.7.1) Requirement already satisfied: websocket-client in /usr/local/lib/python3.10/dist-packages (from jupyter-server>=1.8->nbclassic>=0.4.7->notebook) (1.7.0) Requirement already satisfied: wcwidth in /usr/local/lib/python3.10/dist-packages (from prompt-toolkit!=3.0.0,!=3.0.1,<3.1.0,>=2.0.0->ipython>=5.0.0->ipykernel) (0.2.12) Requirement already satisfied: google-auth<3,>=1.6.3 in /usr/local/lib/python3.10/dist-packages (from tensorboard<2.16,>=2.15->tensorflow->antspynet) (2.17.3) Requirement already satisfied: google-auth-oauthlib<2,>=0.5 in /usr/local/lib/python3.10/dist-packages (from tensorboard<2.16,>=2.15->tensorflow->antspynet) (1.2.0) Requirement already satisfied: markdown>=2.6.8 in /usr/local/lib/python3.10/dist-packages (from tensorboard<2.16,>=2.15->tensorflow->antspynet) (3.5.1) Requirement already satisfied: tensorboard-data-server<0.8.0,>=0.7.0 in /usr/local/lib/python3.10/dist-packages (from tensorboard<2.16,>=2.15->tensorflow->antspynet) (0.7.2) Requirement already satisfied: werkzeug>=1.0.1 in /usr/local/lib/python3.10/dist-packages (from tensorboard<2.16,>=2.15->tensorflow->antspynet) (3.0.1) Requirement already satisfied: cffi>=1.0.1 in /usr/local/lib/python3.10/dist-packages (from argon2-cffi-bindings->argon2-cffi->notebook) (1.16.0) Requirement already satisfied: soupsieve>1.2 in /usr/local/lib/python3.10/dist-packages (from beautifulsoup4->nbconvert->jupyter) (2.5) Requirement already satisfied: webencodings in /usr/local/lib/python3.10/dist-packages (from bleach->nbconvert->jupyter) (0.5.1) Requirement already satisfied: tenacity>=6.2.0 in /usr/local/lib/python3.10/dist-packages (from plotly->chart-studio->antspyx) (8.2.3) Requirement already satisfied: sniffio>=1.1 in /usr/local/lib/python3.10/dist-packages (from anyio<4,>=3.1.0->jupyter-server>=1.8->nbclassic>=0.4.7->notebook) (1.3.0) Requirement already satisfied: exceptiongroup in /usr/local/lib/python3.10/dist-packages (from anyio<4,>=3.1.0->jupyter-server>=1.8->nbclassic>=0.4.7->notebook) (1.2.0) Requirement already satisfied: pycparser in /usr/local/lib/python3.10/dist-packages (from cffi>=1.0.1->argon2-cffi-bindings->argon2-cffi->notebook) (2.21) Requirement already satisfied: cachetools<6.0,>=2.0.0 in /usr/local/lib/python3.10/dist-packages (from google-auth<3,>=1.6.3->tensorboard<2.16,>=2.15->tensorflow->antspynet) (5.3.2) Requirement already satisfied: pyasn1-modules>=0.2.1 in /usr/local/lib/python3.10/dist-packages (from google-auth<3,>=1.6.3->tensorboard<2.16,>=2.15->tensorflow->antspynet) (0.3.0) Requirement already satisfied: rsa<5,>=3.1.4 in /usr/local/lib/python3.10/dist-packages (from google-auth<3,>=1.6.3->tensorboard<2.16,>=2.15->tensorflow->antspynet) (4.9) Requirement already satisfied: requests-oauthlib>=0.7.0 in /usr/local/lib/python3.10/dist-packages (from google-auth-oauthlib<2,>=0.5->tensorboard<2.16,>=2.15->tensorflow->antspynet) (1.3.1) Requirement already satisfied: pyasn1<0.6.0,>=0.4.6 in /usr/local/lib/python3.10/dist-packages (from pyasn1-modules>=0.2.1->google-auth<3,>=1.6.3->tensorboard<2.16,>=2.15->tensorflow->antspynet) (0.5.1) Requirement already satisfied: oauthlib>=3.0.0 in /usr/local/lib/python3.10/dist-packages (from requests-oauthlib>=0.7.0->google-auth-oauthlib<2,>=0.5->tensorboard<2.16,>=2.15->tensorflow->antspynet) (3.2.2) Building wheels for collected packages: ants Building wheel for ants (setup.py) ... done Created wheel for ants: filename=ants-0.0.7-py3-none-any.whl size=14522 sha256=38c5cb91e09ddfb12d85863883e3f16f400d7690fa7210f51398c4f1cf99c79d Stored in directory: /root/.cache/pip/wheels/2d/36/ad/719ec3c226c5547a8ed5a668a5fc95cf01919db0453dc220ca Successfully built ants Installing collected packages: SimpleITK, zope.interface, zope.event, retrying, qtpy, jedi, asgiref, gevent, Django, chart-studio, ants, qtconsole, antspyx, antspynet, jupyter Successfully installed Django-5.0.1 SimpleITK-2.3.1 ants-0.0.7 antspynet-0.2.3 antspyx-0.4.2 asgiref-3.7.2 chart-studio-1.1.0 gevent-23.9.1 jedi-0.19.1 jupyter-1.0.0 qtconsole-5.5.1 qtpy-2.4.1 retrying-1.3.4 zope.event-5.0 zope.interface-6.1

Now we can import modules in our code. Let me check whether it has successfully installed the AntsPy and SimpleITK or not. Those two are very important libraries for our task.

%matplotlib inline
# matplotlib will be displayed inline in the notebook

import os
import sys

sys.path.append('/content/drive/MyDrive/MRI_Image_Processing/notebooks')
# appending the path to include our custom helper
import helpers
from helpers import *
import ants
import SimpleITK as sitk
print(f'AntsPy Version = {ants.__version__}')
print(f'SimpleITK version = {sitk.__version__}')

Output:

AntsPy Version = 0.4.2
SimpleITK version = 2.3.1

The rest of the code with their output and necessary comments are given below:


# Define the base directory by getting the directory name of the specified path
BASE_DIR = os.path.dirname("/content/drive/MyDrive/MRI_Image_Processing/")

# Print the path to the project folder
print(f'project folder = {BASE_DIR}')

Output

project folder = /content/drive/MyDrive/MRI_Image_Processing
import os

# Define the directory path
directory_path = '/content/drive/MyDrive/MRI_Image_Processing/assets/raw_examples/'

# Initialize an empty list to store filenames
raw_examples = []

# Iterate through files in the directory and add filenames with extensions to raw_examples list
for filename in os.listdir(directory_path):
    # Check if the path refers to a file (not a subdirectory)
    if os.path.isfile(os.path.join(directory_path, filename)):
        raw_examples.append(filename)

# Display the updated raw_examples list
print(raw_examples)

Output:

['ADNI_136_S_0184_MR_MPR____N3__Scaled_2_Br_20081008132905229_S18601_I119714.nii.gz', 'ADNI_136_S_0195_MR_MPR____N3__Scaled_Br_20090708095227200_S65770_I148270.nii.gz', 'ADNI_136_S_0184_MR_MPR____N3__Scaled_Br_20080123103107781_S18601_I88159.nii.gz', 'ADNI_136_S_0086_MR_MPR____N3__Scaled_Br_20070815111150885_S31407_I67781.nii.gz', 'ADNI_136_S_0195_MR_MPR____N3__Scaled_2_Br_20081008133516751_S12748_I119721.nii.gz', 'ADNI_136_S_0184_MR_MPR____N3__Scaled_2_Br_20081008132712063_S12474_I119712.nii.gz', 'ADNI_136_S_0195_MR_MPR____N3__Scaled_Br_20080123103529723_S19574_I88164.nii.gz', 'ADNI_136_S_0184_MR_MPR-R____N3__Scaled_Br_20080415154909319_S47135_I102840.nii.gz', 'ADNI_136_S_0196_MR_MPR____N3__Scaled_Br_20070215192140032_S13831_I40269.nii.gz', 'ADNI_136_S_0195_MR_MPR____N3__Scaled_Br_20071113184014832_S28912_I81981.nii.gz', 'ADNI_136_S_0196_MR_MPR____N3__Scaled_Br_20070809224441151_S31829_I66740.nii.gz', 'ADNI_136_S_0086_MR_MPR____N3__Scaled_Br_20081013161936541_S49691_I120416.nii.gz', 'ADNI_136_S_0195_MR_MPR____N3__Scaled_2_Br_20081008133704276_S19574_I119723.nii.gz', 'ADNI_136_S_0086_MR_MPR____N3__Scaled_Br_20070215172221943_S14069_I40172.nii.gz', 'ADNI_136_S_0184_MR_MPR____N3__Scaled_Br_20070215174801158_S12474_I40191.nii.gz', 'ADNI_136_S_0195_MR_MPR-R____N3__Scaled_Br_20071110123442398_S39882_I81460.nii.gz', 'ADNI_136_S_0184_MR_MPR____N3__Scaled_Br_20090708094745554_S64785_I148265.nii.gz', 'ADNI_136_S_0195_MR_MPR____N3__Scaled_Br_20070215185520914_S12748_I40254.nii.gz', 'ADNI_136_S_0195_MR_MPR-R____N3__Scaled_Br_20081013162450631_S47389_I120423.nii.gz', 'ADNI_136_S_0184_MR_MPR-R____N3__Scaled_Br_20070819190556867_S28430_I69136.nii.gz']

Above, we can see all the necessary input data that we want to process.

from antspynet.utilities import brain_extraction

# Initialize an empty list to store the generated probabilistic brain masks
prob_brain_masks = []

# Loop through all items in the raw_examples list
for raw_example in raw_examples:
    # Create the full file path by joining the base directory with the path to the specific image file
    raw_img_path = os.path.join(BASE_DIR, 'assets', 'raw_examples', raw_example)

    # Read the image using AntsPy's image_read function, specifying reorientation to 'IAL'
    raw_img_ants = ants.image_read(raw_img_path, reorient='IAL')

    # Print the shape of the image as a numpy array in the format (Z, X, Y)
    print(f'shape = {raw_img_ants.numpy().shape} -> (Z, X, Y)')

    # Display the 3D array using a custom function 'explore_3D_array' with the image array and a specified colormap
    explore_3D_array(arr=raw_img_ants.numpy(), cmap='nipy_spectral')

    # Generate a probabilistic brain mask using the 'brain_extraction' function
    # The 'modality' parameter specifies the imaging modality as 'bold'
    # The 'verbose' parameter is set to 'True' to display detailed progress information
    prob_brain_mask = brain_extraction(raw_img_ants, modality='bold', verbose=True)

    # Append the generated probabilistic brain mask to the list
    prob_brain_masks.append(prob_brain_mask)

    # Print the filename or any relevant information about the current image (optional)
    print(f"Probabilistic Brain Mask for: {raw_example}")

    # Print the probabilistic brain mask
    print(prob_brain_mask)

    # Visualize the probabilistic brain mask using the 'explore_3D_array' function
    # This function displays the 3D array representation of the brain mask
    explore_3D_array(prob_brain_mask.numpy())

    # Generate a binary brain mask from the probabilistic brain mask using a threshold
    brain_mask = ants.get_mask(prob_brain_mask, low_thresh=0.5)

    # Visualize the original image overlaid with the brain mask contour
    explore_3D_array_with_mask_contour(raw_img_ants.numpy(), brain_mask.numpy())

    # Define the output folder path by joining the base directory with the 'assets' and 'preprocessed' directories
    out_folder = os.path.join(BASE_DIR, 'assets', 'preprocessed')

    # Create a subfolder within the 'preprocessed' directory named after the raw file (without extension)
    out_folder = os.path.join(out_folder, raw_example.split('.')[0])  # Create folder with name of the raw file

    # Create the output folder if it doesn't exist already
    os.makedirs(out_folder, exist_ok=True)  # Create folder if it doesn't exist

    # Generate a filename by adding the suffix 'brainMaskByDL' to the original raw file name
    out_filename = add_suffix_to_filename(raw_example, suffix='brainMaskByDL')

    # Create the full output file path by joining the output folder with the generated filename
    out_path = os.path.join(out_folder, out_filename)

    # Print the relative path of the input raw image file (excluding the base directory)
    print(raw_img_path[len(BASE_DIR):])

    # Print the relative path of the output file (excluding the base directory)
    print(out_path[len(BASE_DIR):])

    # Save the brain mask to a file
    brain_mask.to_file(out_path)

    # Create a masked image by applying the binary brain mask ('brain_mask') to the original image ('raw_img_ants')
    masked = ants.mask_image(raw_img_ants, brain_mask)

    # Visualize the 3D array representation of the masked image
    explore_3D_array(masked.numpy())

    # Generate a filename by adding the suffix 'brainMaskedByDL' to the original raw file name
    out_filename = add_suffix_to_filename(raw_example, suffix='brainMaskedByDL')

    # Create the full output file path by joining the output folder with the generated filename
    out_path = os.path.join(out_folder, out_filename)

    # Print the relative path of the input raw image file (excluding the base directory)
    print(raw_img_path[len(BASE_DIR):])

    # Print the relative path of the output file (excluding the base directory)
    print(out_path[len(BASE_DIR):])

    # Save the masked image ('masked') to a file specified by 'out_path'
    masked.to_file(out_path)

Conclusion

The output is very huge.

But you can check the entire project from here: FahimFBA/skull-stripping-3D-brain-mri/

Also, you will find a warning in the output like below:

WARNING:tensorflow:5 out of the last 5 calls to <function Model.make_predict_function.<locals>.predict_function at 0x792af6defe20> triggered tf.function retracing. Tracing is expensive and the excessive number of tracings could be due to (1) creating @tf.function repeatedly in a loop, (2) passing tensors with different shapes, (3) passing Python objects instead of tensors. For (1), please define your @tf.function outside of the loop. For (2), @tf.function has reduce_retracing=True option that can avoid unnecessary retracing. For (3), please refer to https://www.tensorflow.org/guide/function#controlling_retracing and https://www.tensorflow.org/api_docs/python/tf/function for  more details.

I can solve it for you. But I am leaving this task for you to resolve! 😉

You can find my raw data and the preprocessed data in the assets directory.

The complete notebook with all the outputs is here!

Some sample images of the output images are given below.

Brain mask

Brain area detection

Make sure to ⭐ the repository if you like this!

Cheers! 😊