Preprocessing
MedTDA provides comprehensive preprocessing capabilities for medical images. This guide covers all preprocessing options and how to use them effectively.
Overview
Preprocessing transforms raw medical images into a standardized format suitable for persistent homology computation. MedTDA supports:
Format Conversion - Load various medical image formats
Intensity Normalization - Standardize intensity values
Resampling - Change voxel spacing (3D/4D only)
Windowing - Apply intensity windowing (CT images)
Masking - Apply binary or multi-label masks
ROI Cropping - Reduce computation by cropping to region of interest
All preprocessing is configured through FeatureExtractor parameters.
Supported Image Formats
2D Images
PNG (.png)
JPEG (.jpg, .jpeg)
TIFF (.tif, .tiff)
BMP (.bmp)
from medtda import FeatureExtractor
import numpy as np
from PIL import Image
# Load 2D image
image = np.array(Image.open('image.png').convert('L'))
extractor = FeatureExtractor()
features = extractor.execute(image)
3D/4D Medical Images
Requires SimpleITK:
NIfTI (.nii, .nii.gz)
NRRD (.nrrd)
MetaImage (.mha, .mhd)
import SimpleITK as sitk
from medtda import FeatureExtractor
# Load 3D medical image
image = sitk.ReadImage('scan.nii.gz')
extractor = FeatureExtractor()
features = extractor.execute(image)
File Paths vs Arrays
MedTDA accepts both file paths and NumPy/SimpleITK arrays:
# Using file paths (recommended for 3D)
features = extractor.execute(
image='scan.nii.gz',
mask='mask.nii.gz'
)
# Using NumPy arrays
import numpy as np
image_array = np.random.rand(100, 100, 50)
mask_array = np.random.randint(0, 2, (100, 100, 50))
features = extractor.execute(image=image_array, mask=mask_array)
# Using SimpleITK images
import SimpleITK as sitk
image_sitk = sitk.ReadImage('scan.nii.gz')
features = extractor.execute(image=image_sitk)
Intensity Normalization
Normalization standardizes intensity values across images, crucial for consistent TDA features.
Enable Normalization
extractor = FeatureExtractor(
normalize=True, # Enable normalization
normalize_method='minmax' # Choose method
)
Normalization Methods
Min-Max Normalization (minmax)
Scales values to [0, 1]:
extractor = FeatureExtractor(
normalize=True,
normalize_method='minmax'
)
Pros: Preserves relative differences, bounded output
Z-Score Normalization (zscore)
Standardizes to zero mean and unit variance:
extractor = FeatureExtractor(
normalize=True,
normalize_method='zscore'
)
Pros: Removes scale, handles outliers better than minmax
Robust Scaling (robust)
Uses median and interquartile range:
extractor = FeatureExtractor(
normalize=True,
normalize_method='robust'
)
Pros: Very robust to outliers
Resampling and Spacing
Resampling changes voxel spacing, useful for standardizing spatial resolution across 3D/4D images.
Target Spacing
Specify target spacing in mm (or image units):
# Resample to 1mm isotropic (same spacing in all directions)
extractor = FeatureExtractor(
spacing=(1.0, 1.0, 1.0)
)
# Resample to anisotropic spacing
extractor = FeatureExtractor(
spacing=(0.5, 0.5, 2.0) # Higher resolution in xy, lower in z
)
For 2D images:
extractor = FeatureExtractor(
spacing=(0.25, 0.25) # 2D spacing
)
Why Resample?
Benefits:
Standardizes spatial resolution across images
Enables fair comparison of topological features
Can reduce computation time (downsample large images)
Makes features independent of acquisition parameters
Considerations:
Upsampling (small spacing) increases computation time
Downsampling (large spacing) may lose fine details
Choose spacing based on feature size of interest
Common Spacing Values
CT Scans:
# Whole body: 2-3mm isotropic
extractor = FeatureExtractor(spacing=(2.0, 2.0, 2.0))
# Chest/abdomen: 1-1.5mm isotropic
extractor = FeatureExtractor(spacing=(1.0, 1.0, 1.0))
# High-resolution: 0.5-0.75mm isotropic
extractor = FeatureExtractor(spacing=(0.5, 0.5, 0.5))
MRI:
# Standard brain MRI: 1mm isotropic
extractor = FeatureExtractor(spacing=(1.0, 1.0, 1.0))
Automatic Detection
If spacing=None (default), no resampling is performed:
extractor = FeatureExtractor(spacing=None) # Use original spacing
Windowing
Windowing applies an intensity window, commonly used for CT images to focus on specific tissue types.
Window Parameters
Specify as (center, width):
# Soft tissue window for CT
extractor = FeatureExtractor(
window=(40, 400) # Center=40 HU, Width=400 HU
)
# Lung window
extractor = FeatureExtractor(
window=(-600, 1500) # Center=-600 HU, Width=1500 HU
)
# Bone window
extractor = FeatureExtractor(
window=(400, 1800) # Center=400 HU, Width=1800 HU
)
Values outside the window are clipped.
Common CT Windows
Tissue Type |
Center (HU) |
Width (HU) |
Use Case |
|---|---|---|---|
Soft Tissue |
40 |
400 |
General abdomen/pelvis |
Lung |
-600 |
1500 |
Chest CT, lung nodules |
Bone |
400 |
1800 |
Skeletal imaging |
Brain |
40 |
80 |
Brain CT |
Liver |
60 |
150 |
Liver lesions |
Masking
Masks restrict analysis to specific regions of interest.
Binary Masks
Simple foreground/background masks:
extractor = FeatureExtractor(
background_value=0, # Value for background pixels
crop_to_roi=True # Crop to mask bounding box
)
features = extractor.execute(
image='scan.nii.gz',
mask='binary_mask.nii.gz' # 0=background, >0=foreground
)
Multi-Label Masks
Extract specific labels from multi-label segmentations:
# Extract label 1 (e.g., tumor)
extractor = FeatureExtractor(
label=1, # Extract this label only
background_value=0, # Value for background
crop_to_roi=True
)
features = extractor.execute('scan.nii.gz', 'multilabel_mask.nii.gz')
Process Multiple Labels
Extract features for each label separately:
labels = [1, 2, 3] # Tumor core, edema, enhancing
results = {}
for label_id in labels:
extractor = FeatureExtractor(
label=label_id,
normalize=True,
crop_to_roi=True,
vectorization_method='PersStats'
)
features = extractor.execute('scan.nii.gz', 'mask.nii.gz')
results[f'label_{label_id}'] = features
Background Value
Control what value to assign to masked-out regions:
# Set background to 0 (common)
extractor = FeatureExtractor(background_value=0)
# Set background to minimum image value
extractor = FeatureExtractor(background_value=None) # Auto-detect
# Set background to specific value
extractor = FeatureExtractor(background_value=-1000) # E.g., air in CT
ROI Cropping
Cropping to the region of interest reduces memory usage and computation time.
Enable ROI Cropping
extractor = FeatureExtractor(
crop_to_roi=True, # Enable cropping (default)
roi_padding=1 # Padding in pixels (default=1)
)
Padding
Add padding around the ROI bounding box:
# No padding (tight crop)
extractor = FeatureExtractor(crop_to_roi=True, roi_padding=0)
# Small padding (1-2 pixels)
extractor = FeatureExtractor(crop_to_roi=True, roi_padding=2)
# Large padding (preserve context)
extractor = FeatureExtractor(crop_to_roi=True, roi_padding=5)
Recommendations:
Use
roi_padding=1or2for most casesIncrease for features that depend on surrounding context
Decrease to
0for maximum speed on large volumes
Disable Cropping
Process the full image:
extractor = FeatureExtractor(crop_to_roi=False)
When to disable:
ROI is already tightly cropped
Studying whole-image topology
Very small images
Complete Preprocessing Example
Combining all preprocessing options:
from medtda import FeatureExtractor
extractor = FeatureExtractor(
# Resampling
spacing=(1.0, 1.0, 1.0), # Isotropic 1mm
# Windowing
window=(40, 400), # Soft tissue window
# Normalization
normalize=True, # Enable normalization
normalize_method='minmax', # Min-max to [0,1]
# Masking
label=1, # Extract label 1
background_value=0, # Background = 0
# ROI Cropping
crop_to_roi=True, # Enable cropping
roi_padding=2, # 2-pixel padding
# Persistent Homology
filtration_type='sublevel',
max_dimension=2,
# Vectorization
vectorization_method='PersStats'
)
# Extract features
features = extractor.execute('ct_scan.nii.gz', 'tumor_mask.nii.gz')
Using the Preprocessor Directly
For standalone preprocessing without feature extraction:
from medtda import Preprocessor
preprocessor = Preprocessor(
spacing=(1.0, 1.0, 1.0),
window=(40, 400),
normalize=True,
normalize_method='minmax',
background_value=0,
label=1,
crop_to_roi=True,
roi_padding=2
)
# Preprocess image
image_preprocessed, mask_preprocessed = preprocessor.execute(
image='scan.nii.gz',
mask='mask.nii.gz'
)
# Returns NumPy arrays
print(image_preprocessed.shape)
Best Practices
Always normalize for consistency across images
Resample to isotropic spacing for 3D images (e.g., 1mm)
Use ROI cropping to improve performance
Choose appropriate windowing for CT images
Process each label separately for multi-label masks
Document preprocessing parameters for reproducibility
Troubleshooting
Image is too large / out of memory
Increase
spacingto downsampleEnable
crop_to_roiReduce
roi_padding
Features are inconsistent across images
Ensure all images use same preprocessing
Always enable
normalizeUse consistent
spacingacross dataset
Preprocessing is slow
Downsample with larger
spacingEnable
crop_to_roiSkip windowing if not needed
Mask and image dimensions don’t match
Check that mask corresponds to the image
Ensure both are loaded correctly
Verify coordinate systems match (for medical images)
Next Steps
Persistent Homology - Understanding PH computation
Vectorization - Choosing vectorization methods
Batch Processing - Processing multiple images
Preprocessor - Preprocessor API reference