Repository: Project-MONAI/MONAI

Issue:
Add customized ImageReader support in LoadImage transform
**Is your feature request related to a problem? Please describe.**
Currently, we already added support for customized writers in SaveImage transform:
https://github.com/Project-MONAI/MONAI/blob/dev/monai/transforms/io/array.py#L344

I will add a similar logic to the LoadImage transform for customized readers.




Discussion:

Resolving patch:
diff --git a/monai/transforms/io/array.py b/monai/transforms/io/array.py
--- a/monai/transforms/io/array.py
+++ b/monai/transforms/io/array.py
@@ -105,13 +105,12 @@ def __init__(
         """
         Args:
             reader: reader to load image file and meta data
-
                 - if `reader` is None, a default set of `SUPPORTED_READERS` will be used.
-                - if `reader` is a string, the corresponding item in `SUPPORTED_READERS` will be used,
-                  and a reader instance will be constructed with the `*args` and `**kwargs` parameters.
-                  the supported reader names are: "nibabelreader", "pilreader", "itkreader", "numpyreader".
+                - if `reader` is a string, it's treated as a class name or dotted path
+                (such as ``"monai.data.ITKReader"``), the supported built-in reader classes are
+                ``"ITKReader"``, ``"NibabelReader"``, ``"NumpyReader"``.
+                a reader instance will be constructed with the `*args` and `**kwargs` parameters.
                 - if `reader` is a reader class/instance, it will be registered to this loader accordingly.
-
             image_only: if True return only the image volume, otherwise return image data array and header dict.
             dtype: if not None convert the loaded image to this data type.
             ensure_channel_first: if `True` and loaded both image array and meta data, automatically convert
@@ -153,15 +152,19 @@ def __init__(
 
         for _r in ensure_tuple(reader):
             if isinstance(_r, str):
-                the_reader = look_up_option(_r.lower(), SUPPORTED_READERS)
+                the_reader, has_built_in = optional_import("monai.data", name=f"{_r}")  # search built-in
+                if not has_built_in:
+                    the_reader = locate(f"{_r}")  # search dotted path
+                if the_reader is None:
+                    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."
+                        f"required package for reader {_r} is not installed, or the version doesn't match requirement."
                     )
                 except TypeError:  # the reader doesn't have the corresponding args/kwargs
-                    warnings.warn(f"{r} is not supported with the given parameters {args} {kwargs}.")
+                    warnings.warn(f"{_r} is not supported with the given parameters {args} {kwargs}.")
                     self.register(the_reader())
             elif inspect.isclass(_r):
                 self.register(_r(*args, **kwargs))
diff --git a/monai/transforms/io/dictionary.py b/monai/transforms/io/dictionary.py
--- a/monai/transforms/io/dictionary.py
+++ b/monai/transforms/io/dictionary.py
@@ -82,10 +82,13 @@ def __init__(
         Args:
             keys: keys of the corresponding items to be transformed.
                 See also: :py:class:`monai.transforms.compose.MapTransform`
-            reader: register reader to load image file and meta data, if None, still can register readers
-                at runtime or use the default readers. If a string of reader name provided, will construct
-                a reader object with the `*args` and `**kwargs` parameters, supported reader name: "NibabelReader",
-                "PILReader", "ITKReader", "NumpyReader".
+            reader: reader to load image file and meta data
+                - if `reader` is None, a default set of `SUPPORTED_READERS` will be used.
+                - if `reader` is a string, it's treated as a class name or dotted path
+                (such as ``"monai.data.ITKReader"``), the supported built-in reader classes are
+                ``"ITKReader"``, ``"NibabelReader"``, ``"NumpyReader"``.
+                a reader instance will be constructed with the `*args` and `**kwargs` parameters.
+                - if `reader` is a reader class/instance, it will be registered to this loader accordingly.
             dtype: if not None, convert the loaded image data to this data type.
             meta_keys: explicitly indicate the key to store the corresponding meta data dictionary.
                 the meta data is a dictionary object which contains: filename, original_shape, etc.


Test patch:
diff --git a/tests/test_load_image.py b/tests/test_load_image.py
--- a/tests/test_load_image.py
+++ b/tests/test_load_image.py
@@ -126,7 +126,7 @@ def get_data(self, _obj):
 
 TEST_CASE_16 = [{"reader": "itkreader", "channel_dim": 0}, "test_image.nii.gz", (3, 128, 128, 128)]
 
-TEST_CASE_17 = [{"reader": "ITKReader", "channel_dim": -1}, "test_image.nii.gz", (128, 128, 128, 3)]
+TEST_CASE_17 = [{"reader": "monai.data.ITKReader", "channel_dim": -1}, "test_image.nii.gz", (128, 128, 128, 3)]
 
 TEST_CASE_18 = [
     {"reader": "ITKReader", "channel_dim": 2, "ensure_channel_first": True},