diff --git a/monai/transforms/intensity/array.py b/monai/transforms/intensity/array.py index ec0a579c01..ff5462e25e 100644 --- a/monai/transforms/intensity/array.py +++ b/monai/transforms/intensity/array.py @@ -254,7 +254,10 @@ def __call__(self, img: NdarrayOrTensor, offset: float | None = None) -> Ndarray img = convert_to_tensor(img, track_meta=get_track_meta()) offset = self.offset if offset is None else offset - out = img + offset + # Promote integer inputs before addition when safe=True. Otherwise integer + # offsets can overflow in the input dtype before safe conversion clamps. + computation_img = img.to(torch.float64) if self.safe and not img.is_floating_point() else img + out = computation_img + offset out, *_ = convert_data_type(data=out, dtype=img.dtype, safe=self.safe) return out diff --git a/tests/transforms/test_shift_intensity.py b/tests/transforms/test_shift_intensity.py index 70a6c7bf79..4346f9adc9 100644 --- a/tests/transforms/test_shift_intensity.py +++ b/tests/transforms/test_shift_intensity.py @@ -14,12 +14,27 @@ import unittest import numpy as np +import torch from monai.transforms import ShiftIntensity from tests.test_utils import NumpyImageTestCase2D class TestShiftIntensity(NumpyImageTestCase2D): + def test_safe_integer_offset_clamps_unsigned_image(self): + image = torch.tensor([0, 100, 255], dtype=torch.uint8) + for offset, expected in ((1, [1, 101, 255]), (-1, [0, 99, 254]), (1.0, [1, 101, 255]), (-1.0, [0, 99, 254])): + with self.subTest(offset=offset): + result = ShiftIntensity(offset=offset, safe=True)(image) + torch.testing.assert_close(result, torch.tensor(expected, dtype=torch.uint8)) + + def test_safe_integer_offset_clamps_signed_image(self): + image = torch.tensor([-128, 0, 127], dtype=torch.int8) + result = ShiftIntensity(offset=1, safe=True)(image) + torch.testing.assert_close(result, torch.tensor([-127, 1, 127], dtype=torch.int8)) + result = ShiftIntensity(offset=-1, safe=True)(image) + torch.testing.assert_close(result, torch.tensor([-128, -1, 126], dtype=torch.int8)) + def test_value(self): shifter = ShiftIntensity(offset=1.0) result = shifter(self.imt)