Per-instance augmentation
When a transform runs on a batch, each element can receive its own randomly sampled augmentation. This is the default behavior and mirrors batched GPU augmentation libraries such as BatchAug and Kornia.
Why per-instance
Training augmentation works best when every sample in a mini-batch sees a different perturbation. If a whole batch shared one rotation angle or one noise level, the effective augmentation diversity per step would be much lower. Sampling parameters independently per element restores that diversity while keeping a single, vectorized call.
How it works
TorchIO converts every input into a
SubjectsBatch of 5D tensors
(B, C, I, J, K). A transform's
make_params step samples either one
parameter set (shared) or B independent sets (per-instance), and
apply_transform broadcasts or
loops over the batch dimension accordingly.
flowchart LR
A["SubjectsBatch (B, C, I, J, K)"] --> B{"per_instance and B > 1<br/>and transform opts in?"}
B -- yes --> C["sample B parameter sets"]
B -- no --> D["sample 1 parameter set"]
C --> E["apply per element"]
D --> F["apply to whole batch"]
E --> G["augmented batch"]
F --> G
Per-instance sampling activates only for genuine batches
(batch_size > 1). A single
Subject, Image, or tensor always uses the
single-sample path, so existing single-subject code is unchanged.
Controlling it
Every transform inherits a per_instance flag (default True). Set it
on the individual stochastic transform whose sampling you want to
control:
import torchio as tio
# Independent rotation per batch element (default)
augmented = tio.Affine(degrees=(0, 45))(batch)
# One rotation applied identically to the whole batch
augmented = tio.Affine(degrees=(0, 45), per_instance=False)(batch)
Per-element probability
When a transform opts into per-element probability and its probability
p is below 1, each batch element is gated independently: some
elements receive the transform and others are left unchanged.
# About half of the batch elements get noise, sampled independently
augmented = tio.Noise(std=(0.05, 0.2), p=0.5)(batch)
Shape-changing transforms (for example a resampling target), and transforms that have not opted into per-element probability, keep a single batch-wide decision: masked and unmasked elements would otherwise have incompatible shapes.
Choosing a different transform per element
OneOf and SomeOf also branch per
element: each batch element independently chooses which transform (or
subset of transforms) to apply.
# Element 0 might be flipped while element 1 is blurred
augmented = tio.OneOf([tio.Flip(axes=(0,)), tio.Blur(std=(1, 3))])(batch)
Per-element selection requires shape- and schema-preserving transforms, so the augmented elements can be re-stacked into one batch.
History and inversion
Each element keeps its own sampled parameters in the transform history.
Calling unbatch() returns subjects that
each carry only their own history, and elements gated out by per-element
probability omit that transform entirely.
Invertible transforms invert each element with its own parameters.
After a per-element OneOf/SomeOf,
the batch is inverted element by element:
Capability and roll-out
Per-instance support is advertised per transform through two properties:
supports_per_instance_params: the transform samples parameters independently per element.supports_per_instance_p: the transform gates each element independently withp.
Transforms that do not opt in (for example purely deterministic
preprocessing) keep batch-shared behavior even under the default
per_instance=True, so mixing them in a pipeline is always safe.
Stochastic realizations
Some transforms (such as Noise and BiasField) draw a full
random field spanning the batch dimension. For these,
per_instance=False shares only the sampled parameters (e.g. the
noise standard deviation); the per-voxel realization still differs
across elements.