Skip to content

Update TiffFile backend in WSIReader #3438

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Dec 2, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 31 additions & 26 deletions monai/data/image_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -768,10 +768,8 @@ def get_data(
# Verify inputs
if level is None:
level = self._check_level(img, level)
if size is None:
size = self._get_image_size(img, size, level, location)

# Extract patch (or the whole image)
# Extract a region or the entire image
region = self._extract_region(img, location=location, size=size, level=level, dtype=dtype)

# Add necessary metadata
Expand Down Expand Up @@ -810,49 +808,56 @@ def _check_level(self, img, level):
return level

def _get_image_size(self, img, size, level, location):
"""
Calculate the maximum region size for the given level and starting location (if size is None).
Note that region size in OpenSlide and cuCIM are WxH (but the final image output would be HxW)
"""
if size is not None:
return size[::-1]

max_size = []
downsampling_factor = []
if self.backend == "openslide":
downsampling_factor = img.level_downsamples[level]
max_size = img.level_dimensions[level][::-1]
max_size = img.level_dimensions[level]
elif self.backend == "cucim":
downsampling_factor = img.resolutions["level_downsamples"][level]
max_size = img.resolutions["level_dimensions"][level][::-1]
elif self.backend == "tifffile":
level0_size = img.pages[0].shape[:2]
max_size = img.pages[level].shape[:2]
downsampling_factor = np.mean([level0_size[i] / max_size[i] for i in range(len(max_size))])
max_size = img.resolutions["level_dimensions"][level]

# subtract the top left corner of the patch from maximum size
level_location = [round(location[i] / downsampling_factor) for i in range(len(location))]
size = [max_size[i] - level_location[i] for i in range(len(max_size))]
# subtract the top left corner of the patch (at given level) from maximum size
location_at_level = (round(location[1] / downsampling_factor), round(location[0] / downsampling_factor))
size = [max_size[i] - location_at_level[i] for i in range(len(max_size))]

return size

def _extract_region(
self,
img_obj,
size: Tuple[int, int],
size: Optional[Tuple[int, int]],
location: Tuple[int, int] = (0, 0),
level: int = 0,
dtype: DtypeLike = np.uint8,
):
if self.backend == "tifffile":
# with img_obj:
region = img_obj.asarray(level=level)
if level != 0:
level0_size = img_obj.pages[0].shape[:2]
max_size = img_obj.pages[level].shape[:2]
location = (
int(location[0] / level0_size[0] * max_size[0]),
int(location[1] / level0_size[1] * max_size[1]),
# Read the entire image
if size is not None:
raise ValueError(
f"TiffFile backend reads the entire image only, so size '{size}'' should not be provided!",
"For more flexibility or extracting regions, please use cuCIM or OpenSlide backend.",
)
if location != (0, 0):
raise ValueError(
f"TiffFile backend reads the entire image only, so location '{location}' should not be provided!",
"For more flexibility and extracting regions, please use cuCIM or OpenSlide backend.",
)
region = region[location[0] : location[0] + size[0], location[1] : location[1] + size[1]]
region = img_obj.asarray(level=level)
else:
# reverse the order of dimensions for size and location to become WxH
location = location[::-1]
size = size[::-1]
region = img_obj.read_region(location=location, size=size, level=level)
# Get region size to be extracted
region_size = self._get_image_size(img_obj, size, level, location)
# reverse the order of location's dimensions to become WxH (for cuCIM and OpenSlide)
region_location = location[::-1]
# Extract a region (or the entire image)
region = img_obj.read_region(location=region_location, size=region_size, level=level)

region = self.convert_to_rgb_array(region, dtype)
return region
Expand Down
31 changes: 18 additions & 13 deletions tests/test_wsireader.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,26 +119,31 @@ def test_read_whole_image(self, file_path, level, expected_shape):

@parameterized.expand([TEST_CASE_1, TEST_CASE_2, TEST_CASE_5])
def test_read_region(self, file_path, patch_info, expected_img):
# Due to CPU memory limitation ignore tifffile at level 0.
if self.backend == "tifffile" and patch_info["level"] == 0:
return
reader = WSIReader(self.backend)
# Read twice to check multiple calls
with reader.read(file_path) as img_obj:
img = reader.get_data(img_obj, **patch_info)[0]
img2 = reader.get_data(img_obj, **patch_info)[0]
self.assertTupleEqual(img.shape, img2.shape)
self.assertIsNone(assert_array_equal(img, img2))
self.assertTupleEqual(img.shape, expected_img.shape)
self.assertIsNone(assert_array_equal(img, expected_img))
if self.backend == "tifffile":
with self.assertRaises(ValueError):
reader.get_data(img_obj, **patch_info)[0]
else:
# Read twice to check multiple calls
img = reader.get_data(img_obj, **patch_info)[0]
img2 = reader.get_data(img_obj, **patch_info)[0]
self.assertTupleEqual(img.shape, img2.shape)
self.assertIsNone(assert_array_equal(img, img2))
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)
with reader.read(file_path) as img_obj:
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 self.backend == "tifffile":
with self.assertRaises(ValueError):
reader.get_data(img_obj, **patch_info)[0]
else:
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.")
Expand Down