Interactive Tutorial
This page provides an interactive Jupyter notebook tutorial for learning MedTDA.
Overview
The interactive tutorial notebook provides a hands-on introduction to MedTDA with real examples, visualizations, and explanations. It covers:
Loading 3D (NIfTI) and 2D (JPEG/PNG) medical images
Preprocessing: resampling, CT windowing, normalization, ROI cropping
Computing persistence diagrams with
BarcodeExtractorVisualizing barcodes, persistence diagrams, Betti curves, landscapes, and more
Comparing sublevel vs superlevel filtrations
Feature extraction with
FeatureExtractorand all vectorization methods
Tutorial Notebook
The complete tutorial is available as a Jupyter notebook in the tutorials/ directory of the repository.
Location: tutorials/medtda_tutorial.ipynb
Repository: MedTDA on GitHub
Running the Tutorial
Local Installation
Clone the repository:
git clone https://github.com/dashtiali/medtda.git cd medtda
Install dependencies:
pip install -e . pip install jupyter matplotlib
Launch Jupyter:
jupyter notebook tutorials/medtda_tutorial.ipynb
Google Colab
Run the tutorial in Google Colab without local installation:
Binder
Run interactively in your browser using Binder:
Tutorial Contents
The notebook is divided into two main sections covering 3D medical images (NIfTI) and 2D images (JPEG/PNG).
Part 1 — 3D Medical Images
1. Loading the Data
Load NIfTI images and masks using SimpleITK. Supports .nii, .nii.gz, .mha, .mhd, and .nrrd formats.
Code preview:
import SimpleITK as sitk
from medtda import Preprocessor, BarcodeExtractor, FeatureExtractor
from medtda import plotting
image_path = 'scan.nii.gz'
mask_path = 'mask.nii.gz'
sitk_image = sitk.ReadImage(image_path)
image = sitk.GetArrayFromImage(sitk_image) # (Z, Y, X)
print(f"Image shape: {image.shape}")
print(f"Intensity range: [{image.min():.3f}, {image.max():.3f}]")
2. Visualize Loaded Data
Display the middle axial slice of the image alongside the corresponding mask slice.
Code preview:
mid_z = image.shape[0] // 2
fig, axes = plt.subplots(1, 2, figsize=(8, 15))
axes[0].imshow(slice_image, cmap='gray')
axes[0].set_title(f'Image - Axial Slice (Z={mid_z})')
axes[1].imshow(mask[mid_z], cmap='Reds', alpha=0.7)
axes[1].set_title(f'Mask - Axial Slice (Z={mid_z})')
plt.tight_layout()
plt.show()
3. Preprocessing
Configure and apply preprocessing including resampling, CT windowing, normalization, label selection, and ROI cropping.
Basic Preprocessing — create a
Preprocessorand inspect its settings withget_preprocessing_info()Visualize Preprocessing Effects — compare original vs preprocessed image side by side
Test Different Normalization Methods — compare
minmax,zscore, and no normalization
Code preview:
preprocessor = Preprocessor(
spacing=[1.0, 1.0, 2.5],
window=[100, 400], # CT windowing (HU range)
normalize=True,
label=2, # Extract label 2 from multi-label mask
crop_to_roi=True,
roi_padding=5
)
preprocessor.get_preprocessing_info()
preprocessed_image, metadata = preprocessor.preprocess(image_path, mask_path)
print(f"Preprocessed shape: {preprocessed_image.shape}")
4. Barcode Computation
Use BarcodeExtractor to compute persistence diagrams directly from image and mask paths.
Code preview:
barcode_extractor = BarcodeExtractor(
spacing=[1.0, 1.0, 2.5],
crop_to_roi=True,
roi_padding=5,
label=2,
normalize=True,
normalize_method='minmax',
max_dimension=2, # H0, H1, H2
filtration_type='sublevel', # Track bright features
)
persistence_diagram = barcode_extractor.execute(image_path, mask_path)
print(f"H0 features: {len(persistence_diagram['H0'])}")
print(f"H1 features: {len(persistence_diagram['H1'])}")
5. Visualizations
MedTDA provides a full suite of visualization functions for persistence diagrams.
Code preview:
# Persistence diagram and barcode
plotting.plot_persistence_diagram(persistence_diagram, ax=ax, markersize=5)
plotting.plot_barcode(persistence_diagram, dimensions=[0])
# Betti curves, lifespan, entropy summary, silhouette
fig, ax = plt.subplots(figsize=(20, 4), ncols=4)
plotting.plot_betti_curve(persistence_diagram, resolution=100, ax=ax[0])
plotting.plot_lifespan(persistence_diagram, resolution=100, ax=ax[1])
plotting.plot_entropy_summary(persistence_diagram, resolution=100, ax=ax[2])
plotting.plot_silhouette(persistence_diagram, resolution=100, ax=ax[3])
# Persistence landscapes (one panel per homology dimension)
fig, ax = plt.subplots(figsize=(20, 6), ncols=3)
for dim in range(3):
plotting.plot_landscape(persistence_diagram, ax=ax[dim],
dimension=dim, num_landscapes=5, resolution=50)
# Tropical coordinates
plotting.plot_tropical_coordinates(persistence_diagram, bar_width=0.25)
6. Comparing PH Filtration Types
Compare sublevel (bright structures) and superlevel (dark structures) filtrations side by side.
Code preview:
extractor_sublevel = BarcodeExtractor(
max_dimension=2, filtration_type='sublevel',
crop_to_roi=True, roi_padding=1,
normalize=True, label=2, normalize_method='minmax'
)
barcodes_sublevel = extractor_sublevel.execute(image, mask)
extractor_superlevel = BarcodeExtractor(
max_dimension=2, filtration_type='superlevel',
crop_to_roi=True, roi_padding=1,
normalize=True, label=2, normalize_method='minmax'
)
barcodes_superlevel = extractor_superlevel.execute(image, mask)
print(f"Sublevel — H0: {len(barcodes_sublevel['H0'])}, H1: {len(barcodes_sublevel['H1'])}")
print(f"Superlevel — H0: {len(barcodes_superlevel['H0'])}, H1: {len(barcodes_superlevel['H1'])}")
7. Feature Extraction
Use FeatureExtractor to run the full pipeline (preprocessing → PH → vectorization) in one call.
Code preview:
# Default vectorization (PersStats)
extractor = FeatureExtractor(
spacing=[1.0, 1.0, 2.5],
crop_to_roi=True,
normalize=True,
label=2,
max_dimension=2,
construction='V',
)
features = extractor.execute(image_path, mask_path)
print(f"Number of features: {len(features)}")
# Switch to PersLandscape and customize parameters
extractor.set_vectorization_method('PersLandscape')
extractor.set_vectorization_params('PersLandscape', num_landscapes=5, resolution=100)
extractor.get_settings()
features = extractor.execute(image_path, mask_path)
print(f"Feature vector shape: {np.array(list(features.values())).shape}")
Part 2 — 2D Examples
The same workflow applies to 2D images (JPEG, PNG, TIFF, etc.); no SimpleITK loading is required and masks are optional.
1. Loading & Visualizing
Code preview:
image_path = 'image.jpg'
img_2d = plt.imread(image_path)
plt.imshow(img_2d, cmap='gray' if img_2d.ndim == 2 else None)
plt.title(f'Shape: {img_2d.shape}')
plt.axis('off')
plt.show()
2. Barcode Computation & Visualizations
Identical API — pass the image path directly to BarcodeExtractor.execute().
Code preview:
barcode_extractor = BarcodeExtractor(
max_dimension=1, # H0 and H1 for 2D
filtration_type='sublevel',
)
persistence_diagram = barcode_extractor.execute(image_path)
plotting.plot_persistence_diagram(persistence_diagram, ax=ax, markersize=5)
plotting.plot_barcode(persistence_diagram, linewidth=1)
fig, ax = plt.subplots(figsize=(20, 4), ncols=4)
plotting.plot_betti_curve(persistence_diagram, resolution=100, ax=ax[0])
plotting.plot_lifespan(persistence_diagram, resolution=100, ax=ax[1])
plotting.plot_entropy_summary(persistence_diagram, resolution=100, ax=ax[2])
plotting.plot_silhouette(persistence_diagram, resolution=100, ax=ax[3])
fig, ax = plt.subplots(figsize=(20, 6), ncols=2)
for dim in range(2):
plotting.plot_landscape(persistence_diagram, ax=ax[dim], dimension=dim,
num_landscapes=5, resolution=50)
plotting.plot_tropical_coordinates(persistence_diagram, bar_width=0.25)
3. Feature Extraction
Code preview:
# Default (PersStats)
extractor = FeatureExtractor()
features = extractor.execute(image_path)
print(f"Number of features: {len(features)}")
# Switch to PersLandscape
extractor = FeatureExtractor(normalize=True, normalize_method='minmax')
extractor.set_vectorization_method('PersLandscape')
extractor.set_vectorization_params('PersLandscape', num_landscapes=5, resolution=100)
features = extractor.execute(image_path)
print(f"Feature vector shape: {np.array(list(features.values())).shape}")
4. Comparing PH Filtration Types
Code preview:
ext_sub = BarcodeExtractor(max_dimension=1, filtration_type='sublevel')
ext_super = BarcodeExtractor(max_dimension=1, filtration_type='superlevel')
barcodes_sub = ext_sub.execute(image_path)
barcodes_super = ext_super.execute(image_path)
print(f"Sublevel — H0: {len(barcodes_sub['H0'])}, H1: {len(barcodes_sub['H1'])}")
print(f"Superlevel — H0: {len(barcodes_super['H0'])}, H1: {len(barcodes_super['H1'])}")
fig, axes = plt.subplots(1, 2, figsize=(14, 6))
plotting.plot_persistence_diagram(barcodes_sub, ax=axes[0], markersize=5)
axes[0].set_title('Sublevel (Bright Features)')
plotting.plot_persistence_diagram(barcodes_super, ax=axes[1], markersize=5)
axes[1].set_title('Superlevel (Dark Features)')
plt.tight_layout()
plt.show()
Sample Data
The notebook is designed to work with your own data. Update the image_path and mask_path variables in the relevant cells to point to your files.
3D section — expects NIfTI or other volumetric formats supported by SimpleITK (.nii, .nii.gz, .mha, .mhd, .nrrd).
2D section — expects any standard image format (.jpg, .png, .tiff). Masks are optional.
Getting Help Getting Help ============
If you encounter issues with the tutorial:
Check the FAQ: Frequently Asked Questions
Search GitHub Issues: MedTDA Issues
Ask a question: Open a new issue
Join discussion: GitHub Discussions
Contributing
Found a bug in the tutorial or have a suggestion? Please:
Open an issue: Report a bug
Submit a pull request: Contributing to MedTDA
Share your notebook: We’d love to feature community tutorials!
Citation
If you use this tutorial in your research or teaching, please cite MedTDA:
@software{medtda2026,
title={Med-TDA: Medical Imaging Topological Data Analysis Tool},
author={Dashti A. Ali, Amber L. Simpson},
year={2026},
url={https://github.com/dashtiali/medtda}
}
Next Steps
After completing the tutorial:
User Guide - Comprehensive user guide
Examples - More examples
API Reference - API reference
TDA Theory - Theoretical background
See Also
Quick Start - Quick start guide
Basic Usage - Basic usage examples
Persistent Homology - Mathematical background