diff --git a/monai/data/image_reader.py b/monai/data/image_reader.py index 0f8de6b57d..e5ddbf8f33 100644 --- a/monai/data/image_reader.py +++ b/monai/data/image_reader.py @@ -244,6 +244,7 @@ def read(self, data: Sequence[PathLike] | PathLike, **kwargs): series_identifier = series_uid[0] if not self.series_name else self.series_name name = names_generator.GetFileNames(series_identifier) + name = name[0] if len(name) == 1 else name # type: ignore _obj = itk.imread(name, **kwargs_) if self.series_meta: _reader = itk.ImageSeriesReader.New(FileNames=name) @@ -631,7 +632,7 @@ def _get_meta_dict(self, img) -> dict: if self.prune_metadata: prune_metadata = {} - for key in ["00200037", "00200032", "52009229", "52009230"]: + for key in ["00200037", "00200032", "00280030", "52009229", "52009230"]: if key in metadata.keys(): prune_metadata[key] = metadata[key] return prune_metadata @@ -661,7 +662,9 @@ def _get_affine(self, metadata: dict, lps_to_ras: bool = True): rx, ry, rz, cx, cy, cz = metadata["00200037"]["Value"] # "00200032" is the tag of `ImagePositionPatient` sx, sy, sz = metadata["00200032"]["Value"] - dr, dc = metadata.get("spacing", (1.0, 1.0))[:2] + # "00280030" is the tag of `PixelSpacing` + spacing = metadata["00280030"]["Value"] + dr, dc = metadata.get("spacing", spacing)[:2] affine[0, 0] = cx * dr affine[0, 1] = rx * dc affine[0, 3] = sx @@ -670,7 +673,7 @@ def _get_affine(self, metadata: dict, lps_to_ras: bool = True): affine[1, 3] = sy affine[2, 0] = cz * dr affine[2, 1] = rz * dc - affine[2, 2] = 0 + affine[2, 2] = 1.0 affine[2, 3] = sz # 3d diff --git a/tests/test_load_image.py b/tests/test_load_image.py index ec748f9951..6f29e7ac50 100644 --- a/tests/test_load_image.py +++ b/tests/test_load_image.py @@ -23,12 +23,13 @@ from parameterized import parameterized from PIL import Image +from monai.apps import download_and_extract from monai.data import NibabelReader, PydicomReader from monai.data.meta_obj import set_track_meta from monai.data.meta_tensor import MetaTensor from monai.transforms import LoadImage from monai.utils import optional_import -from tests.utils import assert_allclose +from tests.utils import assert_allclose, skip_if_downloading_fails, testing_data_config itk, has_itk = optional_import("itk", allow_namespace_pkg=True) ITKReader, _ = optional_import("monai.data", name="ITKReader", as_type="decorator") @@ -159,6 +160,25 @@ def get_data(self, _obj): @unittest.skipUnless(has_itk, "itk not installed") class TestLoadImage(unittest.TestCase): + @classmethod + def setUpClass(cls): + super(__class__, cls).setUpClass() + with skip_if_downloading_fails(): + cls.tmpdir = tempfile.mkdtemp() + key = "DICOM_single" + url = testing_data_config("images", key, "url") + hash_type = testing_data_config("images", key, "hash_type") + hash_val = testing_data_config("images", key, "hash_val") + download_and_extract( + url=url, output_dir=cls.tmpdir, hash_val=hash_val, hash_type=hash_type, file_type="zip" + ) + cls.data_dir = os.path.join(cls.tmpdir, "CT_DICOM_SINGLE") + + @classmethod + def tearDownClass(cls): + shutil.rmtree(cls.tmpdir) + super(__class__, cls).tearDownClass() + @parameterized.expand( [TEST_CASE_1, TEST_CASE_2, TEST_CASE_3, TEST_CASE_3_1, TEST_CASE_4, TEST_CASE_4_1, TEST_CASE_5] ) @@ -206,6 +226,22 @@ def test_itk_dicom_series_reader(self, input_param, filenames, expected_shape, e ) self.assertTupleEqual(result.shape, expected_np_shape) + def test_itk_dicom_series_reader_single(self): + result = LoadImage(image_only=True, reader="ITKReader")(self.data_dir) + self.assertEqual(result.meta["filename_or_obj"], f"{Path(self.data_dir)}") + assert_allclose( + result.affine, + torch.tensor( + [ + [-0.488281, 0.0, 0.0, 125.0], + [0.0, -0.488281, 0.0, 128.100006], + [0.0, 0.0, 1.0, -99.480003], + [0.0, 0.0, 0.0, 1.0], + ] + ), + ) + self.assertTupleEqual(result.shape, (16, 16, 1)) + def test_itk_reader_multichannel(self): test_image = np.random.randint(0, 256, size=(256, 224, 3)).astype("uint8") with tempfile.TemporaryDirectory() as tempdir: @@ -231,6 +267,17 @@ def test_dicom_reader_consistency(self, filenames): np.testing.assert_allclose(pydicom_result, itk_result) np.testing.assert_allclose(pydicom_result.affine, itk_result.affine) + def test_dicom_reader_consistency_single(self): + itk_param = {"reader": "ITKReader"} + pydicom_param = {"reader": "PydicomReader"} + for affine_flag in [True, False]: + itk_param["affine_lps_to_ras"] = affine_flag + pydicom_param["affine_lps_to_ras"] = affine_flag + itk_result = LoadImage(image_only=True, **itk_param)(self.data_dir) + pydicom_result = LoadImage(image_only=True, **pydicom_param)(self.data_dir) + np.testing.assert_allclose(pydicom_result, itk_result.squeeze()) + np.testing.assert_allclose(pydicom_result.affine, itk_result.affine) + def test_load_nifti_multichannel(self): test_image = np.random.randint(0, 256, size=(31, 64, 16, 2)).astype(np.float32) with tempfile.TemporaryDirectory() as tempdir: diff --git a/tests/testing_data/data_config.json b/tests/testing_data/data_config.json index abda77f7eb..4bdac6abba 100644 --- a/tests/testing_data/data_config.json +++ b/tests/testing_data/data_config.json @@ -79,6 +79,11 @@ "url": "https://github.com/Project-MONAI/MONAI-extra-test-data/releases/download/0.8.1/CT_2D_head_moving.mha", "hash_type": "sha256", "hash_val": "a37c5fe388c38b3f4ac564f456277d09d3982eda58c4da05ead8ee2332360f47" + }, + "DICOM_single": { + "url": "https://github.com/Project-MONAI/MONAI-extra-test-data/releases/download/0.8.1/CT_DICOM_SINGLE.zip", + "hash_type": "sha256", + "hash_val": "a41f6e93d2e3d68956144f9a847273041d36441da12377d6a1d5ae610e0a7023" } }, "videos": {