From 7db60a67abe3aa9a252f3da6bcc67be39aad8231 Mon Sep 17 00:00:00 2001 From: Vishnu Kannaujia Date: Sun, 2 Aug 2026 17:22:27 -0700 Subject: [PATCH 1/2] Warn when lazy resampling upcasts non-float input to float32 (#6713) Lazy resampling always computes in floating point (grid_sample only supports floats, and float32 is used even in the array-slicing fast path for collate robustness). As a result, integer inputs such as label maps are silently converted to float32 in a lazy Compose, whereas the same pipeline with lazy=False preserves the dtype. This surprised users (issue #6713) and can corrupt integer label data downstream with no indication. Preserving the dtype in the fast path was ruled out by maintainers for collate robustness, so this adds the warning that was agreed as the fix in the issue thread: resample() now emits a UserWarning when the input is a non-floating, non-complex dtype and will be upcast to float32. Adds regression tests in tests/transforms/functional/test_resample.py: the warning fires for uint8 input and is absent for float32 input. Co-Authored-By: Claude Opus 4.8 Signed-off-by: Vishnu Kannaujia --- monai/transforms/lazy/utils.py | 6 ++++++ tests/transforms/functional/test_resample.py | 15 +++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/monai/transforms/lazy/utils.py b/monai/transforms/lazy/utils.py index 75f1e3529d0..96034890d2b 100644 --- a/monai/transforms/lazy/utils.py +++ b/monai/transforms/lazy/utils.py @@ -192,6 +192,12 @@ def resample(data: torch.Tensor, matrix: NdarrayOrTensor, kwargs: dict | None = } ndim = len(matrix) - 1 img = convert_to_tensor(data=data, track_meta=monai.data.get_track_meta()) + if not (torch.is_floating_point(img) or torch.is_complex(img)): + warnings.warn( + f"Lazy resampling computes in floating point and converts the input of dtype {img.dtype} to " + "float32; the original data type is not preserved. For integer data such as label maps, set " + "`lazy=False` for the affected transforms (or cast back afterwards) if the data type must be preserved." + ) init_affine = monai.data.to_affine_nd(ndim, img.affine) spatial_size = kwargs.get(LazyAttr.SHAPE, None) out_spatial_size = img.peek_pending_shape() if spatial_size is None else spatial_size diff --git a/tests/transforms/functional/test_resample.py b/tests/transforms/functional/test_resample.py index 40d264598d3..be34c677587 100644 --- a/tests/transforms/functional/test_resample.py +++ b/tests/transforms/functional/test_resample.py @@ -12,6 +12,7 @@ from __future__ import annotations import unittest +import warnings import torch from parameterized import parameterized @@ -45,6 +46,20 @@ def test_resample_function_impl(self, img, matrix, expected): out_1 = resample(img, matrix, {"lazy_resample_mode": "other value", "lazy_dtype": torch.float}) self.assertIs(out.dtype, out_1.dtype) # testing dtype in different lazy_resample_mode + def test_resample_warns_on_non_float_dtype(self): + """Lazy resampling upcasts non-floating-point inputs to float32; the user should be warned (see issue #6713).""" + img = convert_to_tensor(get_arange_img((3, 3)), dtype=torch.uint8) + with self.assertWarns(Warning): + out = resample(img, torch.eye(3), {"lazy_resample_mode": "auto"}) + self.assertIs(out.dtype, torch.float32) + + def test_resample_no_warning_for_float_dtype(self): + """Floating-point inputs are not upcast, so no dtype warning should be emitted.""" + img = convert_to_tensor(get_arange_img((3, 3)), dtype=torch.float32) + with warnings.catch_warnings(): + warnings.simplefilter("error") # turn any warning into an error + resample(img, torch.eye(3), {"lazy_resample_mode": "auto"}) + if __name__ == "__main__": unittest.main() From 7f081fe4baceeb2d52e61b34595dab2985659bb4 Mon Sep 17 00:00:00 2001 From: Vishnu Kannaujia Date: Sun, 2 Aug 2026 17:32:47 -0700 Subject: [PATCH 2/2] Address review: warn with stacklevel=2, assert input unchanged, fix docstring - pass stacklevel=2 so the warning points to the caller - assert the input tensor dtype is not mutated by resample() - narrow the no-warning test docstring to float32 (float16/bfloat16 are still upcast to float32 without a warning) Co-Authored-By: Claude Opus 4.8 Signed-off-by: Vishnu Kannaujia --- monai/transforms/lazy/utils.py | 3 ++- tests/transforms/functional/test_resample.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/monai/transforms/lazy/utils.py b/monai/transforms/lazy/utils.py index 96034890d2b..0e9ff6f9716 100644 --- a/monai/transforms/lazy/utils.py +++ b/monai/transforms/lazy/utils.py @@ -196,7 +196,8 @@ def resample(data: torch.Tensor, matrix: NdarrayOrTensor, kwargs: dict | None = warnings.warn( f"Lazy resampling computes in floating point and converts the input of dtype {img.dtype} to " "float32; the original data type is not preserved. For integer data such as label maps, set " - "`lazy=False` for the affected transforms (or cast back afterwards) if the data type must be preserved." + "`lazy=False` for the affected transforms (or cast back afterwards) if the data type must be preserved.", + stacklevel=2, ) init_affine = monai.data.to_affine_nd(ndim, img.affine) spatial_size = kwargs.get(LazyAttr.SHAPE, None) diff --git a/tests/transforms/functional/test_resample.py b/tests/transforms/functional/test_resample.py index be34c677587..062a1b0b4de 100644 --- a/tests/transforms/functional/test_resample.py +++ b/tests/transforms/functional/test_resample.py @@ -52,9 +52,10 @@ def test_resample_warns_on_non_float_dtype(self): with self.assertWarns(Warning): out = resample(img, torch.eye(3), {"lazy_resample_mode": "auto"}) self.assertIs(out.dtype, torch.float32) + self.assertIs(img.dtype, torch.uint8) # the input tensor itself is not mutated def test_resample_no_warning_for_float_dtype(self): - """Floating-point inputs are not upcast, so no dtype warning should be emitted.""" + """Float32 inputs do not trigger the lazy resampling dtype warning.""" img = convert_to_tensor(get_arange_img((3, 3)), dtype=torch.float32) with warnings.catch_warnings(): warnings.simplefilter("error") # turn any warning into an error