|
| 1 | +# Augmentation pipelines |
| 2 | + |
| 3 | +This tutorial shows how to build data augmentation pipelines with |
| 4 | +TorchIO transforms. You will learn how to apply spatial, intensity, |
| 5 | +and artifact transforms, compose them into pipelines, and use |
| 6 | +`Choice` for discrete parameter sampling. |
| 7 | + |
| 8 | +## A simple pipeline |
| 9 | + |
| 10 | +<!-- pytest-codeblocks:skip --> |
| 11 | +```python |
| 12 | +import torchio as tio |
| 13 | + |
| 14 | +augmentation = tio.Compose([ |
| 15 | + tio.Flip(axes=(0, 1, 2), flip_probability=0.5), |
| 16 | + tio.Affine(degrees=10, translation=5), |
| 17 | + tio.Noise(std=(0.01, 0.1)), |
| 18 | + tio.Gamma(log_gamma=(-0.3, 0.3)), |
| 19 | +]) |
| 20 | + |
| 21 | +subject = tio.Subject( |
| 22 | + t1=tio.ScalarImage("t1.nii.gz"), |
| 23 | + seg=tio.LabelMap("seg.nii.gz"), |
| 24 | +) |
| 25 | +augmented = augmentation(subject) |
| 26 | +``` |
| 27 | + |
| 28 | +Every call to `augmentation(subject)` produces a different result. |
| 29 | +Spatial transforms (Flip, Affine) are applied consistently to all |
| 30 | +images in the subject — the T1 and segmentation are transformed |
| 31 | +together. |
| 32 | + |
| 33 | +## Deterministic vs random |
| 34 | + |
| 35 | +The same class handles both cases. A scalar gives a fixed value; |
| 36 | +a tuple gives a uniform range: |
| 37 | + |
| 38 | +<!-- pytest-codeblocks:skip --> |
| 39 | +```python |
| 40 | +# Always rotate 90° |
| 41 | +tio.Affine(degrees=90) |
| 42 | + |
| 43 | +# Rotate uniformly between -15° and 15° |
| 44 | +tio.Affine(degrees=15) |
| 45 | + |
| 46 | +# Rotate uniformly between 5° and 20° |
| 47 | +tio.Affine(degrees=(5, 20)) |
| 48 | +``` |
| 49 | + |
| 50 | +For discrete choices, use `Choice`: |
| 51 | + |
| 52 | +<!-- pytest-codeblocks:skip --> |
| 53 | +```python |
| 54 | +# Rotate by exactly -90, 0, 90, or 180 degrees |
| 55 | +tio.Affine(degrees=tio.Choice([-90, 0, 90, 180])) |
| 56 | +``` |
| 57 | + |
| 58 | +You can mix `Choice`, ranges, and fixed values per axis: |
| 59 | + |
| 60 | +<!-- pytest-codeblocks:skip --> |
| 61 | +```python |
| 62 | +# Fixed along I, random along J, discrete along K |
| 63 | +tio.Affine(degrees=(0, (-10, 10), tio.Choice([-90, 0, 90]))) |
| 64 | +``` |
| 65 | + |
| 66 | +## Probability control |
| 67 | + |
| 68 | +Every transform has a `p` parameter (probability of being applied): |
| 69 | + |
| 70 | +<!-- pytest-codeblocks:skip --> |
| 71 | +```python |
| 72 | +# 50% chance of adding noise |
| 73 | +tio.Noise(std=0.1, p=0.5) |
| 74 | +``` |
| 75 | + |
| 76 | +## Composition strategies |
| 77 | + |
| 78 | +### Compose — apply all in sequence |
| 79 | + |
| 80 | +<!-- pytest-codeblocks:skip --> |
| 81 | +```python |
| 82 | +pipeline = tio.Compose([ |
| 83 | + tio.Affine(degrees=10), |
| 84 | + tio.Noise(std=0.05), |
| 85 | + tio.Gamma(log_gamma=0.3), |
| 86 | +]) |
| 87 | +``` |
| 88 | + |
| 89 | +### OneOf — pick one at random |
| 90 | + |
| 91 | +<!-- pytest-codeblocks:skip --> |
| 92 | +```python |
| 93 | +artifact = tio.OneOf({ |
| 94 | + tio.Ghosting(intensity=0.5): 0.4, |
| 95 | + tio.Spike(intensity=1.0): 0.3, |
| 96 | + tio.Motion(degrees=5): 0.3, |
| 97 | +}) |
| 98 | +``` |
| 99 | + |
| 100 | +### SomeOf — pick N at random |
| 101 | + |
| 102 | +<!-- pytest-codeblocks:skip --> |
| 103 | +```python |
| 104 | +augment = tio.SomeOf( |
| 105 | + [ |
| 106 | + tio.Flip(axes=(0, 1, 2)), |
| 107 | + tio.Blur(std=1.0), |
| 108 | + tio.Noise(std=0.05), |
| 109 | + tio.Gamma(log_gamma=0.3), |
| 110 | + ], |
| 111 | + num_transforms=(1, 3), # apply 1 to 3 of the 4 |
| 112 | +) |
| 113 | +``` |
| 114 | + |
| 115 | +### Operator sugar |
| 116 | + |
| 117 | +You can use `+` for Compose and `|` for OneOf: |
| 118 | + |
| 119 | +<!-- pytest-codeblocks:skip --> |
| 120 | +```python |
| 121 | +pipeline = tio.Flip(axes=(0,)) + tio.Noise(std=0.05) + tio.Gamma() |
| 122 | +artifact = tio.Ghosting() | tio.Spike() | tio.Motion() |
| 123 | +``` |
| 124 | + |
| 125 | +## Preprocessing + augmentation |
| 126 | + |
| 127 | +A common pattern separates preprocessing (applied once) from |
| 128 | +augmentation (applied each epoch): |
| 129 | + |
| 130 | +<!-- pytest-codeblocks:skip --> |
| 131 | +```python |
| 132 | +preprocessing = tio.Compose([ |
| 133 | + tio.Resample(target=1.0), # isotropic 1mm |
| 134 | + tio.CropOrPad(target_shape=128), # fixed shape |
| 135 | + tio.Normalize(out_min=-1, out_max=1), # rescale to [-1, 1] |
| 136 | +]) |
| 137 | + |
| 138 | +augmentation = tio.Compose([ |
| 139 | + tio.Flip(axes=(0, 1, 2), flip_probability=0.5), |
| 140 | + tio.Affine(degrees=15, translation=5, p=0.8), |
| 141 | + tio.OneOf({ |
| 142 | + tio.Noise(std=(0.01, 0.1)): 0.5, |
| 143 | + tio.Blur(std=(0.5, 2.0)): 0.3, |
| 144 | + tio.BiasField(coefficients=0.5): 0.2, |
| 145 | + }), |
| 146 | + tio.Gamma(log_gamma=(-0.3, 0.3), p=0.5), |
| 147 | +]) |
| 148 | + |
| 149 | +full_pipeline = preprocessing + augmentation |
| 150 | +``` |
| 151 | + |
| 152 | +## MRI artifact simulation |
| 153 | + |
| 154 | +TorchIO includes several MRI-specific artifact transforms: |
| 155 | + |
| 156 | +<!-- pytest-codeblocks:skip --> |
| 157 | +```python |
| 158 | +artifacts = tio.Compose([ |
| 159 | + tio.Motion(degrees=5, num_transforms=2, p=0.3), |
| 160 | + tio.Ghosting(num_ghosts=5, intensity=0.5, p=0.3), |
| 161 | + tio.Spike(num_spikes=1, intensity=1.5, p=0.2), |
| 162 | + tio.Anisotropy(downsampling=3, p=0.3), |
| 163 | + tio.BiasField(coefficients=0.5, p=0.3), |
| 164 | +]) |
| 165 | +``` |
| 166 | + |
| 167 | +These are useful for training models that are robust to common MRI |
| 168 | +acquisition artifacts. |
| 169 | + |
| 170 | +## Label-aware transforms |
| 171 | + |
| 172 | +Some transforms only affect label maps: |
| 173 | + |
| 174 | +<!-- pytest-codeblocks:skip --> |
| 175 | +```python |
| 176 | +label_pipeline = tio.Compose([ |
| 177 | + tio.SequentialLabels(), # renumber to 0, 1, 2, ... |
| 178 | + tio.RemoveLabels([4, 5]), # drop unwanted labels |
| 179 | + tio.KeepLargestComponent(labels=[1]), # clean up label 1 |
| 180 | +]) |
| 181 | +``` |
| 182 | + |
| 183 | +## SynthSeg-style synthesis |
| 184 | + |
| 185 | +Generate synthetic images from label maps: |
| 186 | + |
| 187 | +<!-- pytest-codeblocks:skip --> |
| 188 | +```python |
| 189 | +synth = tio.Compose([ |
| 190 | + tio.LabelsToImage(label_key="seg"), |
| 191 | + tio.Blur(std=(0.5, 1.5)), |
| 192 | + tio.BiasField(coefficients=0.5), |
| 193 | + tio.Gamma(log_gamma=(-0.3, 0.3)), |
| 194 | + tio.Noise(std=(0.01, 0.05)), |
| 195 | +]) |
| 196 | +``` |
| 197 | + |
| 198 | +## Summary |
| 199 | + |
| 200 | +| Goal | Transform(s) | |
| 201 | +|------|-------------| |
| 202 | +| Random flipping | `Flip` | |
| 203 | +| Rotation / scaling / shearing | `Affine` | |
| 204 | +| Elastic deformation | `ElasticDeformation` | |
| 205 | +| Change resolution | `Resample`, `Resize` | |
| 206 | +| Fixed shape | `CropOrPad` | |
| 207 | +| Intensity normalization | `Normalize`, `Standardize`, `HistogramStandardization` | |
| 208 | +| Gaussian noise | `Noise` | |
| 209 | +| Gaussian blur | `Blur` | |
| 210 | +| Gamma correction | `Gamma` | |
| 211 | +| Bias field | `BiasField` | |
| 212 | +| MRI motion | `Motion` | |
| 213 | +| MRI ghosting | `Ghosting` | |
| 214 | +| K-space spikes | `Spike` | |
| 215 | +| Simulate low-res axis | `Anisotropy` | |
| 216 | +| Label cleanup | `RemoveLabels`, `KeepLargestComponent`, `SequentialLabels` | |
| 217 | +| Synthetic images | `LabelsToImage` | |
| 218 | +| Self-supervised | `Swap` | |
0 commit comments