SimpleITK is a simplified interface to the Insight Toolkit (ITK) for image registration and segmentation
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Handling corner cases, special situations, and advanced scenarios in SimpleITK.
import SimpleITK as sitk
# Create empty image
empty = sitk.Image()
# Check if empty
if empty.GetSize() == ():
print("Image is empty")
# Initialize empty image
empty = sitk.Image(256, 256, sitk.sitkUInt8)
# All pixels initialized to 0# Single pixel image
single = sitk.Image(1, 1, sitk.sitkFloat32)
single.SetPixel(0, 0, 42.0)
# Filtering single-pixel images
# Most filters will work but may produce unexpected results
result = sitk.SmoothingRecursiveGaussian(single, sigma=[1.0, 1.0])import SimpleITK as sitk
import numpy as np
# Image with NaN or Inf values
array = np.random.randn(100, 100, 50).astype(np.float32)
array[50, 50, 25] = np.nan
array[60, 60, 30] = np.inf
image = sitk.GetImageFromArray(array)
# Check for invalid values
stats = sitk.StatisticsImageFilter()
stats.Execute(image)
if np.isnan(stats.GetMean()) or np.isinf(stats.GetMean()):
print("Image contains NaN or Inf values")
# Clean up
array = sitk.GetArrayFromImage(image)
array = np.nan_to_num(array, nan=0.0, posinf=1000.0, neginf=-1000.0)
image = sitk.GetImageFromArray(array)# Extreme value handling
image = sitk.ReadImage('extreme_values.nii')
# Clip to reasonable range
image = sitk.Clamp(image, lowerBound=-10000, upperBound=10000)
# Or use robust normalization
stats = sitk.StatisticsImageFilter()
stats.Execute(image)
# Use percentiles for robust normalization
array = sitk.GetArrayFromImage(image)
p1, p99 = np.percentile(array, [1, 99])
image_clipped = sitk.Clamp(image, lowerBound=p1, upperBound=p99)
image_normalized = sitk.RescaleIntensity(image_clipped, outputMinimum=0, outputMaximum=1000)import SimpleITK as sitk
# Image with highly anisotropic spacing
image = sitk.ReadImage('anisotropic.nii')
spacing = image.GetSpacing()
print(f"Spacing: {spacing}") # e.g., (0.5, 0.5, 5.0)
# Resample to isotropic
iso_spacing = [min(spacing)] * 3
original_size = image.GetSize()
new_size = [
int(original_size[i] * spacing[i] / iso_spacing[i])
for i in range(3)
]
isotropic = sitk.Resample(
image,
size=new_size,
transform=sitk.Transform(),
interpolator=sitk.sitkLinear,
outputOrigin=image.GetOrigin(),
outputSpacing=iso_spacing,
outputDirection=image.GetDirection()
)# Image with oblique orientation
image = sitk.ReadImage('oblique.nii')
direction = image.GetDirection()
# Check if orthogonal
is_orthogonal = all(
abs(direction[i*3 + j] - (1 if i == j else 0)) < 0.01
for i in range(3) for j in range(3)
)
if not is_orthogonal:
print("Image has oblique orientation")
# Resample to standard orientation
identity_direction = (1, 0, 0, 0, 1, 0, 0, 0, 1)
resampled = sitk.Resample(
image,
size=image.GetSize(),
transform=sitk.Transform(),
interpolator=sitk.sitkLinear,
outputOrigin=image.GetOrigin(),
outputSpacing=image.GetSpacing(),
outputDirection=identity_direction
)import SimpleITK as sitk
# Images with different sizes
image1 = sitk.ReadImage('image1.nii') # Size: (256, 256, 100)
image2 = sitk.ReadImage('image2.nii') # Size: (128, 128, 50)
# Cannot directly operate on them
# image_sum = image1 + image2 # ERROR!
# Solution: Resample to common geometry
image2_resampled = sitk.Resample(
image2,
image1, # Use image1 as reference
sitk.Transform(),
sitk.sitkLinear,
0.0,
image2.GetPixelID()
)
# Now can operate
image_sum = image1 + image2_resampled# Images with different pixel types
uint8_image = sitk.ReadImage('image1.png') # UInt8
float_image = sitk.ReadImage('image2.mha') # Float32
# Cannot directly operate
# result = uint8_image + float_image # ERROR!
# Solution: Cast to common type
uint8_as_float = sitk.Cast(uint8_image, sitk.sitkFloat32)
result = uint8_as_float + float_image
# Cast back if needed
result_uint8 = sitk.Cast(result, sitk.sitkUInt8)import SimpleITK as sitk
import numpy as np
def process_large_image_chunked(large_image_path, output_path):
"""
Process very large image in chunks to avoid memory issues.
"""
# Read image
image = sitk.ReadImage(large_image_path)
size = image.GetSize()
# Process in slabs
slab_size = 20 # Process 20 slices at a time
results = []
for z_start in range(0, size[2], slab_size):
z_end = min(z_start + slab_size, size[2])
# Extract slab
extract_size = list(size[:2]) + [z_end - z_start]
extract_index = [0, 0, z_start]
slab = sitk.Extract(image, size=extract_size, index=extract_index)
# Process slab
processed_slab = sitk.SmoothingRecursiveGaussian(
slab,
sigma=[1.0, 1.0, 1.0]
)
results.append(processed_slab)
# Join slabs back together
final = sitk.JoinSeries(*results)
final.CopyInformation(image)
sitk.WriteImage(final, output_path)
return finaldef compute_statistics_efficiently(large_image_path):
"""Compute statistics without copying large image."""
image = sitk.ReadImage(large_image_path)
# Use view for read-only operations
array_view = sitk.GetArrayViewFromImage(image)
# Compute statistics efficiently
stats = {
'mean': array_view.mean(),
'std': array_view.std(),
'min': array_view.min(),
'max': array_view.max(),
'median': np.median(array_view),
'percentiles': np.percentile(array_view, [25, 50, 75])
}
return statsimport SimpleITK as sitk
def read_specific_dicom_series(dicom_dir, series_description=None):
"""
Read specific DICOM series from directory with multiple series.
"""
# Get all series IDs
series_ids = sitk.ImageSeriesReader.GetGDCMSeriesIDs(dicom_dir)
print(f"Found {len(series_ids)} series")
# Read metadata to find desired series
for series_id in series_ids:
reader = sitk.ImageSeriesReader()
dicom_names = sitk.ImageSeriesReader.GetGDCMSeriesFileNames(
dicom_dir,
series_id
)
reader.SetFileNames(dicom_names)
# Read just metadata
reader.LoadPrivateTagsOn()
# Check series description if provided
if series_description:
# Read first file metadata
file_reader = sitk.ImageFileReader()
file_reader.SetFileName(dicom_names[0])
file_reader.ReadImageInformation()
if file_reader.HasMetaDataKey('0008|103e'):
desc = file_reader.GetMetaData('0008|103e')
if series_description.lower() in desc.lower():
print(f"Found matching series: {desc}")
return reader.Execute()
else:
# Return first series
return reader.Execute()
return Nonedef handle_missing_dicom_slices(dicom_dir):
"""Handle DICOM series with missing slices."""
reader = sitk.ImageSeriesReader()
dicom_names = sitk.ImageSeriesReader.GetGDCMSeriesFileNames(dicom_dir)
# Check for gaps in slice positions
positions = []
for filename in dicom_names:
file_reader = sitk.ImageFileReader()
file_reader.SetFileName(filename)
file_reader.ReadImageInformation()
if file_reader.HasMetaDataKey('0020|1041'): # Slice Location
pos = float(file_reader.GetMetaData('0020|1041'))
positions.append(pos)
# Check for gaps
positions_sorted = sorted(positions)
gaps = []
for i in range(len(positions_sorted) - 1):
diff = positions_sorted[i+1] - positions_sorted[i]
if diff > 1.5 * (positions_sorted[1] - positions_sorted[0]):
gaps.append(i)
if gaps:
print(f"WARNING: Found {len(gaps)} gaps in slice positions")
# Read series
reader.SetFileNames(dicom_names)
volume = reader.Execute()
return volumeimport SimpleITK as sitk
# Float to integer conversion
float_image = sitk.Image(100, 100, sitk.sitkFloat32)
# Set fractional values
float_image.SetPixel(50, 50, 42.7)
# Cast to integer (truncates)
int_image = sitk.Cast(float_image, sitk.sitkInt16)
value = int_image.GetPixel(50, 50) # 42 (not 43)
# For rounding, add 0.5 before casting
rounded = float_image + 0.5
int_rounded = sitk.Cast(rounded, sitk.sitkInt16)# Integer overflow
uint8_image = sitk.Image(100, 100, sitk.sitkUInt8)
uint8_image.SetPixel(50, 50, 200)
# Addition can overflow
result = uint8_image + 100 # 200 + 100 = 300, but UInt8 max is 255
# Solution: Cast to larger type first
float_image = sitk.Cast(uint8_image, sitk.sitkFloat32)
result = float_image + 100
result = sitk.Clamp(result, lowerBound=0, upperBound=255)
result = sitk.Cast(result, sitk.sitkUInt8)def register_different_modalities(fixed_ct, moving_pet):
"""
Register images with vastly different intensity distributions.
Use mutual information, not mean squares.
"""
# Normalize both images first
fixed_normalized = sitk.RescaleIntensity(fixed_ct, outputMinimum=0, outputMaximum=1000)
moving_normalized = sitk.RescaleIntensity(moving_pet, outputMinimum=0, outputMaximum=1000)
# Use mutual information
registration = sitk.ImageRegistrationMethod()
registration.SetMetricAsMattesMutualInformation(numberOfHistogramBins=50)
# ... rest of registration setup
return registration.Execute(fixed_normalized, moving_normalized)# Very small image (< 10 pixels per dimension)
tiny_image = sitk.Image(5, 5, sitk.sitkFloat32)
# Many filters require minimum size
# Pad to minimum size before filtering
padded = sitk.ConstantPad(
tiny_image,
padLowerBound=(10, 10),
padUpperBound=(10, 10),
constant=0
)
# Now can apply filters
smoothed = sitk.SmoothingRecursiveGaussian(padded, sigma=[1.0, 1.0])
# Crop back to original size
result = sitk.Crop(
smoothed,
lowerBoundaryCropSize=(10, 10),
upperBoundaryCropSize=(10, 10)
)def robust_region_growing(image, initial_seeds, lower, upper):
"""
Region growing with fallback if seeds are outside region.
"""
# Check if seeds are in valid range
valid_seeds = []
for seed in initial_seeds:
value = image.GetPixel(*seed)
if lower <= value <= upper:
valid_seeds.append(seed)
if not valid_seeds:
print("WARNING: No seeds in valid intensity range")
# Fallback: Use automatic threshold
return sitk.BinaryThreshold(image, lowerThreshold=lower, upperThreshold=upper)
# Proceed with region growing
return sitk.ConnectedThreshold(
image,
seedList=valid_seeds,
lower=lower,
upper=upper
)def segment_multiple_disconnected_regions(image, seed_points):
"""
Segment multiple disconnected regions.
"""
# Segment each seed separately
segments = []
for i, seed in enumerate(seed_points):
seg = sitk.ConfidenceConnected(
image,
seedList=[seed],
numberOfIterations=5,
multiplier=2.5
)
# Label this segment with unique ID
seg = sitk.Cast(seg, sitk.sitkUInt32)
seg = seg * (i + 1)
segments.append(seg)
# Combine all segments
combined = segments[0]
for seg in segments[1:]:
combined = sitk.Maximum(combined, seg)
return combinedimport SimpleITK as sitk
# Some transforms cannot be inverted
affine = sitk.AffineTransform(3)
# Singular matrix (not invertible)
singular_matrix = (
1, 0, 0,
0, 1, 0,
0, 0, 0 # Degenerate
)
affine.SetMatrix(singular_matrix)
try:
inverse = affine.GetInverse()
except RuntimeError as e:
print(f"Cannot invert: {e}")
# Handle non-invertible casedef handle_displacement_field_boundaries(image, displacement_field):
"""
Handle pixels that map outside image bounds.
"""
# Warp with explicit boundary handling
warped = sitk.Warp(
image,
displacement_field,
interpolator=sitk.sitkLinear,
edgePaddingValue=0 # Value for out-of-bounds pixels
)
return warpedimport SimpleITK as sitk
import numpy as np
def dangerous_view_pattern():
"""WRONG: View outlives image."""
def get_view():
image = sitk.ReadImage('temp.nii')
return sitk.GetArrayViewFromImage(image)
# image deleted when function returns!
view = get_view()
# view now points to deleted memory - CRASH!
# mean = view.mean() # DANGER!
def safe_view_pattern():
"""CORRECT: Keep image alive."""
image = sitk.ReadImage('temp.nii')
view = sitk.GetArrayViewFromImage(image)
# Use view while image is alive
mean = view.mean()
# If need to return, use copy
return sitk.GetArrayFromImage(image)# 4D array could be 3D vector or 4D scalar
array_4d = np.random.randn(3, 100, 100, 50).astype(np.float32)
# Explicit control
vector_image = sitk.GetImageFromArray(array_4d, isVector=True)
# Result: 3D image with 3 components per pixel
scalar_image = sitk.GetImageFromArray(array_4d, isVector=False)
# Result: 4D scalar image# Zero sigma means no smoothing
image = sitk.ReadImage('input.nii')
# This returns the input unchanged
result = sitk.SmoothingRecursiveGaussian(image, sigma=[0.0, 0.0, 0.0])
# result is identical to image# Lower > upper creates empty result
image = sitk.ReadImage('input.nii')
# This produces all zeros
empty = sitk.BinaryThreshold(
image,
lowerThreshold=200,
upperThreshold=100 # Invalid: lower > upper
)
# Check for empty result
stats = sitk.StatisticsImageFilter()
stats.Execute(empty)
if stats.GetSum() == 0:
print("WARNING: Threshold produced empty result")# All-zero image
zero_image = sitk.Image(100, 100, sitk.sitkUInt8)
# Connected component on empty image
labeled = sitk.ConnectedComponent(zero_image)
# Check result
cc_filter = sitk.ConnectedComponentImageFilter()
cc_filter.Execute(zero_image)
num_objects = cc_filter.GetObjectCount()
print(f"Objects found: {num_objects}") # 0import SimpleITK as sitk
def safe_read_image(file_path):
"""Safely read image with format validation."""
try:
image = sitk.ReadImage(file_path)
return image
except RuntimeError as e:
if "could not be created" in str(e).lower():
print(f"Unsupported format: {file_path}")
# Try specifying ImageIO explicitly
for io_name in sitk.GetRegisteredImageIOs():
try:
image = sitk.ReadImage(file_path, imageIO=io_name)
print(f"Success with {io_name}")
return image
except:
continue
raisedef validate_image_file(file_path):
"""Validate image file before full read."""
try:
# Read just metadata
reader = sitk.ImageFileReader()
reader.SetFileName(file_path)
reader.ReadImageInformation()
# Check basic properties
size = reader.GetSize()
if any(s <= 0 for s in size):
print(f"Invalid size: {size}")
return False
# Check pixel type
pixel_id = reader.GetPixelID()
if pixel_id == sitk.sitkUnknown:
print("Unknown pixel type")
return False
# Looks valid, read full image
image = reader.Execute()
return True
except RuntimeError as e:
print(f"Validation failed: {e}")
return Falseimport SimpleITK as sitk
# Avoid division by zero
denominator = sitk.ReadImage('denominator.nii')
# Add small epsilon to avoid division by zero
epsilon = 1e-10
safe_denominator = denominator + epsilon
result = numerator / safe_denominator# Log requires positive values
image = sitk.ReadImage('input.nii')
# Shift to ensure positive
min_val = sitk.MinimumMaximumImageFilter()
min_val.Execute(image)
if min_val.GetMinimum() <= 0:
# Shift to positive range
image = image - min_val.GetMinimum() + 1.0
# Now safe to take log
log_image = sitk.Log(image)Install with Tessl CLI
npx tessl i tessl/pypi-simpleitk@2.5.1