Skip to content

Commit 6bbc534

Browse files
committed
Add dependency hints to image writer errors
Signed-off-by: songhahaha66 <[email protected]>
1 parent e72145c commit 6bbc534

File tree

2 files changed

+55
-2
lines changed

2 files changed

+55
-2
lines changed

monai/data/image_writer.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
]
6363

6464
SUPPORTED_WRITERS: dict = {}
65+
WRITER_DEPENDENCY_HINTS: dict[type, tuple[str, str]] = {}
6566

6667

6768
def register_writer(ext_name, *im_writers):
@@ -106,17 +107,34 @@ def resolve_writer(ext_name, error_if_not_found=True) -> Sequence:
106107
if fmt.startswith("."):
107108
fmt = fmt[1:]
108109
avail_writers = []
110+
dependency_hints: set[tuple[str, str]] = set()
109111
default_writers = SUPPORTED_WRITERS.get(EXT_WILDCARD, ())
110112
for _writer in look_up_option(fmt, SUPPORTED_WRITERS, default=default_writers):
111113
try:
112114
_writer() # this triggers `monai.utils.module.require_pkg` to check the system availability
113115
avail_writers.append(_writer)
114116
except OptionalImportError:
117+
hint = WRITER_DEPENDENCY_HINTS.get(_writer)
118+
if hint:
119+
dependency_hints.add(hint)
115120
continue
116121
except Exception: # other writer init errors indicating it exists
117122
avail_writers.append(_writer)
118123
if not avail_writers and error_if_not_found:
119-
raise OptionalImportError(f"No ImageWriter backend found for {fmt}.")
124+
hint_msg = ""
125+
if dependency_hints:
126+
sorted_hints = sorted(dependency_hints, key=lambda item: item[0].lower())
127+
if len(sorted_hints) == 1:
128+
pkg, cmd = sorted_hints[0]
129+
hint_msg = f" Install `{pkg}` (e.g. `{cmd}`) to enable writing {fmt} images."
130+
else:
131+
pkg_names = ", ".join(f"`{pkg}`" for pkg, _ in sorted_hints)
132+
commands = ", ".join(f"`{cmd}`" for _, cmd in sorted_hints)
133+
hint_msg = (
134+
f" Install one of the supported dependencies {pkg_names} "
135+
f"(for example: {commands}) to enable writing {fmt} images."
136+
)
137+
raise OptionalImportError(f"No ImageWriter backend found for {fmt}.{hint_msg}")
120138
writer_tuple = ensure_tuple(avail_writers)
121139
SUPPORTED_WRITERS[fmt] = writer_tuple
122140
return writer_tuple
@@ -862,6 +880,15 @@ def create_backend_obj(
862880
return PILImage.fromarray(data, mode=kwargs.pop("image_mode", None))
863881

864882

883+
WRITER_DEPENDENCY_HINTS.update(
884+
{
885+
ITKWriter: ("ITK", "pip install itk"),
886+
NibabelWriter: ("Nibabel", "pip install nibabel"),
887+
PILWriter: ("Pillow", "pip install pillow"),
888+
}
889+
)
890+
891+
865892
def init():
866893
"""
867894
Initialize the image writer modules according to the filename extension.

tests/data/test_image_rw.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,12 @@
2121
import torch
2222
from parameterized import parameterized
2323

24+
import monai.data.image_writer as image_writer
2425
from monai.data.image_reader import ITKReader, NibabelReader, NrrdReader, PILReader
2526
from monai.data.image_writer import ITKWriter, NibabelWriter, PILWriter, register_writer, resolve_writer
2627
from monai.data.meta_tensor import MetaTensor
2728
from monai.transforms import LoadImage, SaveImage, moveaxis
28-
from monai.utils import MetaKeys, OptionalImportError, optional_import
29+
from monai.utils import MetaKeys, OptionalImportError, optional_import, require_pkg
2930
from tests.test_utils import TEST_NDARRAYS, assert_allclose
3031

3132
_, has_itk = optional_import("itk", allow_namespace_pkg=True)
@@ -150,6 +151,31 @@ def test_1_new(self):
150151
register_writer("new2", lambda x: x + 1)
151152
self.assertEqual(resolve_writer("new")[0](0), 1)
152153

154+
def test_missing_dependency_hint(self):
155+
ext = ".needshint"
156+
fmt_key = ext.lstrip(".").lower()
157+
previous = image_writer.SUPPORTED_WRITERS.get(fmt_key)
158+
159+
@require_pkg(pkg_name="__monai_missing_test_pkg__")
160+
class MissingHintWriter(image_writer.ImageWriter):
161+
pass
162+
163+
image_writer.WRITER_DEPENDENCY_HINTS[MissingHintWriter] = ("FakePkg", "pip install fakepkg")
164+
165+
try:
166+
register_writer(ext, MissingHintWriter)
167+
with self.assertRaises(OptionalImportError) as ctx:
168+
resolve_writer(ext)
169+
err_msg = str(ctx.exception)
170+
self.assertIn("FakePkg", err_msg)
171+
self.assertIn("pip install fakepkg", err_msg)
172+
finally:
173+
image_writer.WRITER_DEPENDENCY_HINTS.pop(MissingHintWriter, None)
174+
if previous is None:
175+
image_writer.SUPPORTED_WRITERS.pop(fmt_key, None)
176+
else:
177+
image_writer.SUPPORTED_WRITERS[fmt_key] = previous
178+
153179

154180
@unittest.skipUnless(has_itk, "itk not installed")
155181
class TestLoadSaveNrrd(unittest.TestCase):

0 commit comments

Comments
 (0)