Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions monai/transforms/spatial/functional.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -135,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)
Expand All @@ -156,7 +165,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)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
)
extra_info = {
"dtype": str(dtype_pt)[6:], # remove "torch": torch.float32 -> float32
Expand Down
26 changes: 22 additions & 4 deletions tests/transforms/test_spatial_resample.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []
Expand Down Expand Up @@ -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):
Expand All @@ -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):
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -230,6 +230,24 @@ def test_unchange(self):
assert_allclose(result, img, type_test=False)
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)

self.assertEqual(result.shape, img.shape)
self.assertIsInstance(result, MetaTensor)
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))

self.assertEqual(result.shape, (1, 3, 4, 5))
self.assertIsInstance(result, MetaTensor)
self.assertTrue(torch.isfinite(result).all())


if __name__ == "__main__":
unittest.main()
Loading