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 BarcodeExtractor

  • Visualizing barcodes, persistence diagrams, Betti curves, landscapes, and more

  • Comparing sublevel vs superlevel filtrations

  • Feature extraction with FeatureExtractor and 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

  1. Clone the repository:

    git clone https://github.com/dashtiali/medtda.git
    cd medtda
    
  2. Install dependencies:

    pip install -e .
    pip install jupyter matplotlib
    
  3. Launch Jupyter:

    jupyter notebook tutorials/medtda_tutorial.ipynb
    

Google Colab

Run the tutorial in Google Colab without local installation:

Open In Colab

Binder

Run interactively in your browser using Binder:

Launch 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 Preprocessor and inspect its settings with get_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:

  1. Check the FAQ: Frequently Asked Questions

  2. Search GitHub Issues: MedTDA Issues

  3. Ask a question: Open a new issue

  4. Join discussion: GitHub Discussions

Contributing

Found a bug in the tutorial or have a suggestion? Please:

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:

See Also