Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
95 commits
Select commit Hold shift + click to select a range
f3e7d03
Added function get_img_at_mpp to class OpenSlideWSIReader of module w…
Mar 22, 2024
88002e8
Added get_img_at_mpp to class CuCIMWSIReader
Mar 22, 2024
a9fe772
Added function get_img_at_mpp to class TifffileWSIReader; changed res…
Mar 24, 2024
feac0dc
Small changes
Mar 24, 2024
8194026
Small changes
Mar 24, 2024
4df0b4b
Stein's Unbiased Risk Estimator (SURE) loss and Conjugate Gradient (#…
cxlcl Mar 22, 2024
d989c18
Renamed function to get_wsi_at_mpp
Mar 24, 2024
105f00b
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Mar 24, 2024
5db27c1
Reformatted wsi_reader.py
NikolasSchmitz Mar 25, 2024
18e82bd
auto updates (#7577)
monai-bot Mar 25, 2024
5bb531e
Fixed return type
NikolasSchmitz Mar 25, 2024
5214c56
Small fixes
NikolasSchmitz Mar 25, 2024
3f055a9
Remove nested error propagation on `ConfigComponent` instantiate (#7569)
surajpaib Mar 26, 2024
3264079
2872 implementation of mixup, cutmix and cutout (#7198)
juampatronics Mar 26, 2024
22ecc8c
Merge branch 'dev' into 4980-get-wsi-at-mpp
bhashemian Apr 9, 2024
6fcc4a6
Updated function get_wsi_at_mpp; added function _resize_to_mpp_res to…
NikolasSchmitz Jul 31, 2024
4b0c9ba
Minor fixes: removed unnecessary comments
NikolasSchmitz Jul 31, 2024
d1a5e28
Merge branch 'dev' into 4980-get-wsi-at-mpp
NikolasSchmitz Aug 2, 2024
66508e9
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 2, 2024
d4040dd
Merge pull request #2 from NikolasSchmitz/4980-get-wsi-at-mpp
NikolasSchmitz Aug 2, 2024
441b462
Added function _compute_mpp_target_res to BaseWSIReader
NikolasSchmitz Aug 4, 2024
d73d739
Added new feature and merged updates from main repository
NikolasSchmitz Aug 4, 2024
feb6828
Added function _compute_mpp_target_res to BaseWSIReader
NikolasSchmitz Aug 4, 2024
5461801
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 4, 2024
e8c1544
Merge branch 'dev' into 4980-get-wsi-at-mpp
ericspod Aug 7, 2024
59683bc
Added a function _compute_mpp_tolerances which checks the mpp toleran…
NikolasSchmitz Aug 11, 2024
547442e
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 11, 2024
3e337b0
Merge branch 'Project-MONAI:dev' into 4980-get-wsi-at-mpp
NikolasSchmitz Aug 14, 2024
cc55b8a
Merge branch 'Project-MONAI:dev' into 4980-get-wsi-at-mpp
NikolasSchmitz Aug 27, 2024
9eca8de
Merge branch 'Project-MONAI:dev' into 4980-get-wsi-at-mpp
NikolasSchmitz Sep 10, 2024
a8bb436
Merge branch 'Project-MONAI:dev' into 4980-get-wsi-at-mpp
NikolasSchmitz Sep 16, 2024
6094ffd
Merge branch 'Project-MONAI:dev' into 4980-get-wsi-at-mpp
NikolasSchmitz Sep 21, 2024
c1dd7c3
Merge branch 'Project-MONAI:dev' into 4980-get-wsi-at-mpp
NikolasSchmitz Nov 17, 2024
349c011
Merge branch 'Project-MONAI:dev' into 4980-get-wsi-at-mpp
NikolasSchmitz Dec 3, 2024
6ff912d
Merge branch 'Project-MONAI:dev' into dev
NikolasSchmitz Dec 3, 2024
ca6796b
Merge branch 'Project-MONAI:dev' into 4980-get-wsi-at-mpp
NikolasSchmitz Jan 8, 2025
49f1663
Merge branch 'Project-MONAI:dev' into dev
NikolasSchmitz Jan 15, 2025
52e0516
Merge branch 'Project-MONAI:dev' into 4980-get-wsi-at-mpp
NikolasSchmitz Mar 4, 2025
54774d9
Updated WSI reader
NikolasSchmitz Mar 4, 2025
2962016
Merge branch '4980-get-wsi-at-mpp' of https://github.com/NikolasSchmi…
NikolasSchmitz Mar 4, 2025
3f87b10
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Mar 4, 2025
801ca7f
Merge branch 'Project-MONAI:dev' into 4980-get-wsi-at-mpp
NikolasSchmitz Mar 5, 2025
1c5a26c
Merge branch 'Project-MONAI:dev' into 4980-get-wsi-at-mpp
NikolasSchmitz Mar 27, 2025
e73cff8
Merge branch 'Project-MONAI:dev' into 4980-get-wsi-at-mpp
NikolasSchmitz Apr 4, 2025
aa4f2de
Merge branch 'Project-MONAI:dev' into 4980-get-wsi-at-mpp
NikolasSchmitz May 7, 2025
c09c7e2
Merge branch 'dev' into 4980-get-wsi-at-mpp
bhashemian May 8, 2025
505e38b
Merge branch 'Project-MONAI:dev' into 4980-get-wsi-at-mpp
NikolasSchmitz Jun 25, 2025
df29f44
Merge branch 'Project-MONAI:dev' into 4980-get-wsi-at-mpp
NikolasSchmitz Jul 11, 2025
f70d0ef
Added get_wsi_at_mpp tests; fixed a few bugs
NikolasSchmitz Jul 17, 2025
61fc9bf
Added function get_img_at_mpp to class OpenSlideWSIReader of module w…
Mar 22, 2024
a834514
Added get_img_at_mpp to class CuCIMWSIReader
Mar 22, 2024
c92475d
Added function get_img_at_mpp to class TifffileWSIReader; changed res…
Mar 24, 2024
5b988a2
Small changes
Mar 24, 2024
5168a12
Small changes
Mar 24, 2024
1437477
Stein's Unbiased Risk Estimator (SURE) loss and Conjugate Gradient (#…
cxlcl Mar 22, 2024
a4a78e3
Renamed function to get_wsi_at_mpp
Mar 24, 2024
b1ed4ff
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Mar 24, 2024
6b18e9b
Reformatted wsi_reader.py
NikolasSchmitz Mar 25, 2024
d74d4be
auto updates (#7577)
monai-bot Mar 25, 2024
d6d0cc2
Fixed return type
NikolasSchmitz Mar 25, 2024
2f4388e
Small fixes
NikolasSchmitz Mar 25, 2024
d0a4881
Updated function get_wsi_at_mpp; added function _resize_to_mpp_res to…
NikolasSchmitz Jul 31, 2024
7545148
Minor fixes: removed unnecessary comments
NikolasSchmitz Jul 31, 2024
b01bf63
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 2, 2024
5c7822f
Added new feature and merged updates from main repository
NikolasSchmitz Aug 4, 2024
fd0d0cf
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 4, 2024
234f23f
Added a function _compute_mpp_tolerances which checks the mpp toleran…
NikolasSchmitz Aug 11, 2024
2730abe
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 11, 2024
9d817e7
Updated WSI reader
NikolasSchmitz Mar 4, 2025
2afa6fb
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Mar 4, 2025
8270658
Added get_wsi_at_mpp tests; fixed a few bugs
NikolasSchmitz Jul 17, 2025
23e4a74
Added get_wsi_at_mpp tests; fixed a few bugs
NikolasSchmitz Jul 17, 2025
787d30f
Added get_wsi_at_mpp tests; fixed a few bugs
NikolasSchmitz Jul 18, 2025
0d9f1dd
Added get_wsi_at_mpp tests; fixed a few bugs
NikolasSchmitz Jul 18, 2025
45182fa
Added get_wsi_at_mpp tests; fixed a few bugs
NikolasSchmitz Jul 18, 2025
a361fa2
Merge branch '4980-get-wsi-at-mpp' of https://github.com/NikolasSchmi…
NikolasSchmitz Jul 18, 2025
832c14e
Added get_wsi_at_mpp tests; fixed a few bugs
NikolasSchmitz Jul 18, 2025
98d45b9
Merge branch 'Project-MONAI:dev' into dev
NikolasSchmitz Jul 18, 2025
5059b14
Merge branch 'Project-MONAI:dev' into 4980-get-wsi-at-mpp
NikolasSchmitz Jul 18, 2025
d644371
Merge branch 'dev' into 4980-get-wsi-at-mpp
NikolasSchmitz Jul 24, 2025
922bbf1
Merge branch 'Project-MONAI:dev' into 4980-get-wsi-at-mpp
NikolasSchmitz Jul 25, 2025
4249b48
DCO Remediation Commit for Nikolas Schmitz <nikolas.schmitz@rwth-aach…
NikolasSchmitz Jul 25, 2025
72f14e5
Merge branch 'Project-MONAI:dev' into dev
NikolasSchmitz Jul 25, 2025
bb19273
Merge branch 'Project-MONAI:dev' into dev
NikolasSchmitz Aug 26, 2025
bd6f87e
Merge branch 'Project-MONAI:dev' into 4980-get-wsi-at-mpp
NikolasSchmitz Aug 26, 2025
2553e3e
Merge branch 'dev' into 4980-get-wsi-at-mpp
bhashemian Sep 3, 2025
1c1ecd0
Merge branch 'dev' into 4980-get-wsi-at-mpp
bhashemian Oct 21, 2025
b3b1341
Merge branch 'Project-MONAI:dev' into dev
NikolasSchmitz Oct 31, 2025
77de338
Merge branch 'Project-MONAI:dev' into dev
NikolasSchmitz Nov 4, 2025
6169e75
Merge branch 'dev' into 4980-get-wsi-at-mpp
NikolasSchmitz Nov 4, 2025
1789735
Merge branch 'dev' into 4980-get-wsi-at-mpp
bhashemian Nov 12, 2025
01e60e0
Minor changes
NikolasSchmitz Nov 12, 2025
165af63
Merge branch '4980-get-wsi-at-mpp' of https://github.com/NikolasSchmi…
NikolasSchmitz Nov 12, 2025
4460f45
Fixed bug in get_wsi_at_mpp; added Return sections to docstrings
NikolasSchmitz Nov 14, 2025
414078d
Merge branch 'Project-MONAI:dev' into 4980-get-wsi-at-mpp
NikolasSchmitz Nov 14, 2025
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
319 changes: 315 additions & 4 deletions monai/data/wsi_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,70 @@ def get_data(
metadata[key] = [m[key] for m in metadata_list]
return _stack_images(patch_list, metadata), metadata

def _compute_mpp_target_res(self, closest_lvl, closest_lvl_dim, mpp_list, mpp: tuple):
"""
Computes the target dimensions for resizing a whole slide image
to match a user-specified resolution in microns per pixel (MPP).

Args:
closest_lvl: Whole slide image level closest to user-provided MPP resolution.
closest_lvl_dim: Dimensions (height, width) of the image at the closest level.
mpp_list: List of MPP values for all levels of the whole slide image.
mpp: The MPP resolution at which the whole slide image representation should be extracted.

Returns:
Tuple of (target_res_x, target_res_y) representing the target pixel dimensions.

"""
mpp_closest_lvl = mpp_list[closest_lvl]
mpp_closest_lvl_x, mpp_closest_lvl_y = mpp_closest_lvl

ds_factor_x = mpp_closest_lvl_x / mpp[0]
ds_factor_y = mpp_closest_lvl_y / mpp[1]

target_res_x = int(np.round(closest_lvl_dim[1] * ds_factor_x))
target_res_y = int(np.round(closest_lvl_dim[0] * ds_factor_y))

return target_res_x, target_res_y

def _compute_mpp_tolerances(self, closest_lvl, mpp_list, mpp, atol, rtol) -> bool:
"""
Determines if user-provided MPP values are within a specified tolerance of the closest
level's MPP and checks if the closest level has higher resolution than desired MPP.

Args:
closest_lvl: Whole slide image level closest to user-provided MPP resolution.
mpp_list: List of MPP values for all levels of the whole slide image.
mpp: The MPP resolution at which the whole slide image representation should be extracted.
atol: Absolute tolerance for MPP comparison.
rtol: Relative tolerance for MPP comparison.

Returns:
Tuple of (is_within_tolerance, closest_level_is_bigger) where first element indicates
if MPP is within tolerance and second indicates if closest level has higher resolution.

"""
user_mpp_x, user_mpp_y = mpp
mpp_closest_lvl_x, mpp_closest_lvl_y = mpp_list[closest_lvl]

# Define tolerance intervals for x and y of closest level
lower_bound_x = mpp_closest_lvl_x * (1 - rtol) - atol
upper_bound_x = mpp_closest_lvl_x * (1 + rtol) + atol
lower_bound_y = mpp_closest_lvl_y * (1 - rtol) - atol
upper_bound_y = mpp_closest_lvl_y * (1 + rtol) + atol

# Check if user-provided mpp_x and mpp_y fall within the tolerance intervals for closest level
is_within_tolerance_x = (user_mpp_x >= lower_bound_x) and (user_mpp_x <= upper_bound_x)
is_within_tolerance_y = (user_mpp_y >= lower_bound_y) and (user_mpp_y <= upper_bound_y)
is_within_tolerance = is_within_tolerance_x and is_within_tolerance_y

# If mpp_closest_level < mpp -> closest_level has higher res than img at mpp => downscale from closest_level to mpp
closest_level_is_bigger_x = mpp_closest_lvl_x < user_mpp_x
closest_level_is_bigger_y = mpp_closest_lvl_y < user_mpp_y
closest_level_is_bigger = closest_level_is_bigger_x and closest_level_is_bigger_y

return is_within_tolerance, closest_level_is_bigger

def verify_suffix(self, filename: Sequence[PathLike] | PathLike) -> bool:
"""
Verify whether the specified file or files format is supported by WSI reader.
Expand Down Expand Up @@ -603,6 +667,28 @@ def get_mpp(self, wsi, level: int) -> tuple[float, float]:
"""
return self.reader.get_mpp(wsi, level)

def get_wsi_at_mpp(self, wsi, mpp: tuple, atol: float = 0.00, rtol: float = 0.05) -> np.ndarray:
"""
Returns the representation of the whole slide image at a given micro-per-pixel (mpp) resolution.
The optional tolerance parameters are considered at the level whose mpp value is closest to the one provided by the user.
If the user-provided mpp is larger than the mpp of the closest level,
the image is downscaled to a resolution that matches the user-provided mpp.
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.
The image from this level is then down-scaled to achieve a resolution at the user-provided mpp value.

Args:
wsi: whole slide image object from WSIReader
mpp: the resolution in micron per pixel at which the representation of the whole slide image should be extracted.
atol: the acceptable absolute tolerance for resolution in micro per pixel.
rtol: the acceptable relative tolerance for resolution in micro per pixel.

Returns:
Numpy array containing the whole slide image at the requested MPP resolution.

"""
return self.reader.get_wsi_at_mpp(wsi, mpp, atol, rtol)

def get_power(self, wsi, level: int) -> float:
"""
Returns the micro-per-pixel resolution of the whole slide image at a given level.
Expand Down Expand Up @@ -744,6 +830,54 @@ def get_mpp(self, wsi, level: int) -> tuple[float, float]:

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

def get_wsi_at_mpp(self, wsi, mpp: tuple, atol: float = 0.00, rtol: float = 0.05) -> Any:
"""
Returns the representation of the whole slide image at a given micro-per-pixel (mpp) resolution.
The optional tolerance parameters are considered at the level whose mpp value is closest to the one provided by the user.
If the user-provided mpp is larger than the mpp of the closest level,
the image is downscaled to a resolution that matches the user-provided mpp.
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.
The image from this level is then down-scaled to achieve a resolution at the user-provided mpp value.

Args:
wsi: whole slide image object from WSIReader
mpp: the resolution in micron per pixel at which the representation of the whole slide image should be extracted.
atol: the acceptable absolute tolerance for resolution in micro per pixel.
rtol: the acceptable relative tolerance for resolution in micro per pixel.

Returns:
Cupy array containing the whole slide image at the requested MPP resolution.

"""
cp, _ = optional_import("cupy")

mpp_list = [self.get_mpp(wsi, lvl) for lvl in range(wsi.resolutions["level_count"])]
closest_lvl = self._find_closest_level("mpp", mpp, mpp_list, 0, 5)

within_tolerance, closest_level_is_bigger = self._compute_mpp_tolerances(closest_lvl, mpp_list, mpp, atol, rtol)

if within_tolerance:
# If the image at the desired mpp resolution is within tolerances, return the image at closest_level.
closest_lvl_wsi = wsi.read_region(
(0, 0), level=closest_lvl, size=wsi.resolutions["level_dimensions"][closest_lvl], num_workers=self.num_workers
)

elif closest_level_is_bigger:
# Otherwise, select the level closest to the desired mpp with a higher resolution and downsample it.
closest_lvl_wsi = self._resize_to_mpp_res(wsi, closest_lvl, mpp_list, mpp)

else:
# If both checks fail, increase resolution (i.e., decrement level) and then downsample it.
if closest_lvl == 0:
closest_lvl_wsi = self._resize_to_mpp_res(wsi, closest_lvl, mpp_list, mpp)
else:
closest_lvl = closest_lvl - 1
closest_lvl_wsi = self._resize_to_mpp_res(wsi, closest_lvl, mpp_list, mpp)

wsi_arr = cp.asnumpy(closest_lvl_wsi)
return wsi_arr
Comment on lines +833 to +879
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Handle scalar mpp inputs.

Passing a single float (supported elsewhere in this API surface) now breaks because _find_closest_level calls len(value) on the scalar and raises TypeError. Normalize mpp with ensure_tuple_rep(mpp, 2) before using it here, and mirror the guard in the OpenSlide and TiffFile implementations so direct backend use stays compatible with existing call patterns.

Apply this diff (and replicate in the other backends):

         cp, _ = optional_import("cupy")
 
+        mpp = ensure_tuple_rep(mpp, 2)
+
         mpp_list = [self.get_mpp(wsi, lvl) for lvl in range(wsi.resolutions["level_count"])]
🤖 Prompt for AI Agents
In monai/data/wsi_reader.py around lines 833 to 879, the function get_wsi_at_mpp
fails when mpp is passed as a scalar because _find_closest_level expects a tuple
and calls len(value); normalize mpp at the start of the function using
ensure_tuple_rep(mpp, 2) (same guard used in OpenSlide/TiffFile backends) so
subsequent calls (mpp_list computation and _find_closest_level) always receive a
2-tuple; apply the same normalization to the other backend implementations to
keep direct backend usage compatible with existing call patterns.


def get_power(self, wsi, level: int) -> float:
"""
Returns the objective power of the whole slide image at a given level.
Expand Down Expand Up @@ -828,6 +962,37 @@ def _get_patch(

return patch

def _resize_to_mpp_res(self, wsi, closest_lvl, mpp_list, user_mpp: tuple):
"""
Resizes the whole slide image to the specified resolution in microns per pixel (mpp).

Args:
wsi: whole slide image object from WSIReader
user_mpp: the resolution in microns per pixel at which the whole slide image representation should be extracted.
closest_lvl: the wsi level that is closest to the user-provided mpp resolution.
mpp_list: list of mpp values for all levels of a whole slide image.

Returns:
Resized cupy image array at the target MPP resolution.

"""
cucim_resize, _ = optional_import("cucim.skimage.transform", name="resize")
cp, _ = optional_import("cupy")

closest_lvl_dim = wsi.resolutions["level_dimensions"][closest_lvl]

target_res_x, target_res_y = self._compute_mpp_target_res(closest_lvl, closest_lvl_dim, mpp_list, user_mpp)

wsi_arr = cp.array(wsi.read_region((0, 0), level=closest_lvl, size=closest_lvl_dim, num_workers=self.num_workers))
closest_lvl_wsi = cucim_resize(
wsi_arr,
(target_res_x, target_res_y),
order=1,
preserve_range=True,
anti_aliasing=False).astype(cp.uint8)

return closest_lvl_wsi


@require_pkg(pkg_name="openslide")
class OpenSlideWSIReader(BaseWSIReader):
Expand Down Expand Up @@ -940,6 +1105,53 @@ def get_mpp(self, wsi, level: int) -> tuple[float, float]:

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

def get_wsi_at_mpp(self, wsi, mpp: tuple, atol: float = 0.00, rtol: float = 0.05) -> np.ndarray:
"""
Returns the representation of the whole slide image at a given micro-per-pixel (mpp) resolution.
The optional tolerance parameters are considered at the level whose mpp value is closest to the one provided by the user.
If the user-provided mpp is larger than the mpp of the closest level,
the image is downscaled to a resolution that matches the user-provided mpp.
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.
The image from this level is then down-scaled to achieve a resolution at the user-provided mpp value.

Args:
wsi: whole slide image object from WSIReader
mpp: the resolution in micron per pixel at which the representation of the whole slide image should be extracted.
atol: the acceptable absolute tolerance for resolution in micro per pixel.
rtol: the acceptable relative tolerance for resolution in micro per pixel.

Returns:
Numpy array containing the whole slide image at the requested MPP resolution.

"""

mpp_list = [self.get_mpp(wsi, lvl) for lvl in range(wsi.level_count)]
closest_lvl = self._find_closest_level("mpp", mpp, mpp_list, 0, 5)

within_tolerance, closest_level_is_bigger = self._compute_mpp_tolerances(closest_lvl, mpp_list, mpp, atol, rtol)

if within_tolerance:
# If the image at the desired mpp resolution is within tolerances, return the image at closest_level.
closest_lvl_wsi = wsi.read_region(
(0, 0), level=closest_lvl, size=wsi.level_dimensions[closest_lvl]
)

elif closest_level_is_bigger:
# Otherwise, select the level closest to the desired mpp with a higher resolution and downsample it.
closest_lvl_wsi = self._resize_to_mpp_res(wsi, closest_lvl, mpp_list, mpp)

else:
# If both checks fail, increase resolution (i.e., decrement level) and then downsample it.
if closest_lvl == 0:
closest_lvl_wsi = self._resize_to_mpp_res(wsi, closest_lvl, mpp_list, mpp)
else:
closest_lvl = closest_lvl - 1
closest_lvl_wsi = self._resize_to_mpp_res(wsi, closest_lvl, mpp_list, mpp)

wsi_arr = np.array(closest_lvl_wsi)
return wsi_arr

def get_power(self, wsi, level: int) -> float:
"""
Returns the objective power of the whole slide image at a given level.
Expand Down Expand Up @@ -1010,6 +1222,31 @@ def _get_patch(

return patch

def _resize_to_mpp_res(self, wsi, closest_lvl, mpp_list, user_mpp: tuple):
"""
Resizes the whole slide image to the specified resolution in microns per pixel (mpp).

Args:
wsi: whole slide image object from WSIReader
user_mpp: the resolution in microns per pixel at which the whole slide image representation should be extracted.
closest_lvl: the wsi level that is closest to the user-provided mpp resolution.
mpp_list: list of mpp values for all levels of a whole slide image.

Returns:
PIL Image object resized to the target MPP resolution.

"""
pil_image, _ = optional_import("PIL", name="Image")

closest_lvl_dim = wsi.level_dimensions[closest_lvl]

target_res_x, target_res_y = self._compute_mpp_target_res(closest_lvl, closest_lvl_dim, mpp_list, user_mpp)

closest_lvl_wsi = wsi.read_region((0, 0), level=closest_lvl, size=closest_lvl_dim)
closest_lvl_wsi = closest_lvl_wsi.resize((target_res_y, target_res_x), pil_image.BILINEAR) # row, col order

return closest_lvl_wsi


@require_pkg(pkg_name="tifffile")
class TiffFileWSIReader(BaseWSIReader):
Expand Down Expand Up @@ -1103,12 +1340,61 @@ def get_mpp(self, wsi, level: int) -> tuple[float, float]:
unit = "micrometer"

convert_to_micron = ConvertUnits(unit, "micrometer")
# Here x and y resolutions are rational numbers so each of them is represented by a tuple.

# Here, x and y resolutions are rational numbers so each of them is represented by a tuple.
yres = wsi.pages[level].tags["YResolution"].value
xres = wsi.pages[level].tags["XResolution"].value
return convert_to_micron(yres[1] / yres[0]), convert_to_micron(xres[1] / xres[0])
if xres[0] and yres[0]:
return convert_to_micron(yres[1] / yres[0]), convert_to_micron(xres[1] / xres[0])
else:
raise ValueError("The `XResolution` and/or `YResolution` property of the image is zero, "
"which is needed to obtain `mpp` for this file. Please use `level` instead.")
raise ValueError("`mpp` cannot be obtained for this file. Please use `level` instead.")

def get_wsi_at_mpp(self, wsi, mpp: tuple, atol: float = 0.00, rtol: float = 0.05) -> np.ndarray:
"""
Returns the representation of the whole slide image at a given micro-per-pixel (mpp) resolution.
The optional tolerance parameters are considered at the level whose mpp value is closest to the one provided by the user.
If the user-provided mpp is larger than the mpp of the closest level,
the image is downscaled to a resolution that matches the user-provided mpp.
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.
The image from this level is then down-scaled to achieve a resolution at the user-provided mpp value.

Args:
wsi: whole slide image object from WSIReader
mpp: the resolution in micron per pixel at which the representation of the whole slide image should be extracted.
atol: the acceptable absolute tolerance for resolution in micro per pixel.
rtol: the acceptable relative tolerance for resolution in micro per pixel.

Returns:
Numpy array containing the whole slide image at the requested MPP resolution.

"""

mpp_list = [self.get_mpp(wsi, lvl) for lvl in range(len(wsi.pages))] # Fails for some Tifffiles
closest_lvl = self._find_closest_level("mpp", mpp, mpp_list, 0, 5)

within_tolerance, closest_level_is_bigger = self._compute_mpp_tolerances(closest_lvl, mpp_list, mpp, atol, rtol)

if within_tolerance:
# If the image at the desired mpp resolution is within tolerances, return the image at closest_level.
closest_lvl_wsi = wsi.read_region((0, 0), level=closest_lvl, size=self.get_size(wsi, closest_lvl))

elif closest_level_is_bigger:
# Otherwise, select the level closest to the desired mpp with a higher resolution and downsample it.
closest_lvl_wsi = self._resize_to_mpp_res(wsi, closest_lvl, mpp_list, mpp)

else:
# If both checks fail, increase resolution (i.e., decrement level) and then downsample it.
if closest_lvl == 0:
closest_lvl_wsi = self._resize_to_mpp_res(wsi, closest_lvl, mpp_list, mpp)
else:
closest_lvl = closest_lvl - 1
closest_lvl_wsi = self._resize_to_mpp_res(wsi, closest_lvl, mpp_list, mpp)

raise ValueError("`mpp` cannot be obtained for this file. Please use `level` instead.")
wsi_arr = np.array(closest_lvl_wsi)
return wsi_arr

def get_power(self, wsi, level: int) -> float:
"""
Expand Down Expand Up @@ -1154,7 +1440,7 @@ def _get_patch(
Extracts and returns a patch image form the whole slide image.

Args:
wsi: a whole slide image object loaded from a file or a lis of such objects
wsi: a whole slide image object loaded from a file or a list of such objects
location: (top, left) tuple giving the top left pixel in the level 0 reference frame. Defaults to (0, 0).
size: (height, width) tuple giving the patch size at the given level (`level`).
If None, it is set to the full image size at the given level.
Expand Down Expand Up @@ -1186,3 +1472,28 @@ def _get_patch(
patch = np.take(patch, [0, 1, 2], self.channel_dim)

return patch

def _resize_to_mpp_res(self, wsi, closest_lvl, mpp_list, user_mpp: tuple):
"""
Resizes the whole slide image to the specified resolution in microns per pixel (mpp).

Args:
wsi: whole slide image object from WSIReader
user_mpp: the resolution in microns per pixel at which the whole slide image representation should be extracted.
closest_lvl: the wsi level that is closest to the user-provided mpp resolution.
mpp_list: list of mpp values for all levels of a whole slide image.

Returns:
PIL Image object resized to the target MPP resolution.

"""
pil_image, _ = optional_import("PIL", name="Image")

closest_lvl_dim = self.get_size(wsi, closest_lvl)

target_res_x, target_res_y = self._compute_mpp_target_res(closest_lvl, closest_lvl_dim, mpp_list, user_mpp)

closest_lvl_wsi = pil_image.fromarray(wsi.pages[closest_lvl].asarray())
closest_lvl_wsi = closest_lvl_wsi.resize((target_res_x, target_res_y), pil_image.BILINEAR)

return closest_lvl_wsi
1 change: 0 additions & 1 deletion monai/transforms/regularization/array.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@


class Mixer(RandomizableTransform):

def __init__(self, batch_size: int, alpha: float = 1.0) -> None:
"""
Mixer is a base class providing the basic logic for the mixup-class of
Expand Down
Loading
Loading