Add partial-volume-aware label resampling mode - #1477
Conversation
Add a new opt-in `label_interpolation="label"` mode to the spatial transforms (Spatial/Resample/Affine/ElasticDeformation). For label maps, the mode one-hot encodes by unique label values (robust to non-contiguous labels), optionally antialiases the one-hot channels with the existing Cardoso et al. sigma when downsampling, resamples each channel linearly, takes the per-voxel argmax, and remaps back to the original label values. Out-of-bounds voxels use `default_pad_label`. This avoids the staircase artifacts and biased label volumes of nearest-neighbor interpolation. Defaults are unchanged: `label_interpolation` stays "nearest" everywhere and users opt in. `image_interpolation="label"` raises.
📖 Docs PreviewPreview of the documentation for this PR: 🔗 https://smokeshow.helpmanual.io/3g5j630h1v1y5e513r4t/ Built from c112917 |
There was a problem hiding this comment.
Pull request overview
Adds an opt-in label_interpolation="label" mode to TorchIO’s unified spatial transform pipeline to resample discrete label maps in a partial-volume-aware way (one-hot → linear resample → argmax), with optional antialiasing, plus tests covering core behaviors.
Changes:
- Extend interpolation parsing/support to include a new
"label"mode forLabelMapresampling. - Implement
_resample_label_partial_volume()and route label batches through it when requested. - Add
TestLabelInterpolationcoverage for validity, padding, antialias, and multi-channel label behavior.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
src/torchio/transforms/spatial/spatial.py |
Adds "label" interpolation option, validation, and the partial-volume label resampling helper integrated into the spatial batch application path. |
tests/test_spatial.py |
Adds tests validating parsing/validation and functional behavior of the new label interpolation mode. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Clarify in docstrings that the "label" mode can introduce `default_pad_label` for out-of-bounds voxels (single-channel path). - Document that multi-channel (C>1) inputs are resampled linearly, fill out-of-bounds with 0 (not `default_pad_label`), and return partial-volume floats. - Preserve floating-point output for non-floating multi-channel inputs so integer one-hot encodings are not truncated back to 0/1. - Add a test covering partial-volume preservation for integer one-hot multi-channel inputs.
- Replace the unresolved mkdocstrings shortcut reference to `_antialias_sigmas` with plain inline code. - Relax the round-trip Dice test to assert "label" is not worse than "nearest" to avoid flakiness from grid alignment / library versions.
Introduce `TypeImageInterpolation` (no "label") and `TypeLabelInterpolation` (adds "label"), and annotate `image_interpolation` parameters/attributes with the narrower type so that `image_interpolation="label"` is rejected by static type checkers, not only at runtime. `TypeInterpolation` remains as a broad alias for internal helpers.
- Clarify the comment on the label-mode out-of-bounds test: the
`sum > 0.5` threshold matches the `mask > 0.5` fill convention used for
intensity images, treating voxels sampled mostly from outside as
out-of-bounds (not only all-zero ones).
- Document that image/label interpolation also accept higher-order
B-spline modes ("quadratic".."seventh") and integer orders 0-7.
- Note that the "label" mode only guarantees no invented labels for
single-channel inputs; multi-channel maps yield partial volumes.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
src/torchio/transforms/spatial/spatial.py:89
_parse_interpolation()and the docstring state that integer interpolation orders0–7are supported, and tests already passimage_interpolation=3. However, the public type aliases/signatures useTypeImageInterpolation/TypeLabelInterpolationthat only accept string literals, so static type checkers will flag valid usage (e.g.tio.Affine(image_interpolation=3)). Consider extending the type aliases to include the allowed integer orders to keep the public typing consistent with runtime behavior and docs.
TypeImageInterpolation: TypeAlias = Literal[
"nearest",
"linear",
"quadratic",
"cubic",
`_parse_interpolation` already accepts integer B-spline orders 0-7 at runtime (and the docs/tests use them), but the public parameter annotations were string-only, so `image_interpolation=3` failed static type checking. Widen the constructor annotations to `TypeImageInterpolation | int` / `TypeLabelInterpolation | int` to match the documented and tested behavior.
|
Hi there This is a cool add on ! Just a small comment about step 3 It would be nice to add an extra parametre (let call it one_hot_label_interpolation=linear by default ) but the user could then also use other interpolation (bspline, sinc ect ...) Cheers Romain (may be a note to add in the documentation: this can be extremely memory demanding as the number of labels augments) |
Let users choose the interpolation used to resample the one-hot channels of the partial-volume "label" mode, instead of hard-coding linear. It accepts the same modes as image interpolation (nearest, linear, B-spline orders 2-7 / integer orders 0-7) but not "label". Defaults to "linear", so behavior is unchanged. Higher-order modes give smoother label boundaries, which is useful when upsampling. Also document that the "label" mode's memory scales with the number of labels, and point to GridSampler + PatchAggregator for patch-based processing of many-label maps. Threaded through Spatial/Resample/Affine/ElasticDeformation, the inverse, history replay (with a "linear" fallback for old params), and the _resample_label_partial_volume helper. Adds tests covering the default, higher-order difference, integer orders, and rejection of "label". Addresses feedback from @romainVala on #1477.
Let users choose the interpolation used to resample the one-hot channels of the partial-volume "label" mode, instead of hard-coding linear. It accepts the same modes as image interpolation (nearest, linear, B-spline orders 2-7 / integer orders 0-7) but not "label". Defaults to "linear", so behavior is unchanged. Higher-order modes give smoother label boundaries, which is useful when upsampling. Also document that the "label" mode's memory scales with the number of labels, and point to GridSampler + PatchAggregator for patch-based processing of many-label maps. Threaded through Spatial/Resample/Affine/ElasticDeformation, the inverse, history replay (with a "linear" fallback for old params), and the _resample_label_partial_volume helper. Adds tests covering the default, higher-order difference, integer orders, and rejection of "label". Addresses reviewer feedback on PR #1477.
38f8fd7 to
e7f8e0f
Compare
|
Thanks, @romainVala. Your suggestions have been addressed in e7f8e0f. |
…lation # Conflicts: # src/torchio/transforms/spatial/spatial.py
[Partially generated by a coding agent]
Summary
Adds an opt-in
label_interpolation="label"mode to the spatial transforms (Spatial,Resample,Affine,ElasticDeformation) for proper, partial-volume-aware resampling of label maps.Nearest-neighbor interpolation (the default) produces staircase artifacts, biases label volumes, and erodes small structures under strong downsampling. The new mode instead:
{0, 2, 5});antialias=True(reusing the existing Cardoso et al., MICCAI 2015 sigma);argmax, remapping back to the original label values.Out-of-bounds voxels use
default_pad_label. Multi-channel (already one-hot) label inputs are resampled linearly without re-encoding.Behavior / compatibility
label_interpolationstays"nearest"everywhere; this is purely opt-in.image_interpolation="label"raises a clearValueError(the mode is label-only).Example
Tests
New
TestLabelInterpolationintests/test_spatial.py: no invalid labels (up/downsampling), non-contiguous round-trip,default_pad_labelfill,image_interpolation="label"raises, antialias path, multi-channel input, and a round-trip Dice check showing"label"beats"nearest".uv run pytest tests/test_spatial.py tests/test_one_hot.py -q-> 99 passed.ruffandtyclean.Notes
"label"on Dice becauseargmaxcorrectly drops sub-voxel-thin structures;"label"wins clearly at moderate-to-strong factors and on small structures.copy.deepcopyof aCropOrPadoutput reverts the image to its original (pre-crop) shape, which makesResamplecompute the wrong output grid (sinceforwarddeepcopies inputs). Not addressed here; can file separately.