Data augmentation

Transforms wrapper


source

CustomDictTransform

 CustomDictTransform (aug)

A class that serves as a wrapper to perform an identical transformation on both the image and the target (if it’s a mask).

Vanilla transforms


source

do_pad_or_crop

 do_pad_or_crop (o, target_shape, padding_mode, mask_name, dtype=<class
                 'torch.Tensor'>)

source

PadOrCrop

 PadOrCrop (size, padding_mode=0, mask_name=None)

Resize image using TorchIO CropOrPad.


source

SpatialPad

 SpatialPad (spatial_size, mode='constant')

Pad image to minimum size without cropping using MONAI’s SpatialPad.

This transform pads each dimension to AT LEAST the specified size. Dimensions already larger than spatial_size are left unchanged (no cropping), making it ideal for patch-based training where images must be at least as large as the patch size.

Uses zero padding (constant value 0) by default.

Args: spatial_size: Minimum size [x, y, z] for each dimension. Can be int (same for all dims) or list. Usually set to patch_size for patch-based training. mode: Padding mode. Default ‘constant’ means zero padding. Other options: ‘edge’, ‘reflect’, ‘symmetric’.

Example: >>> # Image 512×512×48, need at least 256×256×96 for patches >>> transform = SpatialPad(spatial_size=[256, 256, 96]) >>> # Result: 512×512×96 (x,y unchanged, z padded from 48→96)

Note: This differs from PadOrCrop which will crop dimensions larger than the target size. Use SpatialPad when you want to preserve large dimensions and only pad small ones.


source

ZNormalization

 ZNormalization (masking_method=None, channel_wise=True)

Apply TorchIO ZNormalization.


source

RescaleIntensity

 RescaleIntensity (out_min_max:tuple[float,float],
                   in_min_max:tuple[float,float])

Apply TorchIO RescaleIntensity for robust intensity scaling.

Args: out_min_max (tuple[float, float]): Output intensity range (min, max) in_min_max (tuple[float, float]): Input intensity range (min, max)

Example for CT images: # Normalize CT from air (-1000 HU) to bone (1000 HU) into range (-1, 1) transform = RescaleIntensity(out_min_max=(-1, 1), in_min_max=(-1000, 1000))


source

NormalizeIntensity

 NormalizeIntensity (nonzero:bool=True, channel_wise:bool=True,
                     subtrahend:float=None, divisor:float=None)

Apply MONAI NormalizeIntensity.

Args: nonzero (bool): Only normalize non-zero values (default: True) channel_wise (bool): Apply normalization per channel (default: True) subtrahend (float, optional): Value to subtract
divisor (float, optional): Value to divide by


source

BraTSMaskConverter

 BraTSMaskConverter (enc=None, dec=None, split_idx=None, order=None)

Convert BraTS masks.


source

BinaryConverter

 BinaryConverter (enc=None, dec=None, split_idx=None, order=None)

Convert to binary mask.


source

RandomGhosting

 RandomGhosting (intensity=(0.5, 1), p=0.5)

Apply TorchIO RandomGhosting.


source

RandomSpike

 RandomSpike (num_spikes=1, intensity=(1, 3), p=0.5)

Apply TorchIO RandomSpike.


source

RandomNoise

 RandomNoise (mean=0, std=(0, 0.25), p=0.5)

Apply TorchIO RandomNoise.


source

RandomBiasField

 RandomBiasField (coefficients=0.5, order=3, p=0.5)

Apply TorchIO RandomBiasField.


source

RandomBlur

 RandomBlur (std=(0, 2), p=0.5)

Apply TorchIO RandomBlur.


source

RandomGamma

 RandomGamma (log_gamma=(-0.3, 0.3), p=0.5)

Apply TorchIO RandomGamma.


source

RandomIntensityScale

 RandomIntensityScale (scale_range:tuple[float,float]=(0.5, 2.0),
                       p:float=0.5)

Randomly scale image intensities by a multiplicative factor.

Useful for domain generalization across different acquisition protocols with varying intensity ranges.

Args: scale_range (tuple[float, float]): Range of scale factors (min, max). Values > 1 increase intensity, < 1 decrease intensity. p (float): Probability of applying the transform (default: 0.5)

Example: # Scale intensities randomly between 0.5x and 2.0x transform = RandomIntensityScale(scale_range=(0.5, 2.0), p=0.3)


source

RandomMotion

 RandomMotion (degrees=10, translation=10, num_transforms=2,
               image_interpolation='linear', p=0.5)

Apply TorchIO RandomMotion.


source

RandomCutout

 RandomCutout (holes=1, max_holes=3, spatial_size=8, max_spatial_size=16,
               fill='min', mask_only=True, p=0.2)

Randomly erase spherical regions in 3D medical images with mask-aware placement.

Simulates post-operative surgical cavities by filling random ellipsoid volumes with specified values. When mask_only=True (default), cutouts only affect voxels inside the segmentation mask, ensuring no healthy tissue is modified.

Args: holes: Minimum number of cutout regions. Default: 1. max_holes: Maximum number of regions. Default: 3. spatial_size: Minimum cutout diameter in voxels. Default: 8. max_spatial_size: Maximum cutout diameter. Default: 16. fill: Fill value - ‘min’, ‘mean’, ‘random’, or float. Default: ‘min’. mask_only: If True, cutouts only affect mask-positive voxels (tumor tissue). If False, cutouts can affect any voxel (original behavior). Default: True. p: Probability of applying transform. Default: 0.2.

Example: >>> # Simulate post-op cavities only within tumor regions >>> tfm = RandomCutout(holes=1, max_holes=2, spatial_size=10, … max_spatial_size=25, fill=‘min’, mask_only=True, p=0.2)

>>> # Original behavior - cutouts anywhere in the volume
>>> tfm = RandomCutout(mask_only=False, p=0.2)

Dictionary transforms


source

RandomElasticDeformation

 RandomElasticDeformation (num_control_points=7, max_displacement=7.5,
                           image_interpolation='linear', p=0.5)

Apply TorchIO RandomElasticDeformation.


source

RandomAffine

 RandomAffine (scales=0, degrees=10, translation=0, isotropic=False,
               image_interpolation='linear', default_pad_value=0.0, p=0.5)

Apply TorchIO RandomAffine.


source

RandomFlip

 RandomFlip (axes='LR', p=0.5)

Apply TorchIO RandomFlip.


source

OneOf

 OneOf (transform_dict, p=1)

Apply only one of the given transforms using TorchIO OneOf.

# Test .tio_transform property
# CustomDictTransform-based wrappers
test_eq(type(RandomAffine(degrees=10).tio_transform), tio.RandomAffine)
test_eq(type(RandomFlip(p=0.5).tio_transform), tio.RandomFlip)
test_eq(type(RandomElasticDeformation(p=0.5).tio_transform), tio.RandomElasticDeformation)

# DisplayedTransform-based wrappers
test_eq(type(PadOrCrop([64, 64, 64]).tio_transform), tio.CropOrPad)
test_eq(type(SpatialPad([64, 64, 64]).tio_transform).__name__, '_TioSpatialPad')
test_eq(type(ZNormalization().tio_transform), tio.ZNormalization)
test_eq(type(RescaleIntensity((-1, 1), (-1000, 1000)).tio_transform), tio.RescaleIntensity)
test_eq(type(RandomGamma(p=0.5).tio_transform), tio.RandomGamma)
test_eq(type(RandomNoise(p=0.5).tio_transform), tio.RandomNoise)
test_eq(type(RandomBiasField(p=0.5).tio_transform), tio.RandomBiasField)
test_eq(type(RandomBlur(p=0.5).tio_transform), tio.RandomBlur)
test_eq(type(RandomGhosting(p=0.5).tio_transform), tio.RandomGhosting)
test_eq(type(RandomSpike(p=0.5).tio_transform), tio.RandomSpike)
test_eq(type(RandomMotion(p=0.5).tio_transform), tio.RandomMotion)
# Test SpatialPad (pad-only, no cropping)
# Test 1: Pad small dimensions only (your exact scenario: 512×512×48 → 512×512×96)
test_img = MedImage(torch.randn(1, 512, 512, 48))
test_mask = MedMask(torch.zeros(1, 512, 512, 48))
test_mask[0, 200:300, 200:300, 20:30] = 1.0

transform = SpatialPad(spatial_size=[256, 256, 96])
padded_img = transform(test_img)
padded_mask = transform(test_mask)

# Verify: large dims preserved (512, 512), small dim padded (48 → 96)
test_eq(padded_img.shape, torch.Size([1, 512, 512, 96]))
test_eq(padded_mask.shape, torch.Size([1, 512, 512, 96]))
test_eq(type(padded_img), MedImage)
test_eq(type(padded_mask), MedMask)

# Test 2: All dimensions larger than min_size - no padding
test_img2 = MedImage(torch.randn(1, 300, 300, 150))
transform2 = SpatialPad(spatial_size=[256, 256, 96])
padded_img2 = transform2(test_img2)
test_eq(padded_img2.shape, torch.Size([1, 300, 300, 150]))  # Unchanged

# Test 3: All dimensions smaller - pad all
test_img3 = MedImage(torch.randn(1, 32, 32, 32))
transform3 = SpatialPad(spatial_size=[64, 64, 64])
padded_img3 = transform3(test_img3)
test_eq(padded_img3.shape, torch.Size([1, 64, 64, 64]))

# Test 4: Verify zero padding
test_img4 = MedImage(torch.ones(1, 10, 10, 10) * 5.0)  # All values = 5.0
transform4 = SpatialPad(spatial_size=[15, 15, 15])
padded_img4 = transform4(test_img4)
# Check original region still has value 5.0
test_eq(padded_img4[0, 2:12, 2:12, 2:12].mean().item(), 5.0)
# Check padded corners have value 0.0 (zero padding)
test_eq(padded_img4[0, 0, 0, 0].item(), 0.0)

# Test 5: TorchIO compatibility for patch-based workflows
tio_transform = transform.tio_transform
test_eq(isinstance(tio_transform, tio.SpatialTransform), True)

print("✓ All SpatialPad tests passed!")
# Test .tio_transform property
# CustomDictTransform-based wrappers
test_eq(type(RandomAffine(degrees=10).tio_transform), tio.RandomAffine)
test_eq(type(RandomFlip(p=0.5).tio_transform), tio.RandomFlip)
test_eq(type(RandomElasticDeformation(p=0.5).tio_transform), tio.RandomElasticDeformation)

# DisplayedTransform-based wrappers
test_eq(type(PadOrCrop([64, 64, 64]).tio_transform), tio.CropOrPad)
test_eq(type(ZNormalization().tio_transform), tio.ZNormalization)
test_eq(type(RescaleIntensity((-1, 1), (-1000, 1000)).tio_transform), tio.RescaleIntensity)
test_eq(type(RandomGamma(p=0.5).tio_transform), tio.RandomGamma)
test_eq(type(RandomNoise(p=0.5).tio_transform), tio.RandomNoise)
test_eq(type(RandomBiasField(p=0.5).tio_transform), tio.RandomBiasField)
test_eq(type(RandomBlur(p=0.5).tio_transform), tio.RandomBlur)
test_eq(type(RandomGhosting(p=0.5).tio_transform), tio.RandomGhosting)
test_eq(type(RandomSpike(p=0.5).tio_transform), tio.RandomSpike)
test_eq(type(RandomMotion(p=0.5).tio_transform), tio.RandomMotion)
# Test RandomCutout (ItemTransform - expects tuple input)
import numpy as np

# Create test data
test_img = MedImage(torch.randn(1, 32, 32, 32))
test_mask = MedMask(torch.zeros(1, 32, 32, 32))
test_mask[0, 10:20, 10:20, 10:20] = 1.0  # Tumor region

# Test mask_only=True (default): only tumor voxels affected
cutout = RandomCutout(holes=1, spatial_size=8, fill='min', mask_only=True, p=1.0)
result_img, result_mask = cutout.encodes((test_img, test_mask))
test_eq(type(result_img), MedImage)
test_eq(type(result_mask), MedMask)
test_eq(result_img.shape, test_img.shape)
# Verify: healthy tissue (mask==0) unchanged (use numpy for comparison)
healthy_region = test_mask.numpy()[0] == 0
test_eq(np.array_equal(result_img.numpy()[0, healthy_region], test_img.numpy()[0, healthy_region]), True)
# Verify: mask unchanged
test_eq(torch.equal(result_mask, test_mask), True)

# Test empty mask skips cutout (mask_only=True)
empty_mask = MedMask(torch.zeros(1, 32, 32, 32))
result_img, _ = cutout.encodes((test_img, empty_mask))
test_eq(torch.equal(result_img, test_img), True)  # Unchanged

# Test mask_only=False: cutouts can affect any voxel
cutout_any = RandomCutout(mask_only=False, p=1.0)
result_img, _ = cutout_any.encodes((test_img, test_mask))
test_eq(result_img.shape, test_img.shape)

# Test with TensorCategory target (classification task with mask_only=False)
test_label = TensorCategory(1)
cutout_cls = RandomCutout(mask_only=False, p=1.0)
result_img, result_label = cutout_cls.encodes((test_img, test_label))
test_eq(type(result_img), MedImage)
test_eq(result_label, test_label)

# Test TensorCategory with mask_only=True skips cutout (no mask available)
cutout_mask_only = RandomCutout(mask_only=True, p=1.0)
result_img, result_label = cutout_mask_only.encodes((test_img, test_label))
test_eq(torch.equal(result_img, test_img), True)  # Unchanged - no mask to intersect

# tio_transform property
test_eq(isinstance(cutout.tio_transform, tio.IntensityTransform), True)

# Test fill modes with mask_only=False
for fill_mode in ['min', 'mean', 'random', 0.0]:
    cutout = RandomCutout(fill=fill_mode, mask_only=False, p=1.0)
    result_img, _ = cutout.encodes((test_img, test_mask))
    test_eq(result_img.shape, test_img.shape)