From 732191f73e5a386ed131215d0fe11be27fa03bf5 Mon Sep 17 00:00:00 2001 From: Behrooz <3968947+drbeh@users.noreply.github.com> Date: Thu, 4 Nov 2021 18:19:12 +0000 Subject: [PATCH 1/9] Unify wsireader unittests and add new tests Signed-off-by: Behrooz <3968947+drbeh@users.noreply.github.com> --- tests/test_wsireader.py | 175 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 175 insertions(+) create mode 100644 tests/test_wsireader.py diff --git a/tests/test_wsireader.py b/tests/test_wsireader.py new file mode 100644 index 0000000000..aaf9f4836c --- /dev/null +++ b/tests/test_wsireader.py @@ -0,0 +1,175 @@ +# Copyright 2020 - 2021 MONAI Consortium +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import unittest +from unittest import skipUnless + +import numpy as np +from numpy.testing import assert_array_equal +from parameterized import parameterized + +from monai.apps.utils import download_url +from monai.data import DataLoader, Dataset +from monai.data.image_reader import WSIReader +from monai.transforms import Compose, LoadImaged, ToTensord +from monai.utils import first, optional_import + + +cucim, has_cucim = optional_import("cucim") +has_cucim = has_cucim and hasattr(cucim, "CuImage") +_, has_osl = optional_import("openslide") +imsave, has_tiff = optional_import("tifffile", name="imsave") + +FILE_URL = "https://drive.google.com/uc?id=1sGTKZlJBIz53pfqTxoTqiIQzIoEzHLAe" +FILE_PATH = os.path.join(os.path.dirname(__file__), "testing_data", "temp_" + os.path.basename(FILE_URL)) + +HEIGHT = 32914 +WIDTH = 46000 + +TEST_CASE_0 = [FILE_PATH, 2, (3, HEIGHT // 4, WIDTH // 4)] + +TEST_CASE_TRANSFORM_0 = [FILE_PATH, 4, (HEIGHT // 16, WIDTH // 16), (1, 3, HEIGHT // 16, WIDTH // 16)] + +TEST_CASE_1 = [ + FILE_PATH, + {"location": (HEIGHT // 2, WIDTH // 2), "size": (2, 1), "level": 0}, + np.array([[[246], [246]], [[246], [246]], [[246], [246]]]), +] + +TEST_CASE_2 = [ + FILE_PATH, + {"location": (0, 0), "size": (2, 1), "level": 2}, + np.array([[[239], [239]], [[239], [239]], [[239], [239]]]), +] + +TEST_CASE_3 = [ + FILE_PATH, + {"location": (0, 0), "size": (8, 8), "level": 2, "grid_shape": (2, 1), "patch_size": 2}, + np.array( + [ + [[[239, 239], [239, 239]], [[239, 239], [239, 239]], [[239, 239], [239, 239]]], + [[[242, 242], [242, 243]], [[242, 242], [242, 243]], [[242, 242], [242, 243]]], + ] + ), +] + +TEST_CASE_4 = [ + FILE_PATH, + {"location": (0, 0), "size": (8, 8), "level": 2, "grid_shape": (2, 1), "patch_size": 1}, + np.array([[[[239]], [[239]], [[239]]], [[[243]], [[243]], [[243]]]]), +] + +TEST_CASE_RGB_0 = [np.ones((3, 2, 2), dtype=np.uint8)] # CHW + +TEST_CASE_RGB_1 = [np.ones((3, 100, 100), dtype=np.uint8)] # CHW + + +def save_rgba_tiff(array: np.ndarray, filename: str, mode: str): + """ + Save numpy array into a TIFF RGB/RGBA file + + Args: + array: numpy ndarray with the shape of CxHxW and C==3 representing a RGB image + file_prefix: the filename to be used for the tiff file. '_RGB.tiff' or '_RGBA.tiff' will be appended to this filename. + mode: RGB or RGBA + """ + if mode == "RGBA": + array = np.concatenate([array, 255 * np.ones_like(array[0])[np.newaxis]]).astype(np.uint8) + + img_rgb = array.transpose(1, 2, 0) + imsave(filename, img_rgb, shape=img_rgb.shape, tile=(16, 16)) + + return filename + + +@skipUnless(has_cucim and has_osl, "Requires cucim or openslide!") +def setUpModule(): + download_url(FILE_URL, FILE_PATH, "5a3cfd4fd725c50578ddb80b517b759f") + + +class WSIReaderTests: + backend = None + + @parameterized.expand([TEST_CASE_0]) + def test_read_whole_image(self, file_path, level, expected_shape): + reader = WSIReader(self.backend, level=level) + img_obj = reader.read(file_path) + img = reader.get_data(img_obj)[0] + self.assertTupleEqual(img.shape, expected_shape) + + @parameterized.expand([TEST_CASE_1, TEST_CASE_2]) + def test_read_region(self, file_path, patch_info, expected_img): + reader = WSIReader(self.backend) + img_obj = reader.read(file_path) + img = reader.get_data(img_obj, **patch_info)[0] + self.assertTupleEqual(img.shape, expected_img.shape) + self.assertIsNone(assert_array_equal(img, expected_img)) + + @parameterized.expand([TEST_CASE_3, TEST_CASE_4]) + def test_read_patches(self, file_path, patch_info, expected_img): + reader = WSIReader(self.backend) + img_obj = reader.read(file_path) + img = reader.get_data(img_obj, **patch_info)[0] + self.assertTupleEqual(img.shape, expected_img.shape) + self.assertIsNone(assert_array_equal(img, expected_img)) + + @parameterized.expand([TEST_CASE_RGB_0, TEST_CASE_RGB_1]) + def test_read_rgba(self, img_expected): + # skip for OpenSlide since not working with images without tiles + if self.backend == "openslide": + return + image = {} + reader = WSIReader(self.backend) + for mode in ["RGB", "RGBA"]: + file_path = save_rgba_tiff( + img_expected, + os.path.join(os.path.dirname(__file__), "testing_data", f"temp_tiff_image_{mode}.tiff"), + mode=mode, + ) + img_obj = reader.read(file_path) + image[mode], _ = reader.get_data(img_obj) + + self.assertIsNone(assert_array_equal(image["RGB"], img_expected)) + self.assertIsNone(assert_array_equal(image["RGBA"], img_expected)) + + @parameterized.expand([TEST_CASE_TRANSFORM_0]) + def test_with_dataloader(self, file_path, level, expected_spatial_shape, expected_shape): + train_transform = Compose( + [ + LoadImaged(keys=["image"], reader=WSIReader, backend="cuCIM", level=level), + ToTensord(keys=["image"]), + ] + ) + dataset = Dataset([{"image": file_path}], transform=train_transform) + data_loader = DataLoader(dataset) + data: dict = first(data_loader) + spatial_shape = tuple(d.item() for d in data["image_meta_dict"]["spatial_shape"]) + self.assertTupleEqual(spatial_shape, expected_spatial_shape) + self.assertTupleEqual(data["image"].shape, expected_shape) + + +@skipUnless(has_cucim, "Requires cucim") +class TestCuCIM(unittest.TestCase, WSIReaderTests): + @classmethod + def setUpClass(cls): + cls.backend = "cucim" + + +@skipUnless(has_osl, "Requires OpenSlide") +class TestOpenSlide(unittest.TestCase, WSIReaderTests): + @classmethod + def setUpClass(cls): + cls.backend = "openslide" + + +if __name__ == "__main__": + unittest.main() From 20182f7245aac627e80b6472b0528a0a24af7877 Mon Sep 17 00:00:00 2001 From: Behrooz <3968947+drbeh@users.noreply.github.com> Date: Thu, 4 Nov 2021 14:53:45 -0400 Subject: [PATCH 2/9] Remove variant of wsireader unittests Signed-off-by: Behrooz <3968947+drbeh@users.noreply.github.com> --- tests/test_cuimage_reader.py | 127 --------------------------------- tests/test_openslide_reader.py | 95 ------------------------ 2 files changed, 222 deletions(-) delete mode 100644 tests/test_cuimage_reader.py delete mode 100644 tests/test_openslide_reader.py diff --git a/tests/test_cuimage_reader.py b/tests/test_cuimage_reader.py deleted file mode 100644 index c8391939f1..0000000000 --- a/tests/test_cuimage_reader.py +++ /dev/null @@ -1,127 +0,0 @@ -# Copyright 2020 - 2021 MONAI Consortium -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# http://www.apache.org/licenses/LICENSE-2.0 -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os -import unittest -from unittest import skipUnless - -import numpy as np -from numpy.testing import assert_array_equal -from parameterized import parameterized - -from monai.apps.utils import download_url -from monai.data.image_reader import WSIReader -from monai.utils import optional_import - -cucim, has_cucim = optional_import("cucim") -has_cucim = has_cucim and hasattr(cucim, "CuImage") -PILImage, has_pil = optional_import("PIL.Image") - -FILE_URL = "https://drive.google.com/uc?id=1sGTKZlJBIz53pfqTxoTqiIQzIoEzHLAe" -FILE_PATH = os.path.join(os.path.dirname(__file__), "testing_data", "temp_" + os.path.basename(FILE_URL)) - -HEIGHT = 32914 -WIDTH = 46000 - -TEST_CASE_0 = [FILE_PATH, (3, HEIGHT, WIDTH)] - -TEST_CASE_1 = [ - FILE_PATH, - {"location": (HEIGHT // 2, WIDTH // 2), "size": (2, 1), "level": 0}, - np.array([[[246], [246]], [[246], [246]], [[246], [246]]]), -] - -TEST_CASE_2 = [ - FILE_PATH, - {"location": (0, 0), "size": (2, 1), "level": 2}, - np.array([[[239], [239]], [[239], [239]], [[239], [239]]]), -] - -TEST_CASE_3 = [ - FILE_PATH, - {"location": (0, 0), "size": (8, 8), "level": 2, "grid_shape": (2, 1), "patch_size": 2}, - np.array( - [ - [[[239, 239], [239, 239]], [[239, 239], [239, 239]], [[239, 239], [239, 239]]], - [[[242, 242], [242, 243]], [[242, 242], [242, 243]], [[242, 242], [242, 243]]], - ] - ), -] - -TEST_CASE_4 = [ - FILE_PATH, - {"location": (0, 0), "size": (8, 8), "level": 2, "grid_shape": (2, 1), "patch_size": 1}, - np.array([[[[239]], [[239]], [[239]]], [[[243]], [[243]], [[243]]]]), -] - -TEST_CASE_RGB_0 = [np.ones((3, 2, 2), dtype=np.uint8)] # CHW - -TEST_CASE_RGB_1 = [np.ones((3, 100, 100), dtype=np.uint8)] # CHW - - -class TestCuCIMReader(unittest.TestCase): - @skipUnless(has_cucim, "Requires CuCIM") - def setUp(self): - download_url(FILE_URL, FILE_PATH, "5a3cfd4fd725c50578ddb80b517b759f") - - @parameterized.expand([TEST_CASE_0]) - def test_read_whole_image(self, file_path, expected_shape): - reader = WSIReader("cuCIM") - img_obj = reader.read(file_path) - img = reader.get_data(img_obj)[0] - self.assertTupleEqual(img.shape, expected_shape) - - @parameterized.expand([TEST_CASE_1, TEST_CASE_2]) - def test_read_region(self, file_path, patch_info, expected_img): - reader = WSIReader("cuCIM") - img_obj = reader.read(file_path) - img = reader.get_data(img_obj, **patch_info)[0] - self.assertTupleEqual(img.shape, expected_img.shape) - self.assertIsNone(assert_array_equal(img, expected_img)) - - @parameterized.expand([TEST_CASE_3, TEST_CASE_4]) - def test_read_patches(self, file_path, patch_info, expected_img): - reader = WSIReader("cuCIM") - img_obj = reader.read(file_path) - img = reader.get_data(img_obj, **patch_info)[0] - self.assertTupleEqual(img.shape, expected_img.shape) - self.assertIsNone(assert_array_equal(img, expected_img)) - - @parameterized.expand([TEST_CASE_RGB_0, TEST_CASE_RGB_1]) - @skipUnless(has_pil, "Requires PIL") - @skipUnless(has_cucim and cucim.__version__ == "0.19.0", "Skipped for cicum>0.19.0") - def test_read_rgba(self, img_expected): - image = {} - reader = WSIReader("cuCIM") - for mode in ["RGB", "RGBA"]: - file_path = self.create_rgba_image(img_expected, "temp_cu_tiff_image", mode=mode) - img_obj = reader.read(file_path) - image[mode], _ = reader.get_data(img_obj) - - self.assertIsNone(assert_array_equal(image["RGB"], img_expected)) - self.assertIsNone(assert_array_equal(image["RGBA"], img_expected)) - - def create_rgba_image(self, array: np.ndarray, filename_prefix: str, mode: str): - file_path = os.path.join(os.path.dirname(__file__), "testing_data", f"{filename_prefix}_{mode}.tiff") - - if mode == "RGBA": - array = np.concatenate([array, 255 * np.ones_like(array[0])[np.newaxis]]).astype(np.uint8) - - img_rgb = array.transpose(1, 2, 0) - - image = PILImage.fromarray(img_rgb, mode=mode) - image.save(file_path) - - return file_path - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_openslide_reader.py b/tests/test_openslide_reader.py deleted file mode 100644 index cf092158f2..0000000000 --- a/tests/test_openslide_reader.py +++ /dev/null @@ -1,95 +0,0 @@ -# Copyright 2020 - 2021 MONAI Consortium -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# http://www.apache.org/licenses/LICENSE-2.0 -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os -import unittest -from unittest import skipUnless - -import numpy as np -from numpy.testing import assert_array_equal -from parameterized import parameterized - -from monai.apps.utils import download_url -from monai.data.image_reader import WSIReader -from monai.utils import optional_import - -_, has_osl = optional_import("openslide") - - -FILE_URL = "https://drive.google.com/uc?id=1sGTKZlJBIz53pfqTxoTqiIQzIoEzHLAe" -FILE_PATH = os.path.join(os.path.dirname(__file__), "testing_data", "temp_" + os.path.basename(FILE_URL)) - -HEIGHT = 32914 -WIDTH = 46000 - -TEST_CASE_0 = [FILE_PATH, (3, HEIGHT, WIDTH)] - -TEST_CASE_1 = [ - FILE_PATH, - {"location": (HEIGHT // 2, WIDTH // 2), "size": (2, 1), "level": 0}, - np.array([[[246], [246]], [[246], [246]], [[246], [246]]]), -] - -TEST_CASE_2 = [ - FILE_PATH, - {"location": (0, 0), "size": (2, 1), "level": 2}, - np.array([[[239], [239]], [[239], [239]], [[239], [239]]]), -] - -TEST_CASE_3 = [ - FILE_PATH, - {"location": (0, 0), "size": (8, 8), "level": 2, "grid_shape": (2, 1), "patch_size": 2}, - np.array( - [ - [[[239, 239], [239, 239]], [[239, 239], [239, 239]], [[239, 239], [239, 239]]], - [[[242, 242], [242, 243]], [[242, 242], [242, 243]], [[242, 242], [242, 243]]], - ] - ), -] - -TEST_CASE_4 = [ - FILE_PATH, - {"location": (0, 0), "size": (8, 8), "level": 2, "grid_shape": (2, 1), "patch_size": 1}, - np.array([[[[239]], [[239]], [[239]]], [[[243]], [[243]], [[243]]]]), -] - - -class TestOpenSlideReader(unittest.TestCase): - @skipUnless(has_osl, "Requires OpenSlide") - def setUp(self): - download_url(FILE_URL, FILE_PATH, "5a3cfd4fd725c50578ddb80b517b759f") - - @parameterized.expand([TEST_CASE_0]) - def test_read_whole_image(self, file_path, expected_shape): - reader = WSIReader("OpenSlide") - img_obj = reader.read(file_path) - img = reader.get_data(img_obj)[0] - self.assertTupleEqual(img.shape, expected_shape) - - @parameterized.expand([TEST_CASE_1, TEST_CASE_2]) - def test_read_region(self, file_path, patch_info, expected_img): - reader = WSIReader("OpenSlide") - img_obj = reader.read(file_path) - img = reader.get_data(img_obj, **patch_info)[0] - self.assertTupleEqual(img.shape, expected_img.shape) - self.assertIsNone(assert_array_equal(img, expected_img)) - - @parameterized.expand([TEST_CASE_3, TEST_CASE_4]) - def test_read_patches(self, file_path, patch_info, expected_img): - reader = WSIReader("OpenSlide") - img_obj = reader.read(file_path) - img = reader.get_data(img_obj, **patch_info)[0] - self.assertTupleEqual(img.shape, expected_img.shape) - self.assertIsNone(assert_array_equal(img, expected_img)) - - -if __name__ == "__main__": - unittest.main() From c82da2369961c87f709a83d2e165c8625d3d57aa Mon Sep 17 00:00:00 2001 From: Behrooz <3968947+drbeh@users.noreply.github.com> Date: Thu, 4 Nov 2021 19:02:46 +0000 Subject: [PATCH 3/9] Fix import Signed-off-by: Behrooz <3968947+drbeh@users.noreply.github.com> --- tests/test_wsireader.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_wsireader.py b/tests/test_wsireader.py index aaf9f4836c..b78af4b7cd 100644 --- a/tests/test_wsireader.py +++ b/tests/test_wsireader.py @@ -23,7 +23,6 @@ from monai.transforms import Compose, LoadImaged, ToTensord from monai.utils import first, optional_import - cucim, has_cucim = optional_import("cucim") has_cucim = has_cucim and hasattr(cucim, "CuImage") _, has_osl = optional_import("openslide") From 762701c1839239560f5726cdc407579d8e36bac8 Mon Sep 17 00:00:00 2001 From: Behrooz <3968947+drbeh@users.noreply.github.com> Date: Thu, 4 Nov 2021 19:05:13 +0000 Subject: [PATCH 4/9] Change the logic to skip Signed-off-by: Behrooz <3968947+drbeh@users.noreply.github.com> --- tests/test_wsireader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_wsireader.py b/tests/test_wsireader.py index b78af4b7cd..e1b1b4ebe6 100644 --- a/tests/test_wsireader.py +++ b/tests/test_wsireader.py @@ -90,7 +90,7 @@ def save_rgba_tiff(array: np.ndarray, filename: str, mode: str): return filename -@skipUnless(has_cucim and has_osl, "Requires cucim or openslide!") +@skipUnless(has_cucim or has_osl, "Requires cucim or openslide!") def setUpModule(): download_url(FILE_URL, FILE_PATH, "5a3cfd4fd725c50578ddb80b517b759f") From aa463995f2145729230a51e2362b684edd1ee9a2 Mon Sep 17 00:00:00 2001 From: Behrooz <3968947+drbeh@users.noreply.github.com> Date: Thu, 4 Nov 2021 19:25:32 +0000 Subject: [PATCH 5/9] Ignore N802 for setUpModule Signed-off-by: Behrooz <3968947+drbeh@users.noreply.github.com> --- tests/test_wsireader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_wsireader.py b/tests/test_wsireader.py index e1b1b4ebe6..c4976196e8 100644 --- a/tests/test_wsireader.py +++ b/tests/test_wsireader.py @@ -91,7 +91,7 @@ def save_rgba_tiff(array: np.ndarray, filename: str, mode: str): @skipUnless(has_cucim or has_osl, "Requires cucim or openslide!") -def setUpModule(): +def setUpModule(): # noqa: N802 download_url(FILE_URL, FILE_PATH, "5a3cfd4fd725c50578ddb80b517b759f") From 0d1759aaccf1d10ad869577f2c59c3cfa5673e5e Mon Sep 17 00:00:00 2001 From: Behrooz <3968947+drbeh@users.noreply.github.com> Date: Thu, 4 Nov 2021 22:02:03 +0000 Subject: [PATCH 6/9] Skip if no tifffile Signed-off-by: Behrooz <3968947+drbeh@users.noreply.github.com> --- tests/test_wsireader.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_wsireader.py b/tests/test_wsireader.py index c4976196e8..afd5921fc0 100644 --- a/tests/test_wsireader.py +++ b/tests/test_wsireader.py @@ -122,6 +122,7 @@ def test_read_patches(self, file_path, patch_info, expected_img): self.assertIsNone(assert_array_equal(img, expected_img)) @parameterized.expand([TEST_CASE_RGB_0, TEST_CASE_RGB_1]) + @skipUnless(has_tiff, "Requires tifffile.") def test_read_rgba(self, img_expected): # skip for OpenSlide since not working with images without tiles if self.backend == "openslide": From 28f64f1ae9b1901891b7e36e29a2524fc70a4c82 Mon Sep 17 00:00:00 2001 From: Behrooz <3968947+drbeh@users.noreply.github.com> Date: Thu, 4 Nov 2021 22:11:43 +0000 Subject: [PATCH 7/9] exclude test_wsireader form min tests Signed-off-by: Behrooz <3968947+drbeh@users.noreply.github.com> --- tests/min_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/min_tests.py b/tests/min_tests.py index 05bd6781c1..2dbc24533d 100644 --- a/tests/min_tests.py +++ b/tests/min_tests.py @@ -104,7 +104,6 @@ def run_testsuit(): "test_nifti_rw", "test_nifti_saver", "test_occlusion_sensitivity", - "test_openslide_reader", "test_orientation", "test_orientationd", "test_parallel_execution", @@ -140,6 +139,7 @@ def run_testsuit(): "test_vit", "test_vitautoenc", "test_write_metrics_reports", + "test_wsireader", "test_zoom", "test_zoom_affine", "test_zoomd", From bc19d2664b5976d9f67169f635511c0ba9e2375d Mon Sep 17 00:00:00 2001 From: Behrooz <3968947+drbeh@users.noreply.github.com> Date: Thu, 4 Nov 2021 22:27:34 +0000 Subject: [PATCH 8/9] Remove trailing comma Signed-off-by: Behrooz <3968947+drbeh@users.noreply.github.com> --- tests/test_wsireader.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/test_wsireader.py b/tests/test_wsireader.py index afd5921fc0..9eebfcfdf1 100644 --- a/tests/test_wsireader.py +++ b/tests/test_wsireader.py @@ -144,10 +144,7 @@ def test_read_rgba(self, img_expected): @parameterized.expand([TEST_CASE_TRANSFORM_0]) def test_with_dataloader(self, file_path, level, expected_spatial_shape, expected_shape): train_transform = Compose( - [ - LoadImaged(keys=["image"], reader=WSIReader, backend="cuCIM", level=level), - ToTensord(keys=["image"]), - ] + [LoadImaged(keys=["image"], reader=WSIReader, backend="cuCIM", level=level), ToTensord(keys=["image"])] ) dataset = Dataset([{"image": file_path}], transform=train_transform) data_loader = DataLoader(dataset) From 5d81d883b4f28b026a965f18797851fd3d4a47f4 Mon Sep 17 00:00:00 2001 From: Behrooz <3968947+drbeh@users.noreply.github.com> Date: Fri, 5 Nov 2021 19:11:28 +0000 Subject: [PATCH 9/9] Change WSIReaderTests base class Signed-off-by: Behrooz <3968947+drbeh@users.noreply.github.com> --- tests/test_wsireader.py | 111 ++++++++++++++++++++-------------------- 1 file changed, 56 insertions(+), 55 deletions(-) diff --git a/tests/test_wsireader.py b/tests/test_wsireader.py index b1583e3d8b..7cd9efbf06 100644 --- a/tests/test_wsireader.py +++ b/tests/test_wsireader.py @@ -97,73 +97,74 @@ def setUpModule(): # noqa: N802 class WSIReaderTests: - backend = None - - @parameterized.expand([TEST_CASE_0]) - def test_read_whole_image(self, file_path, level, expected_shape): - reader = WSIReader(self.backend, level=level) - img_obj = reader.read(file_path) - img = reader.get_data(img_obj)[0] - self.assertTupleEqual(img.shape, expected_shape) - - @parameterized.expand([TEST_CASE_1, TEST_CASE_2]) - def test_read_region(self, file_path, patch_info, expected_img): - reader = WSIReader(self.backend) - img_obj = reader.read(file_path) - img = reader.get_data(img_obj, **patch_info)[0] - self.assertTupleEqual(img.shape, expected_img.shape) - self.assertIsNone(assert_array_equal(img, expected_img)) - - @parameterized.expand([TEST_CASE_3, TEST_CASE_4]) - def test_read_patches(self, file_path, patch_info, expected_img): - reader = WSIReader(self.backend) - img_obj = reader.read(file_path) - img = reader.get_data(img_obj, **patch_info)[0] - self.assertTupleEqual(img.shape, expected_img.shape) - self.assertIsNone(assert_array_equal(img, expected_img)) - - @parameterized.expand([TEST_CASE_RGB_0, TEST_CASE_RGB_1]) - @skipUnless(has_tiff, "Requires tifffile.") - def test_read_rgba(self, img_expected): - # skip for OpenSlide since not working with images without tiles - if self.backend == "openslide": - return - image = {} - reader = WSIReader(self.backend) - for mode in ["RGB", "RGBA"]: - file_path = save_rgba_tiff( - img_expected, - os.path.join(os.path.dirname(__file__), "testing_data", f"temp_tiff_image_{mode}.tiff"), - mode=mode, - ) + class Tests(unittest.TestCase): + backend = None + + @parameterized.expand([TEST_CASE_0]) + def test_read_whole_image(self, file_path, level, expected_shape): + reader = WSIReader(self.backend, level=level) img_obj = reader.read(file_path) - image[mode], _ = reader.get_data(img_obj) + img = reader.get_data(img_obj)[0] + self.assertTupleEqual(img.shape, expected_shape) - self.assertIsNone(assert_array_equal(image["RGB"], img_expected)) - self.assertIsNone(assert_array_equal(image["RGBA"], img_expected)) + @parameterized.expand([TEST_CASE_1, TEST_CASE_2]) + def test_read_region(self, file_path, patch_info, expected_img): + reader = WSIReader(self.backend) + img_obj = reader.read(file_path) + img = reader.get_data(img_obj, **patch_info)[0] + self.assertTupleEqual(img.shape, expected_img.shape) + self.assertIsNone(assert_array_equal(img, expected_img)) - @parameterized.expand([TEST_CASE_TRANSFORM_0]) - def test_with_dataloader(self, file_path, level, expected_spatial_shape, expected_shape): - train_transform = Compose( - [LoadImaged(keys=["image"], reader=WSIReader, backend="cuCIM", level=level), ToTensord(keys=["image"])] - ) - dataset = Dataset([{"image": file_path}], transform=train_transform) - data_loader = DataLoader(dataset) - data: dict = first(data_loader) - spatial_shape = tuple(d.item() for d in data["image_meta_dict"]["spatial_shape"]) - self.assertTupleEqual(spatial_shape, expected_spatial_shape) - self.assertTupleEqual(data["image"].shape, expected_shape) + @parameterized.expand([TEST_CASE_3, TEST_CASE_4]) + def test_read_patches(self, file_path, patch_info, expected_img): + reader = WSIReader(self.backend) + img_obj = reader.read(file_path) + img = reader.get_data(img_obj, **patch_info)[0] + self.assertTupleEqual(img.shape, expected_img.shape) + self.assertIsNone(assert_array_equal(img, expected_img)) + + @parameterized.expand([TEST_CASE_RGB_0, TEST_CASE_RGB_1]) + @skipUnless(has_tiff, "Requires tifffile.") + def test_read_rgba(self, img_expected): + # skip for OpenSlide since not working with images without tiles + if self.backend == "openslide": + return + image = {} + reader = WSIReader(self.backend) + for mode in ["RGB", "RGBA"]: + file_path = save_rgba_tiff( + img_expected, + os.path.join(os.path.dirname(__file__), "testing_data", f"temp_tiff_image_{mode}.tiff"), + mode=mode, + ) + img_obj = reader.read(file_path) + image[mode], _ = reader.get_data(img_obj) + + self.assertIsNone(assert_array_equal(image["RGB"], img_expected)) + self.assertIsNone(assert_array_equal(image["RGBA"], img_expected)) + + @parameterized.expand([TEST_CASE_TRANSFORM_0]) + def test_with_dataloader(self, file_path, level, expected_spatial_shape, expected_shape): + train_transform = Compose( + [LoadImaged(keys=["image"], reader=WSIReader, backend="cuCIM", level=level), ToTensord(keys=["image"])] + ) + dataset = Dataset([{"image": file_path}], transform=train_transform) + data_loader = DataLoader(dataset) + data: dict = first(data_loader) + spatial_shape = tuple(d.item() for d in data["image_meta_dict"]["spatial_shape"]) + self.assertTupleEqual(spatial_shape, expected_spatial_shape) + self.assertTupleEqual(data["image"].shape, expected_shape) @skipUnless(has_cucim, "Requires cucim") -class TestCuCIM(unittest.TestCase, WSIReaderTests): +class TestCuCIM(WSIReaderTests.Tests): @classmethod def setUpClass(cls): cls.backend = "cucim" @skipUnless(has_osl, "Requires OpenSlide") -class TestOpenSlide(unittest.TestCase, WSIReaderTests): +class TestOpenSlide(WSIReaderTests.Tests): @classmethod def setUpClass(cls): cls.backend = "openslide"