Skip to content

Commit 88002e8

Browse files
author
Nikolas Schmitz
committed
Added get_img_at_mpp to class CuCIMWSIReader
1 parent f3e7d03 commit 88002e8

File tree

1 file changed

+98
-0
lines changed

1 file changed

+98
-0
lines changed

monai/data/wsi_reader.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -603,6 +603,23 @@ def get_mpp(self, wsi, level: int) -> tuple[float, float]:
603603
604604
"""
605605
return self.reader.get_mpp(wsi, level)
606+
607+
def get_img_at_mpp(self, wsi, mpp: tuple, atol: float = 0.00, rtol: float = 0.05) -> np.array:
608+
"""
609+
Returns the representation of the whole slide image at a given micro-per-pixel (mpp) resolution.
610+
The optional tolerance parameters are considered at the level whose mpp value is closest to the one provided by the user.
611+
If the user-provided mpp is larger than the mpp of the closest level—indicating that the closest level has a higher resolution than requested—the image is downscaled to a resolution that matches the user-provided mpp.
612+
Otherwise, if the closest level's resolution is not sufficient to meet the user's requested resolution, the next lower level (which has a higher resolution) is chosen.
613+
The image from this level is then down-scaled to achieve a resolution at the user-provided mpp value.
614+
615+
Args:
616+
wsi: whole slide image object from WSIReader
617+
mpp: the resolution in micron per pixel at which the representation of the whole slide image should be extracted.
618+
atol: the acceptable absolute tolerance for resolution in micro per pixel.
619+
rtol: the acceptable relative tolerance for resolution in micro per pixel.
620+
621+
"""
622+
return self.reader.get_img_at_mpp(wsi, mpp, atol, rtol)
606623

607624
def get_power(self, wsi, level: int) -> float:
608625
"""
@@ -745,6 +762,87 @@ def get_mpp(self, wsi, level: int) -> tuple[float, float]:
745762

746763
raise ValueError("`mpp` cannot be obtained for this file. Please use `level` instead.")
747764

765+
def get_img_at_mpp(self, wsi, mpp: tuple, atol: float = 0.00, rtol: float = 0.05) -> np.array:
766+
"""
767+
Returns the representation of the whole slide image at a given micro-per-pixel (mpp) resolution.
768+
The optional tolerance parameters are considered at the level whose mpp value is closest to the one provided by the user.
769+
If the user-provided mpp is larger than the mpp of the closest level—indicating that the closest level has a higher resolution than requested—the image is downscaled to a resolution that matches the user-provided mpp.
770+
Otherwise, if the closest level's resolution is not sufficient to meet the user's requested resolution, the next lower level (which has a higher resolution) is chosen.
771+
The image from this level is then down-scaled to achieve a resolution at the user-provided mpp value.
772+
773+
Args:
774+
wsi: whole slide image object from WSIReader
775+
mpp: the resolution in micron per pixel at which the representation of the whole slide image should be extracted.
776+
atol: the acceptable absolute tolerance for resolution in micro per pixel.
777+
rtol: the acceptable relative tolerance for resolution in micro per pixel.
778+
779+
"""
780+
781+
user_mpp_x, user_mpp_y = mpp
782+
mpp_list = [self.get_mpp(wsi, lvl) for lvl in range(wsi.resolutions['level_count'])]
783+
closest_lvl = self._find_closest_level("mpp", mpp, mpp_list, 0, 5) # Should not throw ValueError, instead just return the closest value;
784+
mpp_closest_lvl = mpp_list[closest_lvl]
785+
closest_lvl_dim = wsi.resolutions['level_dimensions'][closest_lvl]
786+
787+
print(f'Closest Level: {closest_lvl} with MPP: {mpp_closest_lvl}')
788+
mpp_closest_lvl_x, mpp_closest_lvl_y = mpp_closest_lvl
789+
790+
# Define tolerance intervals for x and y of closest level
791+
lower_bound_x = mpp_closest_lvl_x * (1 - rtol) - atol
792+
upper_bound_x = mpp_closest_lvl_x * (1 + rtol) + atol
793+
lower_bound_y = mpp_closest_lvl_y * (1 - rtol) - atol
794+
upper_bound_y = mpp_closest_lvl_y * (1 + rtol) + atol
795+
796+
# Check if user-provided mpp_x and mpp_y fall within the tolerance intervals for closest level
797+
within_tolerance_x = (user_mpp_x >= lower_bound_x) & (user_mpp_x <= upper_bound_x)
798+
within_tolerance_y = (user_mpp_y >= lower_bound_y) & (user_mpp_y <= upper_bound_y)
799+
within_tolerance = within_tolerance_x & within_tolerance_y
800+
801+
if within_tolerance:
802+
# Take closest_level and continue with returning img at level
803+
print(f'User-provided MPP lies within tolerance of level {closest_lvl}, returning wsi at this level.')
804+
closest_lvl_wsi = np.array(wsi.read_region(location=(0, 0), level=closest_lvl, size=closest_lvl_dim))[:, :, :3]
805+
806+
return closest_lvl_wsi
807+
else:
808+
# If mpp_closest_level < mpp -> closest_level has higher res than img at mpp => downscale from closest_level to mpp
809+
closest_level_is_bigger_x = mpp_closest_lvl_x < user_mpp_x
810+
closest_level_is_bigger_y = mpp_closest_lvl_y < user_mpp_y
811+
closest_level_is_bigger = closest_level_is_bigger_x & closest_level_is_bigger_y
812+
813+
if closest_level_is_bigger:
814+
ds_factor_x = mpp_closest_lvl_x / user_mpp_x
815+
ds_factor_y = mpp_closest_lvl_y / user_mpp_y
816+
817+
closest_lvl_wsi = np.array(wsi.read_region(location=(0, 0), level=closest_lvl, size=closest_lvl_dim, num_workers=self.num_workers))[:, :, :3]
818+
819+
target_res_x = int(np.round(closest_lvl_dim[0] * ds_factor_x))
820+
target_res_y = int(np.round(closest_lvl_dim[1] * ds_factor_y))
821+
822+
closest_lvl_wsi = cv2.resize(closest_lvl_wsi, dsize=(target_res_x, target_res_y), interpolation=cv2.INTER_LINEAR)
823+
824+
print(f'Case 1: Downscaling using factor {(ds_factor_x, ds_factor_y)}')
825+
return closest_lvl_wsi
826+
else:
827+
# Else: increase resolution (ie, decrement level) and then downsample
828+
closest_lvl = closest_lvl - 1
829+
mpp_closest_lvl = mpp_list[closest_lvl] # Update MPP
830+
mpp_closest_lvl_x, mpp_closest_lvl_y = mpp_closest_lvl
831+
832+
ds_factor_x = mpp_closest_lvl_x / user_mpp_x
833+
ds_factor_y = mpp_closest_lvl_y / user_mpp_y
834+
835+
closest_lvl_dim = wsi.resolutions['level_dimensions'][closest_lvl]
836+
closest_lvl_wsi = np.array(wsi.read_region(location=(0, 0), level=closest_lvl, size=closest_lvl_dim, num_workers=self.num_workers))[:, :, :3]
837+
838+
target_res_x = int(np.round(closest_lvl_dim[0] * ds_factor_x))
839+
target_res_y = int(np.round(closest_lvl_dim[1] * ds_factor_y))
840+
841+
closest_lvl_wsi = cv2.resize(closest_lvl_wsi, dsize=(target_res_x, target_res_y), interpolation=cv2.INTER_LINEAR)
842+
843+
print(f'Case 2: Downscaling using factor {(ds_factor_x, ds_factor_y)}, now from level {closest_lvl}')
844+
return closest_lvl_wsi
845+
748846
def get_power(self, wsi, level: int) -> float:
749847
"""
750848
Returns the objective power of the whole slide image at a given level.

0 commit comments

Comments
 (0)