Utils
The utils module provides utility functions for image processing, including resampling, normalization, masking, and cropping.
Overview
This module contains low-level utility functions used by the Preprocessor class. These functions can also be used standalone for custom workflows.
Key Functions:
resample_image- Resample to target spacingnormalize_image- Intensity normalization (3 methods)apply_mask- Apply binary mask to imagecrop_to_roi- Crop to ROI bounding boxextract_label_from_mask- Extract single labelget_image_statistics- Compute image statisticscheck_array_dimensions- Validate array dimensions
Functions
Image Resampling
resample_image
- medtda.utils.resample_image(image_array, original_spacing, target_spacing, interpolator='linear')[source]
Resample image to a new voxel spacing using SimpleITK.
This function resamples a 3D or 4D image to match the target spacing, which is useful for standardizing images from different scanners or acquisition protocols.
- Parameters:
image_array (numpy.ndarray) – Input image array (3D or 4D).
original_spacing (tuple of float) – Original voxel spacing (x, y, z) or (x, y, z, t). For SimpleITK, this is typically (width, height, depth).
target_spacing (tuple of float) – Target voxel spacing (x, y, z) or (x, y, z, t).
interpolator ({'linear', 'nearest', 'bspline'}, default='linear') – Interpolation method: - ‘linear’: Linear interpolation (good for images) - ‘nearest’: Nearest neighbor (good for masks/labels) - ‘bspline’: B-spline interpolation (smooth but slower)
- Returns:
Resampled image array.
- Return type:
- Raises:
ImportError – If SimpleITK is not installed.
ValueError – If image dimensions or spacing don’t match.
Examples
>>> import numpy as np >>> image = np.random.rand(100, 100, 50) >>> # Resample from 1mm × 1mm × 2mm to isotropic 1mm³ >>> resampled = resample_image( ... image, ... original_spacing=(1.0, 1.0, 2.0), ... target_spacing=(1.0, 1.0, 1.0) ... ) >>> print(resampled.shape) (100, 100, 100)
Resample an image to target voxel spacing.
Signature:
def resample_image(
image_array: np.ndarray,
original_spacing: Tuple[float, ...],
target_spacing: Tuple[float, ...],
interpolator: str = 'linear'
) -> np.ndarray
Parameters:
image_array (np.ndarray) - Input image (3D or 4D)
original_spacing (tuple) - Current voxel spacing
target_spacing (tuple) - Desired voxel spacing
interpolator (str) - Interpolation method:
'linear','nearest', or'bspline'
Returns:
resampled_image (np.ndarray) - Resampled image
Example:
from medtda.utils import resample_image
# Resample to isotropic 1mm spacing
resampled = resample_image(
image,
original_spacing=(0.5, 0.5, 2.0),
target_spacing=(1.0, 1.0, 1.0),
interpolator='linear'
)
print(f"Original shape: {image.shape}")
print(f"Resampled shape: {resampled.shape}")
Normalization
normalize_image
- medtda.utils.normalize_image(image_array, method='minmax', mask=None, label=1, clip_percentiles=None)[source]
Normalize image array.
- Parameters:
image_array (numpy.ndarray) – Input image array to normalize.
method ({'minmax', 'zscore', 'robust'}, default='minmax') – Normalization method: - ‘minmax’: Min-max scaling to [0, 1] - ‘zscore’: Z-score normalization (mean=0, std=1) - ‘robust’: Robust scaling using median and IQR
mask (numpy.ndarray, optional) – Mask array. If provided, statistics are computed only within the masked region.
label (int or None, default=1) – For multi-label masks, which label value to consider as foreground. If None, any non-zero value is considered foreground.
clip_percentiles (tuple of float, optional) – If provided, clip values to (lower_percentile, upper_percentile) before normalization. For example, (1, 99) clips to 1st and 99th percentiles, removing extreme outliers.
- Returns:
Normalized image array.
- Return type:
- Raises:
ValueError – If method is not recognized or arrays have incompatible shapes.
Normalize image intensities using various methods.
Signature:
def normalize_image(
image_array: np.ndarray,
method: str = 'minmax',
mask: Optional[np.ndarray] = None,
label: Optional[int] = 1,
clip_percentiles: Optional[Tuple[float, float]] = None
) -> np.ndarray
Parameters:
image_array (np.ndarray) - Input image array
method (str) - Normalization method:
'minmax','zscore', or'robust'mask (np.ndarray or None) - Optional mask (normalize masked region only)
label (int or None) - For multi-label masks, which label to consider as foreground
clip_percentiles (tuple or None) - If provided, clip values to (lower_percentile, upper_percentile) before normalization. For example, (1, 99) clips to 1st and 99th percentiles. Set to None to disable clipping.
Returns:
normalized_image (np.ndarray) - Normalized image
Methods:
minmax: Scales to [0, 1] range
zscore: Mean=0, std=1
robust: Median=0, IQR=1
Example:
from medtda.utils import normalize_image
# Min-max normalization
norm_minmax = normalize_image(image, method='minmax')
# Z-score normalization
norm_zscore = normalize_image(image, method='zscore')
# Robust normalization (good for outliers)
norm_robust = normalize_image(image, method='robust')
# Normalize only inside mask
norm_masked = normalize_image(
image,
method='minmax',
mask=roi_mask
)
Masking
apply_mask
- medtda.utils.apply_mask(image_array, mask, background_value=0, label=1)[source]
Apply mask to image array.
Sets values outside the mask to background_value.
- Parameters:
image_array (numpy.ndarray) – Input image array.
mask (numpy.ndarray) – Mask array (same shape as image).
background_value (float, default=0) – Value to assign to pixels outside the mask.
label (int or None, default=1) – For multi-label masks, which label value to consider as foreground. If None, any non-zero value is considered foreground.
- Returns:
Masked image array.
- Return type:
- Raises:
ValueError – If mask shape doesn’t match image shape.
Apply a binary mask to an image.
Signature:
def apply_mask(
image: np.ndarray,
mask: np.ndarray,
background_value: float = 0.0
) -> np.ndarray
Parameters:
image (np.ndarray) - Input image
mask (np.ndarray) - Binary mask
background_value (float) - Value for masked-out pixels
Returns:
masked_image (np.ndarray) - Image with mask applied
Example:
from medtda.utils import apply_mask
# Set background to 0
masked = apply_mask(image, mask, background_value=0.0)
# Use image minimum as background
masked = apply_mask(image, mask, background_value=image.min())
extract_label_from_mask
- medtda.utils.extract_label_from_mask(mask, label=None)[source]
Convert multi-label mask to binary mask for specified label.
- Parameters:
mask (numpy.ndarray) – Input mask array (can be binary or multi-label).
label (int or None, default=None) – Label value to extract. If None, treat mask as binary (any non-zero value is considered foreground).
- Returns:
Binary mask where specified label = 1, others = 0.
- Return type:
Examples
>>> multi_label_mask = np.array([[0, 1, 2], [1, 2, 3]]) >>> binary_mask = extract_label_from_mask(multi_label_mask, label=2) >>> print(binary_mask) [[0 0 1] [0 1 0]]
Extract a single label from a multi-label mask.
Signature:
def extract_label_from_mask(
mask: np.ndarray,
label: int
) -> np.ndarray
Parameters:
mask (np.ndarray) - Multi-label mask
label (int) - Label value to extract
Returns:
binary_mask (np.ndarray) - Binary mask for the specified label
Example:
from medtda.utils import extract_label_from_mask
# Multi-label mask: 0=background, 1=liver, 2=tumor
liver_mask = extract_label_from_mask(multi_mask, label=1)
tumor_mask = extract_label_from_mask(multi_mask, label=2)
ROI Cropping
crop_to_roi
- medtda.utils.crop_to_roi(image, mask, padding=1, label=1)[source]
Crop image and mask to the bounding box of the Region of Interest (ROI).
This function finds the smallest bounding box containing all non-zero mask values and crops both image and mask to this region with optional padding. This reduces memory usage and speeds up persistent homology computation.
- Parameters:
image (numpy.ndarray) – Input image array.
mask (numpy.ndarray) – Mask array (same shape as image).
padding (int, default=1) – Number of background pixels to include on all sides of the ROI. This ensures the ROI is not cut too tightly.
label (int or None, default=1) – For multi-label masks, which label value to consider as foreground. If None, any non-zero value is considered foreground.
- Return type:
- Returns:
cropped_image (numpy.ndarray) – Cropped image array.
cropped_mask (numpy.ndarray) – Cropped mask array.
crop_info (dict) – Dictionary containing cropping information: - ‘bbox’: Tuple of slices defining the bounding box - ‘original_shape’: Original image shape - ‘cropped_shape’: Shape after cropping - ‘roi_size’: Number of voxels in ROI before padding - ‘reduction_factor’: Ratio of cropped size to original size
- Raises:
ValueError – If mask shape doesn’t match image shape or if mask is all zeros.
Examples
>>> import numpy as np >>> image = np.random.rand(100, 100, 100) >>> mask = np.zeros((100, 100, 100)) >>> mask[40:60, 40:60, 40:60] = 1 # Small ROI >>> cropped_img, cropped_mask, info = crop_to_roi(image, mask, padding=1) >>> print(f"Original: {image.shape}, Cropped: {cropped_img.shape}") Original: (100, 100, 100), Cropped: (22, 22, 22) >>> print(f"Reduction: {info['reduction_factor']:.2f}x smaller") Reduction: 10.65x smaller
Crop image to ROI bounding box with optional padding.
Signature:
def crop_to_roi(
image: np.ndarray,
mask: np.ndarray,
padding: int = 0
) -> Tuple[np.ndarray, np.ndarray]
Parameters:
image (np.ndarray) - Input image
mask (np.ndarray) - Binary mask defining ROI
padding (int) - Padding in pixels on all sides
Returns:
cropped_image (np.ndarray) - Cropped image
cropped_mask (np.ndarray) - Cropped mask
Example:
from medtda.utils import crop_to_roi
# Crop to ROI bounding box
cropped_img, cropped_mask = crop_to_roi(image, mask)
# Crop with 10-pixel padding
cropped_img, cropped_mask = crop_to_roi(
image,
mask,
padding=10
)
print(f"Original shape: {image.shape}")
print(f"Cropped shape: {cropped_img.shape}")
Statistics
get_image_statistics
- medtda.utils.get_image_statistics(image_array, mask=None, label=1)[source]
Compute basic statistics for image array.
- Parameters:
image_array (numpy.ndarray) – Input image array.
mask (numpy.ndarray, optional) – Mask array. If provided, statistics are computed only within the masked region.
label (int or None, default=1) – For multi-label masks, which label value to consider as foreground. If None, any non-zero value is considered foreground.
- Returns:
Dictionary containing: - ‘min’: Minimum value - ‘max’: Maximum value - ‘mean’: Mean value - ‘std’: Standard deviation - ‘median’: Median value - ‘q1’: First quartile - ‘q3’: Third quartile
- Return type:
Compute image statistics.
Signature:
def get_image_statistics(
image: np.ndarray,
mask: Optional[np.ndarray] = None
) -> dict
Parameters:
image (np.ndarray) - Input image
mask (np.ndarray or None) - Optional mask
Returns:
stats (dict) - Dictionary with statistics:
'mean','std','min','max''median','q25','q75''shape','dtype'
Example:
from medtda.utils import get_image_statistics
# Get statistics
stats = get_image_statistics(image)
print(f"Mean: {stats['mean']:.2f}")
print(f"Std: {stats['std']:.2f}")
print(f"Range: [{stats['min']:.2f}, {stats['max']:.2f}]")
# Statistics inside mask only
stats_roi = get_image_statistics(image, mask=roi_mask)
Validation
check_array_dimensions
- medtda.utils.check_array_dimensions(array, expected_dims, name='array')[source]
Check if array has expected dimensions.
- Parameters:
array (numpy.ndarray) – Array to check.
expected_dims (int or tuple of int) – Expected number of dimensions. If int, array must have exactly that many dimensions. If tuple, array must have one of those dimensions.
name (str, default='array') – Name of the array for error messages.
- Raises:
ValueError – If array dimensions don’t match expectations.
- Return type:
Validate array dimensions.
Signature:
def check_array_dimensions(
array: np.ndarray,
expected_ndim: int,
name: str = 'array'
) -> None
Raises:
ValueError if dimensions don’t match
Example:
from medtda.utils import check_array_dimensions
# Validate 3D image
check_array_dimensions(image, expected_ndim=3, name='image')
# Validate 2D mask
check_array_dimensions(mask_2d, expected_ndim=2, name='mask')
safe_divide
- medtda.utils.safe_divide(numerator, denominator)[source]
Safely divide two arrays, handling division by zero.
- Parameters:
numerator (numpy.ndarray) – Numerator array.
denominator (numpy.ndarray) – Denominator array.
- Returns:
Result of division with zeros where denominator is zero.
- Return type:
Safely divide two arrays, handling division by zero.
Signature:
def safe_divide(
numerator: np.ndarray,
denominator: np.ndarray
) -> np.ndarray
Returns:
result (np.ndarray) - Division result with zeros where denominator is zero
Example:
from medtda.utils import safe_divide
# Avoid division by zero
result = safe_divide(numerator, denominator)
Complete Examples
Example 1: Complete Preprocessing Pipeline
from medtda.utils import (
resample_image,
normalize_image,
extract_label_from_mask,
crop_to_roi
)
# Step 1: Resample to isotropic spacing
resampled = resample_image(
image,
original_spacing=(0.5, 0.5, 2.0),
target_spacing=(1.0, 1.0, 1.0)
)
# Step 2: Extract liver from multi-label mask
liver_mask = extract_label_from_mask(multi_mask, label=1)
# Step 3: Crop to liver ROI
cropped, cropped_mask = crop_to_roi(
resampled,
liver_mask,
padding=5
)
# Step 4: Normalize
normalized = normalize_image(
cropped,
method='robust',
mask=cropped_mask
)
print(f"Final shape: {normalized.shape}")
Example 2: Comparing Normalization Methods
from medtda.utils import normalize_image, get_image_statistics
methods = ['minmax', 'zscore', 'robust']
for method in methods:
normalized = normalize_image(image, method=method)
stats = get_image_statistics(normalized)
print(f"\n{method} normalization:")
print(f" Mean: {stats['mean']:.4f}")
print(f" Std: {stats['std']:.4f}")
print(f" Range: [{stats['min']:.4f}, {stats['max']:.4f}]")
Example 3: ROI-Based Processing
from medtda.utils import (
extract_label_from_mask,
crop_to_roi,
apply_mask,
get_image_statistics
)
# Extract specific organ
organ_mask = extract_label_from_mask(multi_label_mask, label=2)
# Get statistics before cropping
stats_full = get_image_statistics(image, mask=organ_mask)
# Crop to organ bounding box
cropped_img, cropped_mask = crop_to_roi(
image,
organ_mask,
padding=10
)
# Apply mask to cropped image
masked = apply_mask(
cropped_img,
cropped_mask,
background_value=0.0
)
# Get statistics after cropping
stats_cropped = get_image_statistics(masked)
print(f"Volume reduction: {image.size / masked.size:.2f}x")
Example 4: Custom Resampling Strategy
from medtda.utils import resample_image
# Original anisotropic spacing
original_spacing = (0.7, 0.7, 3.0)
# Strategy 1: Isotropic 1mm
iso_1mm = resample_image(
image,
original_spacing,
(1.0, 1.0, 1.0)
)
# Strategy 2: Keep in-plane, resample through-plane
semi_iso = resample_image(
image,
original_spacing,
(0.7, 0.7, 1.0)
)
# Strategy 3: Downsample for speed
downsampled = resample_image(
image,
original_spacing,
(2.0, 2.0, 2.0)
)
print(f"Original: {image.shape}")
print(f"Isotropic 1mm: {iso_1mm.shape}")
print(f"Semi-isotropic: {semi_iso.shape}")
print(f"Downsampled: {downsampled.shape}")
Example 5: Multi-Label Processing
from medtda.utils import extract_label_from_mask
import numpy as np
# Get all labels
unique_labels = np.unique(multi_label_mask)
print(f"Labels: {unique_labels}")
# Extract and process each label
label_results = {}
for label in unique_labels[1:]: # Skip background (0)
# Extract label
binary_mask = extract_label_from_mask(multi_label_mask, label)
# Crop to this label's ROI
cropped_img, cropped_mask = crop_to_roi(
image,
binary_mask,
padding=5
)
# Get statistics
stats = get_image_statistics(cropped_img, mask=cropped_mask)
label_results[label] = {
'shape': cropped_img.shape,
'stats': stats
}
# Print results
for label, result in label_results.items():
print(f"\nLabel {label}:")
print(f" Cropped shape: {result['shape']}")
print(f" Mean intensity: {result['stats']['mean']:.2f}")
Common Patterns
Pattern 1: Quick Normalization
from medtda.utils import normalize_image
normalized = normalize_image(image, method='minmax')
Pattern 2: Masked Normalization
from medtda.utils import normalize_image, apply_mask
# Normalize inside mask
normalized = normalize_image(image, method='robust', mask=roi_mask)
# Apply mask to result
final = apply_mask(normalized, roi_mask)
Pattern 3: Resampling and Normalization
from medtda.utils import resample_image, normalize_image
# Resample
resampled = resample_image(
image,
(0.5, 0.5, 2.0),
(1.0, 1.0, 1.0)
)
# Normalize
normalized = normalize_image(resampled, method='minmax')
Pattern 4: Label Extraction and Cropping
from medtda.utils import extract_label_from_mask, crop_to_roi
# Extract specific label
binary = extract_label_from_mask(multi_mask, label=2)
# Crop to bounding box
cropped_img, cropped_mask = crop_to_roi(image, binary, padding=10)
Performance Tips
Interpolation method:
interpolator='nearest': Fastest, use for masks/labelsinterpolator='linear': Good balance, use for most imagesinterpolator='bspline': Slower, higher quality
Crop before normalize:
Reduces computation:
# Faster cropped, _ = crop_to_roi(image, mask) normalized = normalize_image(cropped) # Slower normalized = normalize_image(image) cropped, _ = crop_to_roi(normalized, mask)
Reuse masks:
Extract labels once:
# Extract once tumor_mask = extract_label_from_mask(multi_mask, label=2) # Use multiple times cropped1, _ = crop_to_roi(image1, tumor_mask) cropped2, _ = crop_to_roi(image2, tumor_mask)
See Also
Preprocessor - High-level preprocessing class
Loaders - Image loading and validation
Preprocessing - Preprocessing guide
FeatureExtractor - Complete feature extraction