diff --git a/monai/transforms/io/array.py b/monai/transforms/io/array.py index 0628a7fbc4..e950df6121 100644 --- a/monai/transforms/io/array.py +++ b/monai/transforms/io/array.py @@ -209,10 +209,11 @@ def __init__( the_reader = look_up_option(_r.lower(), SUPPORTED_READERS) 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." - ) + except OptionalImportError as e: + raise OptionalImportError( + f"Required package for reader {_r} is not installed, or the version doesn't match requirement. " + f"Please install the required package to use {_r}." + ) from e 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 4170412207..7728650609 100644 --- a/tests/data/test_init_reader.py +++ b/tests/data/test_init_reader.py @@ -15,6 +15,7 @@ from monai.data import ITKReader, NibabelReader, NrrdReader, NumpyReader, PILReader, PydicomReader from monai.transforms import LoadImage, LoadImaged +from monai.utils import OptionalImportError from tests.test_utils import SkipIfNoModule @@ -26,8 +27,13 @@ def test_load_image(self): self.assertIsInstance(instance2, LoadImage) for r in ["NibabelReader", "PILReader", "ITKReader", "NumpyReader", "NrrdReader", "PydicomReader", None]: - inst = LoadImaged("image", reader=r) - self.assertIsInstance(inst, LoadImaged) + try: + inst = LoadImaged("image", reader=r) + self.assertIsInstance(inst, LoadImaged) + except OptionalImportError: + # Reader's backend package is not installed — expected in + # minimal-dependency environments after the fix for #7437. + pass @SkipIfNoModule("nibabel") @SkipIfNoModule("cupy") diff --git a/tests/transforms/test_load_image.py b/tests/transforms/test_load_image.py index 031e38272e..35121283b4 100644 --- a/tests/transforms/test_load_image.py +++ b/tests/transforms/test_load_image.py @@ -498,5 +498,45 @@ def test_correct(self, input_param, expected_shape, track_meta): self.assertFalse(hasattr(r, "affine")) +class TestLoadImageReaderNotInstalled(unittest.TestCase): + """Test that specifying a reader whose required package is not installed raises an error. + + Addresses https://github.com/Project-MONAI/MONAI/issues/7437 + """ + + @unittest.skipIf(has_itk, "test requires itk to NOT be installed") + def test_specified_reader_not_installed_raises(self): + """When a user explicitly specifies a reader that is not installed, LoadImage should raise + an OptionalImportError instead of silently falling back to another reader.""" + from monai.utils import OptionalImportError + + with self.assertRaises(OptionalImportError): + LoadImage(reader="ITKReader") + + def test_specified_reader_not_installed_raises_mocked(self): + """Mock test to verify OptionalImportError is raised (not just warned) when a user-specified + reader's required package is not installed.""" + from unittest.mock import patch + + from monai.utils import OptionalImportError + + _original = __import__("monai.transforms.io.array", fromlist=["optional_import"]).optional_import + + def _mock_optional_import(module, name="", *args, **kwargs): + if name == "MockMissingReader": + # Return a class that raises OptionalImportError on instantiation, + # simulating a reader whose backend package is not installed + class _Unavailable: + def __init__(self, *a, **kw): + raise OptionalImportError("mock package is not installed") + + return _Unavailable, True + return _original(module, *args, name=name, **kwargs) + + with patch("monai.transforms.io.array.optional_import", side_effect=_mock_optional_import): + with self.assertRaises(OptionalImportError): + LoadImage(reader="MockMissingReader") + + if __name__ == "__main__": unittest.main()