From b809c770678292687155013caa66cb034b7d9e38 Mon Sep 17 00:00:00 2001 From: yashnaiduu Date: Mon, 2 Mar 2026 00:06:19 +0530 Subject: [PATCH 1/5] fix: print install hint when no suitable ImageWriter found When resolve_writer() fails to find a backend for a given file extension, the error was generic. Added a format-to-package lookup that appends an install hint (e.g. pip install nibabel) for common formats. Fixes #7980 Signed-off-by: yashnaiduu --- monai/data/image_writer.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/monai/data/image_writer.py b/monai/data/image_writer.py index 5b47a5e6e5..667bf3e60c 100644 --- a/monai/data/image_writer.py +++ b/monai/data/image_writer.py @@ -116,7 +116,23 @@ def resolve_writer(ext_name, error_if_not_found=True) -> Sequence: except Exception: # other writer init errors indicating it exists avail_writers.append(_writer) if not avail_writers and error_if_not_found: - raise OptionalImportError(f"No ImageWriter backend found for {fmt}.") + # map common extensions to their required package + install_hints: dict = { + "nii": "nibabel", + "nii.gz": "nibabel", + "mha": "itk", + "mhd": "itk", + "nrrd": "itk", + "png": "pillow", + "jpg": "pillow", + "jpeg": "pillow", + "tif": "pillow", + "tiff": "pillow", + "bmp": "pillow", + } + hint = install_hints.get(fmt) + extra = f" Try installing the required package: pip install {hint}" if hint else "" + raise OptionalImportError(f"No ImageWriter backend found for '{fmt}'.{extra}") writer_tuple = ensure_tuple(avail_writers) SUPPORTED_WRITERS[fmt] = writer_tuple return writer_tuple From 6a5238f537ab1441a7de3411a2298ad3060e5006 Mon Sep 17 00:00:00 2001 From: yashnaiduu Date: Mon, 2 Mar 2026 00:11:16 +0530 Subject: [PATCH 2/5] clean up unnecessary comments Signed-off-by: yashnaiduu --- monai/data/image_writer.py | 1 - 1 file changed, 1 deletion(-) diff --git a/monai/data/image_writer.py b/monai/data/image_writer.py index 667bf3e60c..2463b99cfd 100644 --- a/monai/data/image_writer.py +++ b/monai/data/image_writer.py @@ -116,7 +116,6 @@ def resolve_writer(ext_name, error_if_not_found=True) -> Sequence: except Exception: # other writer init errors indicating it exists avail_writers.append(_writer) if not avail_writers and error_if_not_found: - # map common extensions to their required package install_hints: dict = { "nii": "nibabel", "nii.gz": "nibabel", From e35f6fd47d4613cb0121ede32a97a2a0cf844401 Mon Sep 17 00:00:00 2001 From: YASH NAIDU <152394598+yashnaiduu@users.noreply.github.com> Date: Sat, 7 Mar 2026 18:40:05 +0530 Subject: [PATCH 3/5] Refactor filetype hints for ImageWriter backendsrefactor: move install hints to module-level FILETYPE_HINT constant Signed-off-by: YASH NAIDU <152394598+yashnaiduu@users.noreply.github.com> Signed-off-by: yashnaiduu --- monai/data/image_writer.py | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/monai/data/image_writer.py b/monai/data/image_writer.py index 2463b99cfd..f575954716 100644 --- a/monai/data/image_writer.py +++ b/monai/data/image_writer.py @@ -63,6 +63,21 @@ SUPPORTED_WRITERS: dict = {} +# Maps common file extensions to the package needed by ImageWriter backends. +FILETYPE_HINT: dict[str, str] = { + "nii": "nibabel", + "nii.gz": "nibabel", + "mha": "itk", + "mhd": "itk", + "nrrd": "itk", + "png": "pillow", + "jpg": "pillow", + "jpeg": "pillow", + "tif": "pillow", + "tiff": "pillow", + "bmp": "pillow", +} + def register_writer(ext_name, *im_writers): """ @@ -116,21 +131,8 @@ def resolve_writer(ext_name, error_if_not_found=True) -> Sequence: except Exception: # other writer init errors indicating it exists avail_writers.append(_writer) if not avail_writers and error_if_not_found: - install_hints: dict = { - "nii": "nibabel", - "nii.gz": "nibabel", - "mha": "itk", - "mhd": "itk", - "nrrd": "itk", - "png": "pillow", - "jpg": "pillow", - "jpeg": "pillow", - "tif": "pillow", - "tiff": "pillow", - "bmp": "pillow", - } - hint = install_hints.get(fmt) - extra = f" Try installing the required package: pip install {hint}" if hint else "" + hint = FILETYPE_HINT.get(fmt) + extra = f" Try: pip install {hint}" if hint else "" raise OptionalImportError(f"No ImageWriter backend found for '{fmt}'.{extra}") writer_tuple = ensure_tuple(avail_writers) SUPPORTED_WRITERS[fmt] = writer_tuple From c2274ff010d03ed333df22b5905e20e04673bdce Mon Sep 17 00:00:00 2001 From: YASH NAIDU <152394598+yashnaiduu@users.noreply.github.com> Date: Sat, 7 Mar 2026 18:43:40 +0530 Subject: [PATCH 4/5] Add unit tests for image writer functionalitytest: add unit tests for FILETYPE_HINT and resolve_writer error message Added tests for file type hints and error handling in image writer. Signed-off-by: YASH NAIDU <152394598+yashnaiduu@users.noreply.github.com> Signed-off-by: yashnaiduu --- tests/data/test_image_rw.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/data/test_image_rw.py b/tests/data/test_image_rw.py index d90c1c8571..181ca51e3d 100644 --- a/tests/data/test_image_rw.py +++ b/tests/data/test_image_rw.py @@ -190,3 +190,22 @@ def test_3d(self, reader, writer): if __name__ == "__main__": unittest.main() + +class TestResolveWriterHint(unittest.TestCase): + + def test_filetype_hint_content(self): + from monai.data.image_writer import FILETYPE_HINT + self.assertEqual(FILETYPE_HINT.get("nii"), "nibabel") + self.assertEqual(FILETYPE_HINT.get("nii.gz"), "nibabel") + self.assertEqual(FILETYPE_HINT.get("png"), "pillow") + self.assertEqual(FILETYPE_HINT.get("mha"), "itk") + + def test_resolve_writer_error_message(self): + from monai.utils import OptionalImportError + # Test with an unknown extension to see the base error message + # Since EXT_WILDCARD might have backends, it might not raise unless they are missing. + # But we can at least verify the logic is reachable. + try: + resolve_writer("unknown_ext", error_if_not_found=True) + except OptionalImportError as e: + self.assertIn("No ImageWriter backend found for 'unknown_ext'", str(e)) From 5fc5be5e392ccf57f95b48d9d635f20b009c7be7 Mon Sep 17 00:00:00 2001 From: yashnaiduu Date: Sat, 7 Mar 2026 20:35:07 +0530 Subject: [PATCH 5/5] style: fix test_image_rw.py imports and ordering Signed-off-by: yashnaiduu --- tests/data/test_image_rw.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/tests/data/test_image_rw.py b/tests/data/test_image_rw.py index 181ca51e3d..67292088a3 100644 --- a/tests/data/test_image_rw.py +++ b/tests/data/test_image_rw.py @@ -22,7 +22,7 @@ from parameterized import parameterized from monai.data.image_reader import ITKReader, NibabelReader, NrrdReader, PILReader -from monai.data.image_writer import ITKWriter, NibabelWriter, PILWriter, register_writer, resolve_writer +from monai.data.image_writer import FILETYPE_HINT, ITKWriter, NibabelWriter, PILWriter, register_writer, resolve_writer from monai.data.meta_tensor import MetaTensor from monai.transforms import LoadImage, SaveImage, moveaxis from monai.utils import MetaKeys, OptionalImportError, optional_import @@ -188,20 +188,14 @@ def test_3d(self, reader, writer): self.nrrd_rw(test_data, reader, writer, np.float32) -if __name__ == "__main__": - unittest.main() - class TestResolveWriterHint(unittest.TestCase): - def test_filetype_hint_content(self): - from monai.data.image_writer import FILETYPE_HINT self.assertEqual(FILETYPE_HINT.get("nii"), "nibabel") self.assertEqual(FILETYPE_HINT.get("nii.gz"), "nibabel") self.assertEqual(FILETYPE_HINT.get("png"), "pillow") self.assertEqual(FILETYPE_HINT.get("mha"), "itk") def test_resolve_writer_error_message(self): - from monai.utils import OptionalImportError # Test with an unknown extension to see the base error message # Since EXT_WILDCARD might have backends, it might not raise unless they are missing. # But we can at least verify the logic is reachable. @@ -209,3 +203,7 @@ def test_resolve_writer_error_message(self): resolve_writer("unknown_ext", error_if_not_found=True) except OptionalImportError as e: self.assertIn("No ImageWriter backend found for 'unknown_ext'", str(e)) + + +if __name__ == "__main__": + unittest.main()