diff --git a/monai/transforms/io/array.py b/monai/transforms/io/array.py index aadd96763d..e5d127c4b9 100644 --- a/monai/transforms/io/array.py +++ b/monai/transforms/io/array.py @@ -210,9 +210,7 @@ def __init__( try: self.register(the_reader(*args, **kwargs)) except OptionalImportError: - warnings.warn( - f"required package for reader {_r} is not installed, or the version doesn't match requirement." - ) + raise except TypeError: # the reader doesn't have the corresponding args/kwargs warnings.warn(f"{_r} is not supported with the given parameters {args} {kwargs}.") self.register(the_reader()) diff --git a/tests/data/test_init_reader.py b/tests/data/test_init_reader.py index 10365797e9..35a0b9f913 100644 --- a/tests/data/test_init_reader.py +++ b/tests/data/test_init_reader.py @@ -19,7 +19,7 @@ from monai.data import ITKReader, NibabelReader, NrrdReader, NumpyReader, PILReader, PydicomReader from monai.transforms import LoadImage, LoadImaged -from monai.utils import MetaKeys +from monai.utils import MetaKeys, OptionalImportError, optional_import from tests.test_utils import SkipIfNoModule @@ -30,9 +30,27 @@ def test_load_image(self): self.assertIsInstance(instance1, LoadImage) self.assertIsInstance(instance2, LoadImage) - for r in ["NibabelReader", "PILReader", "ITKReader", "NumpyReader", "NrrdReader", "PydicomReader", None]: - inst = LoadImaged("image", reader=r) - self.assertIsInstance(inst, LoadImaged) + optional_readers = { + "NibabelReader": "nibabel", + "PILReader": "PIL", + "ITKReader": "itk", + "NrrdReader": "nrrd", + "PydicomReader": "pydicom", + } + for r, module in optional_readers.items(): + with self.subTest(reader=r): + _, has_module = optional_import(module, allow_namespace_pkg=module in ("itk", "nrrd")) + if has_module: + inst = LoadImaged("image", reader=r) + self.assertIsInstance(inst, LoadImaged) + else: + with self.assertRaises(OptionalImportError): + LoadImaged("image", reader=r) + + inst = LoadImaged("image", reader="NumpyReader") + self.assertIsInstance(inst, LoadImaged) + inst = LoadImaged("image", reader=None) + self.assertIsInstance(inst, LoadImaged) @SkipIfNoModule("nibabel") @SkipIfNoModule("cupy") diff --git a/tests/transforms/test_load_image.py b/tests/transforms/test_load_image.py index 4a470a624c..e7ebec0f97 100644 --- a/tests/transforms/test_load_image.py +++ b/tests/transforms/test_load_image.py @@ -15,7 +15,9 @@ import shutil import tempfile import unittest +import warnings from pathlib import Path +from unittest.mock import patch import nibabel as nib import numpy as np @@ -24,11 +26,11 @@ from PIL import Image from monai.apps import download_and_extract -from monai.data import NibabelReader, PydicomReader +from monai.data import ImageReader, NibabelReader, PydicomReader from monai.data.meta_obj import get_track_meta, set_track_meta from monai.data.meta_tensor import MetaTensor from monai.transforms import LoadImage -from monai.utils import optional_import +from monai.utils import OptionalImportError, optional_import from tests.test_utils import SkipIfNoModule, assert_allclose, skip_if_downloading_fails, testing_data_config itk, has_itk = optional_import("itk", allow_namespace_pkg=True) @@ -52,6 +54,38 @@ def get_data(self, _obj): return np.zeros((1, 1, 1)), {"name": "my test"} +class _MissingDependencyReader(ImageReader): + """a test reader that simulates a missing optional dependency""" + + def __init__(self): + raise OptionalImportError("mock missing dependency") + + def verify_suffix(self, _filename): + return True + + def read(self, _data, **_kwargs): + return None + + def get_data(self, _img): + return np.zeros((1, 1)), {} + + +class _FallbackReader(ImageReader): + """a test reader that should not be used after an explicit reader import failure""" + + read_called = False + + def verify_suffix(self, _filename): + return True + + def read(self, data, **_kwargs): + type(self).read_called = True + return data + + def get_data(self, _img): + return np.zeros((1, 1)), {"name": "fallback"} + + TEST_CASE_1 = [{}, ["test_image.nii.gz"], (128, 128, 128)] TEST_CASE_2 = [{}, ["test_image.nii.gz"], (128, 128, 128)] @@ -184,6 +218,25 @@ def get_data(self, _obj): TESTS_META.append([{"reader": "ITKReader", "fallback_only": False}, (128, 128, 128), track_meta]) +class TestLoadImageReaderSelection(unittest.TestCase): + def test_explicit_string_reader_missing_dependency_raises(self): + """test explicitly requested string readers don't fall back when their dependency is missing""" + _FallbackReader.read_called = False + readers = {"missingreader": _MissingDependencyReader, "fallbackreader": _FallbackReader} + with patch("monai.transforms.io.array.SUPPORTED_READERS", readers): + loader = LoadImage() + self.assertEqual(len(loader.readers), 1) + self.assertIsInstance(loader.readers[0], _FallbackReader) + + with warnings.catch_warnings(record=True) as caught: + warnings.simplefilter("always") + with self.assertRaises(OptionalImportError): + LoadImage(reader="missingreader") + + self.assertEqual(len(caught), 0) + self.assertFalse(_FallbackReader.read_called) + + @unittest.skipUnless(has_itk, "itk not installed") class TestLoadImage(unittest.TestCase): @classmethod