Introduction
In this tutorial, we'll build an end-to-end 3D medical image segmentation pipeline using MONAI to segment the spleen from CT scans. We'll work with the Medical Segmentation Decathlon Task09 dataset, which contains 3D volumetric CT scans of the abdominal region. This tutorial will guide you through preprocessing medical images, applying transformations, and training a 3D UNet model for semantic segmentation.
Prerequisites
- Basic understanding of Python and PyTorch
- MONAI library installed (run
pip install monai) - Basic knowledge of medical image segmentation concepts
- Access to a GPU for training (optional but recommended)
Step-by-step Instructions
1. Setting Up the Environment
1.1 Install Required Libraries
We'll need several libraries for this project. First, install MONAI and other dependencies:
pip install monai torch torchvision numpy matplotlib
1.2 Import Libraries
Import the necessary modules for our pipeline:
import os
import numpy as np
import torch
import matplotlib.pyplot as plt
from monai.data import Dataset, DataLoader
from monai.transforms import (
Compose,
LoadImaged,
EnsureChannelFirstd,
Orientationd,
Spacingd,
ScaleIntensityRanged,
CropForegroundd,
RandCropByPosNegLabeld,
RandRotated,
RandFlipd,
RandGaussianNoised,
ToTensord
)
from monai.networks.nets import UNet
from monai.losses import DiceLoss
from monai.optimizers import Novograd
from monai.metrics import DiceMetric
from monai.inferers import sliding_window_inference
2. Loading and Preprocessing Data
2.1 Define Data Paths
We'll work with the Task09 dataset. First, set up the paths to your data directory:
# Define data directory
DATA_DIR = "./Task09_Spleen"
# Define data list for training
train_files = [
{"image": os.path.join(DATA_DIR, "imagesTr", "spleen_001.nii.gz"), "label": os.path.join(DATA_DIR, "labelsTr", "spleen_001.nii.gz")},
{"image": os.path.join(DATA_DIR, "imagesTr", "spleen_002.nii.gz"), "label": os.path.join(DATA_DIR, "labelsTr", "spleen_002.nii.gz")},
# Add more files as needed
]
2.2 Create Data Transforms
Define the preprocessing pipeline that will normalize and augment our medical images:
# Define transforms
train_transforms = Compose([
LoadImaged(keys=["image", "label"]),
EnsureChannelFirstd(keys=["image", "label"]),
Orientationd(keys=["image", "label"], axcodes="RAS"),
Spacingd(keys=["image", "label"], pixdim=(1.0, 1.0, 1.0), mode=("bilinear", "nearest")),
ScaleIntensityRanged(keys=["image"], a_min=-500, a_max=500, b_min=0.0, b_max=1.0, clip=True),
CropForegroundd(keys=["image", "label"], source_key="image"),
RandCropByPosNegLabeld(
keys=["image", "label"],
label_key="label",
spatial_size=(96, 96, 96),
pos=1,
neg=1,
num_samples=4,
image_key="image",
image_threshold=0,
),
RandRotated(keys=["image", "label"], range_x=0.1, range_y=0.1, range_z=0.1, prob=0.5),
RandFlipd(keys=["image", "label"], spatial_axis=0, prob=0.5),
RandGaussianNoised(keys=["image"], mean=0.0, std=0.1, prob=0.5),
ToTensord(keys=["image", "label"])
])
3. Creating the Dataset and DataLoader
3.1 Initialize Dataset
Create a MONAI dataset using our transforms:
# Create dataset
train_ds = Dataset(data=train_files, transform=train_transforms)
# Create dataloader
train_loader = DataLoader(train_ds, batch_size=2, shuffle=True, num_workers=4)
3.2 Verify Data Loading
Check that our data loads correctly:
# Verify data loading
sample = next(iter(train_loader))
print(f"Image shape: {sample['image'].shape}")
print(f"Label shape: {sample['label'].shape}")
4. Building the 3D UNet Model
4.1 Define Model Architecture
Create a 3D UNet model for segmentation:
# Define the UNet model
model = UNet(
spatial_dims=3,
in_channels=1,
out_channels=2,
channels=(32, 64, 128, 256),
strides=(2, 2, 2),
num_res_units=2,
)
# Move model to device
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = model.to(device)
4.2 Set Up Loss and Optimizer
Configure the loss function and optimizer:
# Define loss function and optimizer
loss_function = DiceLoss(to_onehot_y=True, softmax=True)
optimizer = Novograd(model.parameters(), lr=0.001)
5. Training the Model
5.1 Training Loop
Implement the training loop:
# Training loop
num_epochs = 50
for epoch in range(num_epochs):
model.train()
epoch_loss = 0
for batch_data in train_loader:
inputs = batch_data["image"].to(device)
labels = batch_data["label"].to(device)
optimizer.zero_grad()
outputs = model(inputs)
loss = loss_function(outputs, labels)
loss.backward()
optimizer.step()
epoch_loss += loss.item()
print(f"Epoch {epoch+1}/{num_epochs}, Loss: {epoch_loss/len(train_loader):.4f}")
6. Evaluation and Inference
6.1 Create Evaluation Metrics
Set up metrics for evaluation:
# Create evaluation metrics
metric = DiceMetric(include_background=True, reduction="mean_batch")
6.2 Perform Inference
Run inference on test data:
# Perform inference
model.eval()
with torch.no_grad():
for test_data in test_loader:
test_inputs = test_data["image"].to(device)
test_labels = test_data["label"].to(device)
# Use sliding window inference
pred = sliding_window_inference(test_inputs, (96, 96, 96), 4, model)
# Compute metrics
metric(pred, test_labels)
# Display results
plt.figure(figsize=(12, 4))
plt.subplot(1, 3, 1)
plt.imshow(test_inputs[0, 0, :, :, 48].cpu().numpy(), cmap='gray')
plt.title('Input Image')
plt.subplot(1, 3, 2)
plt.imshow(test_labels[0, 0, :, :, 48].cpu().numpy(), cmap='gray')
plt.title('Ground Truth')
plt.subplot(1, 3, 3)
plt.imshow(pred[0, 1, :, :, 48].cpu().numpy(), cmap='gray')
plt.title('Prediction')
plt.show()
Summary
In this tutorial, we've built a complete end-to-end pipeline for 3D spleen segmentation using MONAI. We covered data loading, preprocessing with appropriate medical image transforms, model architecture definition, training, and evaluation. This pipeline can be extended to other organs or medical imaging tasks by adjusting the dataset paths, transforms, and model parameters. The MONAI library provides powerful tools for handling medical imaging data and makes it straightforward to implement complex segmentation workflows.



