diff --git a/monai/data/image_reader.py b/monai/data/image_reader.py index 27ac4d8287..53fcaa9545 100644 --- a/monai/data/image_reader.py +++ b/monai/data/image_reader.py @@ -347,6 +347,7 @@ def _get_affine(self, img, lps_to_ras: bool = True): affine: np.ndarray = np.eye(sr + 1) affine[:sr, :sr] = direction[:sr, :sr] @ np.diag(spacing[:sr]) affine[:sr, -1] = origin[:sr] + if lps_to_ras: affine = orientation_ras_lps(affine) return affine @@ -752,13 +753,25 @@ def _get_affine(self, metadata: dict, lps_to_ras: bool = True): stacklevel=2, ) return affine + + def _raise_if_not_finite(values: Sequence[Any], tag: str) -> None: + if not np.isfinite(tuple(values)).all(): + raise ValueError( + f"PydicomReader: cannot derive affine matrix because DICOM tag {tag} " + f"has a non-finite value: {values}." + ) + # "00200037" is the tag of `ImageOrientationPatient` rx, ry, rz, cx, cy, cz = metadata["00200037"]["Value"] + _raise_if_not_finite((rx, ry, rz, cx, cy, cz), "ImageOrientationPatient (0020,0037)") # "00200032" is the tag of `ImagePositionPatient` sx, sy, sz = metadata["00200032"]["Value"] + _raise_if_not_finite((sx, sy, sz), "ImagePositionPatient (0020,0032)") # "00280030" is the tag of `PixelSpacing` spacing = metadata["00280030"]["Value"] if "00280030" in metadata else (1.0, 1.0) + _raise_if_not_finite(tuple(spacing), "PixelSpacing (0028,0030)") dr, dc = metadata.get("spacing", spacing)[:2] + _raise_if_not_finite((dr, dc), "spacing") affine[0, 0] = cx * dr affine[0, 1] = rx * dc affine[0, 3] = sx @@ -773,12 +786,16 @@ def _get_affine(self, metadata: dict, lps_to_ras: bool = True): # 3d if "lastImagePositionPatient" in metadata: t1n, t2n, t3n = metadata["lastImagePositionPatient"] + _raise_if_not_finite((t1n, t2n, t3n), "lastImagePositionPatient") n = metadata[MetaKeys.SPATIAL_SHAPE][-1] if n > 1: affine[0, 2] = (t1n - sx) / (n - 1) affine[1, 2] = (t2n - sy) / (n - 1) affine[2, 2] = (t3n - sz) / (n - 1) + if not np.isfinite(affine).all(): + raise ValueError("PydicomReader: affine matrix not finite after composition.") + if lps_to_ras: affine = orientation_ras_lps(affine) return affine diff --git a/tests/data/test_pydicom_reader.py b/tests/data/test_pydicom_reader.py index 1e55ee7a4e..42fcd89185 100644 --- a/tests/data/test_pydicom_reader.py +++ b/tests/data/test_pydicom_reader.py @@ -16,6 +16,7 @@ import numpy as np from monai.data import PydicomReader +from monai.utils import MetaKeys from tests.test_utils import SkipIfNoModule @@ -39,6 +40,73 @@ def test_partial_orientation_tags_warns(self): affine = reader._get_affine(metadata) np.testing.assert_array_equal(affine, np.eye(4)) + def test_non_finite_pixel_spacing_raises(self): + reader = PydicomReader() + metadata = { + "00200037": {"Value": [1.0, 0.0, 0.0, 0.0, 1.0, 0.0]}, + "00200032": {"Value": [0.0, 0.0, 0.0]}, + "00280030": {"Value": [np.nan, 1.0]}, + } + with self.assertRaisesRegex(ValueError, "PixelSpacing"): + reader._get_affine(metadata, lps_to_ras=False) + + def test_non_finite_image_position_raises(self): + reader = PydicomReader() + metadata = { + "00200037": {"Value": [1.0, 0.0, 0.0, 0.0, 1.0, 0.0]}, + "00200032": {"Value": [np.inf, 0.0, 0.0]}, + "00280030": {"Value": [1.0, 1.0]}, + } + with self.assertRaisesRegex(ValueError, "ImagePositionPatient"): + reader._get_affine(metadata, lps_to_ras=False) + + def test_finite_values_return_affine(self): + reader = PydicomReader() + metadata = { + "00200037": {"Value": [1.0, 0.0, 0.0, 0.0, 1.0, 0.0]}, + "00200032": {"Value": [10.0, 20.0, 30.0]}, + "00280030": {"Value": [0.5, 0.25]}, + } + affine = reader._get_affine(metadata, lps_to_ras=False) + self.assertEqual(affine.shape, (4, 4)) + self.assertTrue(np.all(np.isfinite(affine))) + np.testing.assert_allclose(affine[0, 3], 10.0) + np.testing.assert_allclose(affine[1, 3], 20.0) + np.testing.assert_allclose(affine[2, 3], 30.0) + + def test_non_finite_orientation_raises(self): + reader = PydicomReader() + metadata = { + "00200037": {"Value": [np.nan, 0.0, 0.0, 0.0, 1.0, 0.0]}, + "00200032": {"Value": [0.0, 0.0, 0.0]}, + "00280030": {"Value": [1.0, 1.0]}, + } + with self.assertRaisesRegex(ValueError, "ImageOrientationPatient"): + reader._get_affine(metadata, lps_to_ras=False) + + def test_non_finite_last_image_position_raises(self): + reader = PydicomReader() + metadata = { + "00200037": {"Value": [1.0, 0.0, 0.0, 0.0, 1.0, 0.0]}, + "00200032": {"Value": [0.0, 0.0, 0.0]}, + "00280030": {"Value": [1.0, 1.0]}, + "lastImagePositionPatient": [0.0, 0.0, np.inf], + MetaKeys.SPATIAL_SHAPE: [1, 1, 2], + } + with self.assertRaisesRegex(ValueError, "lastImagePositionPatient"): + reader._get_affine(metadata, lps_to_ras=False) + + def test_overflow_from_finite_inputs_raises(self): + # Finite inputs whose product overflows produce a non-finite affine. + reader = PydicomReader() + metadata = { + "00200037": {"Value": [1e308, 0.0, 0.0, 1e308, 0.0, 0.0]}, + "00200032": {"Value": [0.0, 0.0, 0.0]}, + "00280030": {"Value": [1e308, 1e308]}, + } + with self.assertRaisesRegex(ValueError, "not finite"): + reader._get_affine(metadata, lps_to_ras=False) + if __name__ == "__main__": unittest.main()