From 3b23fa149ddac29e2cd76eae55ca93ed0d3951c2 Mon Sep 17 00:00:00 2001 From: Luchang Jiang Date: Sat, 29 Aug 2026 21:38:17 +0800 Subject: [PATCH 1/3] fix(transforms): guard None spatial_size in spatial_resample (#9068) Signed-off-by: Luchang Jiang --- monai/transforms/spatial/functional.py | 2 +- tests/transforms/test_spatial_resample.py | 24 +++++++++++++++++++---- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/monai/transforms/spatial/functional.py b/monai/transforms/spatial/functional.py index c44d979927e..6fdd39c22b9 100644 --- a/monai/transforms/spatial/functional.py +++ b/monai/transforms/spatial/functional.py @@ -156,7 +156,7 @@ def spatial_resample( elif spatial_size is None and spatial_rank > 1: # auto spatial size spatial_size, _ = compute_shape_offset(in_spatial_size, src_affine, dst_affine) # type: ignore spatial_size = torch.tensor( - fall_back_tuple(ensure_tuple(spatial_size)[:spatial_rank], in_spatial_size, lambda x: x >= 0) + fall_back_tuple(ensure_tuple(spatial_size)[:spatial_rank], in_spatial_size, lambda x: x is not None and x >= 0) ) extra_info = { "dtype": str(dtype_pt)[6:], # remove "torch": torch.float32 -> float32 diff --git a/tests/transforms/test_spatial_resample.py b/tests/transforms/test_spatial_resample.py index becd909048d..102a53a50d0 100644 --- a/tests/transforms/test_spatial_resample.py +++ b/tests/transforms/test_spatial_resample.py @@ -23,7 +23,7 @@ from monai.data.utils import to_affine_nd from monai.transforms import SpatialResample from monai.utils import optional_import -from tests.lazy_transforms_utils import test_resampler_lazy +from tests.lazy_transforms_utils import test_resampler_lazy as check_resampler_lazy from tests.test_utils import TEST_DEVICES, TEST_NDARRAYS_ALL, assert_allclose, dict_product TESTS = [] @@ -148,7 +148,7 @@ def test_flips(self, img, device, data_param, expected_output): assert_allclose(out, expected_output, rtol=1e-2, atol=1e-2) assert_allclose(to_affine_nd(len(out.shape) - 1, out.affine), call_param["dst_affine"]) - test_resampler_lazy(resampler, out, init_param=None, call_param=call_param) + check_resampler_lazy(resampler, out, init_param=None, call_param=call_param) @parameterized.expand(TEST_4_5_D) def test_4d_5d(self, new_shape, tile, device, dtype, expected_data): @@ -165,7 +165,7 @@ def test_4d_5d(self, new_shape, tile, device, dtype, expected_data): assert_allclose(out, expected_data[None], rtol=1e-2, atol=1e-2) assert_allclose(out.affine, dst.to(torch.float32), rtol=1e-2, atol=1e-2) - test_resampler_lazy(resampler, out, init_param, call_param) + check_resampler_lazy(resampler, out, init_param, call_param) @parameterized.expand(TEST_DEVICES) def test_ill_affine(self, device): @@ -199,7 +199,7 @@ def test_input_torch(self, new_shape, tile, device, dtype, expected_data, track_ out = resampler(**call_param) assert_allclose(out, expected_data[None], rtol=1e-2, atol=1e-2) - test_resampler_lazy(resampler, out, init_param, call_param) + check_resampler_lazy(resampler, out, init_param, call_param) if track_meta: self.assertIsInstance(out, MetaTensor) @@ -230,6 +230,22 @@ def test_unchange(self): assert_allclose(result, img, type_test=False) set_track_meta(True) + def test_none_spatial_size_rank_one(self): + img = MetaTensor(torch.randn(1, 8)) + result = SpatialResample()(img, spatial_size=None) + + self.assertEqual(result.shape, img.shape) + self.assertIsInstance(result, MetaTensor) + self.assertTrue(torch.isfinite(result).all()) + + def test_partial_none_spatial_size(self): + img = MetaTensor(torch.randn(1, 3, 6, 7)) + result = SpatialResample()(img, spatial_size=(None, 4, 5)) + + self.assertEqual(result.shape, (1, 3, 4, 5)) + self.assertIsInstance(result, MetaTensor) + self.assertTrue(torch.isfinite(result).all()) + if __name__ == "__main__": unittest.main() From fb5b7f254f4ad4696561c2fb2d232ca9f4b0289d Mon Sep 17 00:00:00 2001 From: Luchang Jiang Date: Sat, 29 Aug 2026 21:49:36 +0800 Subject: [PATCH 2/3] docs(transforms): update spatial_resample docstrings for None spatial_size (#9068) Signed-off-by: Luchang Jiang --- monai/transforms/spatial/functional.py | 4 +++- tests/transforms/test_spatial_resample.py | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/monai/transforms/spatial/functional.py b/monai/transforms/spatial/functional.py index 6fdd39c22b9..bfe85e425f4 100644 --- a/monai/transforms/spatial/functional.py +++ b/monai/transforms/spatial/functional.py @@ -117,7 +117,9 @@ def spatial_resample( Args: img: data to be resampled, assuming `img` is channel-first. dst_affine: target affine matrix, if None, use the input affine matrix, effectively no resampling. - spatial_size: output spatial size, if the component is ``-1``, use the corresponding input spatial size. + spatial_size: output spatial size. Components set to ``-1`` or ``None`` use the corresponding input + spatial dimension. If the entire value is ``None``, the output size is computed automatically when + possible, otherwise the input spatial shape is used. mode: {``"bilinear"``, ``"nearest"``} or spline interpolation order 0-5 (integers). Interpolation mode to calculate output values. See also: https://pytorch.org/docs/stable/generated/torch.nn.functional.grid_sample.html diff --git a/tests/transforms/test_spatial_resample.py b/tests/transforms/test_spatial_resample.py index 102a53a50d0..0fad4090e2d 100644 --- a/tests/transforms/test_spatial_resample.py +++ b/tests/transforms/test_spatial_resample.py @@ -231,6 +231,7 @@ def test_unchange(self): set_track_meta(True) def test_none_spatial_size_rank_one(self): + """Verify that an unspecified rank-one size preserves the input shape and returns finite values.""" img = MetaTensor(torch.randn(1, 8)) result = SpatialResample()(img, spatial_size=None) @@ -239,6 +240,7 @@ def test_none_spatial_size_rank_one(self): self.assertTrue(torch.isfinite(result).all()) def test_partial_none_spatial_size(self): + """Verify that ``None`` dimensions fall back while specified dimensions produce the requested shape.""" img = MetaTensor(torch.randn(1, 3, 6, 7)) result = SpatialResample()(img, spatial_size=(None, 4, 5)) From 92fa732d9e7133493c77141d989921fae97c93cd Mon Sep 17 00:00:00 2001 From: Luchang Jiang Date: Sat, 29 Aug 2026 22:05:09 +0800 Subject: [PATCH 3/3] docs(transforms): add Returns and Raises sections to spatial_resample docstring (#9068) Signed-off-by: Luchang Jiang --- monai/transforms/spatial/functional.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/monai/transforms/spatial/functional.py b/monai/transforms/spatial/functional.py index bfe85e425f4..68e66fd29c0 100644 --- a/monai/transforms/spatial/functional.py +++ b/monai/transforms/spatial/functional.py @@ -137,6 +137,13 @@ def spatial_resample( dtype_pt: data `dtype` for resampling computation. lazy: a flag that indicates whether the operation should be performed lazily or not transform_info: a dictionary with the relevant information pertaining to an applied transform. + + Returns: + torch.Tensor: The resampled output tensor, with metadata preserved when metadata tracking is enabled. + + Raises: + ValueError: If the affine or spatial dimensions are invalid, or if the output spatial size cannot be + computed. """ original_spatial_shape = img.peek_pending_shape() if isinstance(img, MetaTensor) else img.shape[1:] src_affine: torch.Tensor = img.peek_pending_affine() if isinstance(img, MetaTensor) else torch.eye(4)