Skip to content

Commit 2ad2512

Browse files
committed
adds tests and docs
Signed-off-by: Wenqi Li <[email protected]>
1 parent 9519fa3 commit 2ad2512

File tree

5 files changed

+74
-11
lines changed

5 files changed

+74
-11
lines changed

docs/source/data.rst

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ Generic Interfaces
7474
.. autoclass:: ImageDataset
7575
:members:
7676
:special-members: __getitem__
77-
77+
7878
`NPZDictItemDataset`
7979
~~~~~~~~~~~~~~~~~~~~
8080
.. autoclass:: NPZDictItemDataset
@@ -108,6 +108,11 @@ Patch-based dataset
108108
Image reader
109109
------------
110110

111+
ImageReader
112+
~~~~~~~~~~~
113+
.. autoclass:: ImageReader
114+
:members:
115+
111116
ITKReader
112117
~~~~~~~~~
113118
.. autoclass:: ITKReader

monai/data/image_reader.py

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,16 +45,30 @@
4545

4646

4747
class ImageReader(ABC):
48-
"""Abstract class to define interface APIs to load image files.
49-
users need to call `read` to load image and then use `get_data`
50-
to get the image data and properties from meta data.
48+
"""
49+
An abstract class defines APIs to load image files.
50+
51+
Typical usage of an implementation of this class is:
52+
53+
.. code-block:: python
54+
55+
image_reader = MyImageReader()
56+
img_obj = image_reader.read(path_to_image)
57+
img_data, meta_data = image_reader.get_data(img_obj)
58+
59+
- The `read` call converts image filenames into image objects,
60+
- The `get_data` call fetches the image data, as well as meta data.
61+
- A reader should implement `verify_suffix` with the logic of checking the input filename
62+
by the filename extensions.
5163
5264
"""
5365

5466
@abstractmethod
5567
def verify_suffix(self, filename: Union[Sequence[str], str]) -> bool:
5668
"""
57-
Verify whether the specified file or files format is supported by current reader.
69+
Verify whether the specified `filename` is supported by the current reader.
70+
This method should return True if the reader is able to read the format suggested by the
71+
`filename`.
5872
5973
Args:
6074
filename: file name or a list of file names to read.
@@ -67,7 +81,7 @@ def verify_suffix(self, filename: Union[Sequence[str], str]) -> bool:
6781
def read(self, data: Union[Sequence[str], str], **kwargs) -> Union[Sequence[Any], Any]:
6882
"""
6983
Read image data from specified file or files.
70-
Note that it returns the raw data, so different readers return different image data type.
84+
Note that it returns a data object or a sequence of data objects.
7185
7286
Args:
7387
data: file name or a list of file names to read.
@@ -80,7 +94,8 @@ def read(self, data: Union[Sequence[str], str], **kwargs) -> Union[Sequence[Any]
8094
def get_data(self, img) -> Tuple[np.ndarray, Dict]:
8195
"""
8296
Extract data array and meta data from loaded image and return them.
83-
This function must return 2 objects, first is numpy array of image data, second is dict of meta data.
97+
This function must return two objects, the first is a numpy array of image data,
98+
the second is a dictionary of meta data.
8499
85100
Args:
86101
img: an image object loaded from an image file or a list of image objects.
@@ -124,7 +139,7 @@ def _stack_images(image_list: List, meta_dict: Dict):
124139
class ITKReader(ImageReader):
125140
"""
126141
Load medical images based on ITK library.
127-
All the supported image formats can be found:
142+
All the supported image formats can be found at:
128143
https://github.com/InsightSoftwareConsortium/ITK/tree/master/Modules/IO
129144
The loaded data array will be in C order, for example, a 3D image NumPy
130145
array index order will be `CDWH`.

monai/transforms/io/array.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ def __init__(self, reader=None, image_only: bool = False, dtype: DtypeLike = np.
128128
self.register(SUPPORTED_READERS[r](*args, **kwargs))
129129
except TypeError: # the reader doesn't have the corresponding args/kwargs
130130
warnings.warn(f"{r} is not supported with the given parameters {args} {kwargs}.")
131+
self.register(SUPPORTED_READERS[r]())
131132
if reader is None:
132133
return # no user-specified reader, no need to register
133134

tests/test_load_image.py

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,24 @@
2121
from PIL import Image
2222

2323
from monai.data import ITKReader, NibabelReader
24-
from monai.transforms import LoadImage
24+
from monai.transforms import SUPPORTED_READERS, LoadImage
25+
26+
27+
class _MiniReader:
28+
"""a test case customised reader"""
29+
30+
def __init__(self, is_compatible=False):
31+
self.is_compatible = is_compatible
32+
33+
def verify_suffix(self, _name):
34+
return self.is_compatible
35+
36+
def read(self, name):
37+
return name
38+
39+
def get_data(self, _obj):
40+
return np.zeros((1, 1, 1)), {"name": "my test"}
41+
2542

2643
TEST_CASE_1 = [{"image_only": True}, ["test_image.nii.gz"], (128, 128, 128)]
2744

@@ -33,7 +50,7 @@
3350
(3, 128, 128, 128),
3451
]
3552

36-
TEST_CASE_3_1 = [
53+
TEST_CASE_3_1 = [ # .mgz format
3754
{"image_only": True, "reader": "nibabelreader"},
3855
["test_image.mgz", "test_image2.mgz", "test_image3.mgz"],
3956
(3, 128, 128, 128),
@@ -45,6 +62,12 @@
4562
(3, 128, 128, 128),
4663
]
4764

65+
TEST_CASE_4_1 = [ # additional parameter
66+
{"image_only": False, "mmap": False},
67+
["test_image.nii.gz", "test_image2.nii.gz", "test_image3.nii.gz"],
68+
(3, 128, 128, 128),
69+
]
70+
4871
TEST_CASE_5 = [
4972
{"reader": NibabelReader(mmap=False), "image_only": False},
5073
["test_image.nii.gz"],
@@ -81,7 +104,9 @@
81104

82105

83106
class TestLoadImage(unittest.TestCase):
84-
@parameterized.expand([TEST_CASE_1, TEST_CASE_2, TEST_CASE_3, TEST_CASE_3_1, TEST_CASE_4, TEST_CASE_5])
107+
@parameterized.expand(
108+
[TEST_CASE_1, TEST_CASE_2, TEST_CASE_3, TEST_CASE_3_1, TEST_CASE_4, TEST_CASE_4_1, TEST_CASE_5]
109+
)
85110
def test_nibabel_reader(self, input_param, filenames, expected_shape):
86111
test_image = np.random.rand(128, 128, 128)
87112
with tempfile.TemporaryDirectory() as tempdir:
@@ -193,6 +218,19 @@ def test_kwargs(self):
193218
np.testing.assert_allclose(header["spatial_shape"], header_raw["spatial_shape"])
194219
self.assertTupleEqual(result.shape, result_raw.shape)
195220

221+
def test_my_reader(self):
222+
"""test customised readers"""
223+
out = LoadImage(reader=_MiniReader, is_compatible=True)("test")
224+
self.assertEqual(out[1]["name"], "my test")
225+
out = LoadImage(reader=_MiniReader, is_compatible=False)("test")
226+
self.assertEqual(out[1]["name"], "my test")
227+
SUPPORTED_READERS["minireader"] = _MiniReader
228+
for item in ("minireader", _MiniReader, _MiniReader(is_compatible=False)):
229+
out = LoadImage(reader=item)("test")
230+
self.assertEqual(out[1]["name"], "my test")
231+
out = LoadImage()("test", reader=_MiniReader(is_compatible=False))
232+
self.assertEqual(out[1]["name"], "my test")
233+
196234

197235
if __name__ == "__main__":
198236
unittest.main()

tests/test_nifti_endianness.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import os
1313
import tempfile
1414
import unittest
15+
from pathlib import Path
1516
from typing import TYPE_CHECKING, List, Tuple
1617
from unittest.case import skipUnless
1718

@@ -85,6 +86,9 @@ def test_switch(self): # verify data types
8586
with self.assertRaises(NotImplementedError):
8687
switch_endianness(np.zeros((2, 1)), "=")
8788

89+
with self.assertRaises(RuntimeError):
90+
switch_endianness(Path("test"), "<")
91+
8892
@skipUnless(has_pil, "Requires PIL")
8993
def test_pil(self):
9094
tempdir = tempfile.mkdtemp()

0 commit comments

Comments
 (0)