CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-simpleitk

SimpleITK is a simplified interface to the Insight Toolkit (ITK) for image registration and segmentation

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

edge-cases.mddocs/examples/

Edge Cases and Special Scenarios

Handling corner cases, special situations, and advanced scenarios in SimpleITK.

Empty and Single-Pixel Images

Empty Images

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 Images

# 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])

Extreme Pixel Values

Handling Infinity and NaN

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)

Very Large or Small Values

# 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)

Non-Standard Image Geometries

Anisotropic Spacing

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()
)

Non-Orthogonal Directions

# 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
    )

Mismatched Image Geometries

Different Sizes

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

Different Pixel Types

# 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)

Memory Constraints

Processing Large Images

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 final

Memory-Efficient View Operations

def 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 stats

DICOM Edge Cases

Multi-Series DICOM Directories

import 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 None

Missing DICOM Slices

def 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 volume

Type Conversion Edge Cases

Loss of Precision

import 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)

Overflow Handling

# 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)

Registration Edge Cases

Images with Different Modalities

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 or Large Images

# 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)
)

Segmentation Edge Cases

No Seeds in Region

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
    )

Disconnected Regions

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 combined

Transform Edge Cases

Non-Invertible Transforms

import 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 case

Displacement Field Boundaries

def 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 warped

NumPy Integration Edge Cases

View Lifetime Issues

import 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)

Vector Image Dimension Ambiguity

# 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

Filter-Specific Edge Cases

Gaussian Smoothing with Zero Sigma

# 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

Threshold with Inverted Range

# 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")

Connected Components with No Foreground

# 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}")  # 0

File I/O Edge Cases

Unsupported Formats

import 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
        
        raise

Corrupted Files

def 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 False

Numerical Stability

Division by Zero

import 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

Logarithm of Zero or Negative

# 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)

See Also

  • Quick Start Guide - Basic usage
  • Real-World Scenarios - Complete examples
  • Performance Patterns - Optimization techniques
  • Error Handling Reference - Complete error documentation

Install with Tessl CLI

npx tessl i tessl/pypi-simpleitk@2.5.1

docs

examples

edge-cases.md

performance-patterns.md

real-world-scenarios.md

index.md

tile.json