Skip to content

Conversation

@NikolasSchmitz
Copy link

@NikolasSchmitz NikolasSchmitz commented Mar 25, 2024

Fixes: #4980

Description

In this pull request, the feature to retrieve a whole slide image at a given mpp (microns per pixel) resolution was implemented for every WSIReader class in the function get_wsi_at_mpp.

While the implementations in the OpenslideWSIReader and CuCIMWSIReader classes were tested thoroughly, I could not find a suitable TIFF file for testing with the TiffFileWSIReader class.

For resizing, I have used PIL.Image.resize for Openslide and TiffFile, and cucim.sklearn.transform.resize for CuCIM. Originally, I used cv2.resize, but since the package isn't listed in requirements-dev.txt, I explored alternative solutions."

Types of changes

  • Non-breaking change (fix or new feature that would not break existing functionality).
  • Breaking change (fix or new feature that would cause existing functionality to change).
  • New tests added to cover the changes.
  • Integration tests passed locally by running ./runtests.sh -f -u --net --coverage.
  • Quick tests passed locally by running ./runtests.sh --quick --unittests --disttests.
  • In-line docstrings updated.
  • Documentation updated, tested make html command in the docs/ folder.

Please let me know what you think and how I can improve this feature.

Best
Niko

(Last PR was closed, because I changed the branch name to include the ticket id)

cxlcl and others added 6 commits March 27, 2024 01:47
…roject-MONAI#7308)

### Description

Based on the discussion topic
[here](Project-MONAI#7161 (comment)),
we implemented the Conjugate-Gradient algorithm for linear operator
inversion, and Stein's Unbiased Risk Estimator (SURE) [1] loss for
ground-truth-date free diffusion process guidance that is proposed in
[2] and illustrated in the algorithm below:

<img width="650" alt="Screenshot 2023-12-10 at 10 19 25 PM"
src="https://github.com/Project-MONAI/MONAI/assets/8581162/97069466-cbaf-44e0-b7a7-ae9deb8fd7f2">

The Conjugate-Gradient (CG) algorithm is used to solve for the inversion
of the linear operator in Line-4 in the algorithm above, where the
linear operator is too large to store explicitly as a matrix (such as
FFT/IFFT of an image) and invert directly. Instead, we can solve for the
linear inversion iteratively as in CG.

The SURE loss is applied for Line-6 above. This is a differentiable loss
function that can be used to train/giude an operator (e.g. neural
network), where the pseudo ground truth is available but the reference
ground truth is not. For example, in the MRI reconstruction, the pseudo
ground truth is the zero-filled reconstruction and the reference ground
truth is the fully sampled reconstruction. The reference ground truth is
not available due to the lack of fully sampled.

**Reference**
[1] Stein, C.M.: Estimation of the mean of a multivariate normal
distribution. Annals of Statistics 1981 [[paper
link](https://projecteuclid.org/journals/annals-of-statistics/volume-9/issue-6/Estimation-of-the-Mean-of-a-Multivariate-Normal-Distribution/10.1214/aos/1176345632.full)]
[2] B. Ozturkler et al. SMRD: SURE-based Robust MRI Reconstruction with
Diffusion Models. MICCAI 2023
[[paper link](https://arxiv.org/pdf/2310.01799.pdf)]

### Types of changes
<!--- Put an `x` in all the boxes that apply, and remove the not
applicable items -->
- [x] Non-breaking change (fix or new feature that would not break
existing functionality).
- [ ] Breaking change (fix or new feature that would cause existing
functionality to change).
- [x] New tests added to cover the changes.
- [x] Integration tests passed locally by running `./runtests.sh -f -u
--net --coverage`.
- [x] Quick tests passed locally by running `./runtests.sh --quick
--unittests --disttests`.
- [x] In-line docstrings updated.
- [x] Documentation updated, tested `make html` command in the `docs/`
folder.

---------

Signed-off-by: chaoliu <[email protected]>
Signed-off-by: cxlcl <[email protected]>
Signed-off-by: chaoliu <[email protected]>
Signed-off-by: YunLiu <[email protected]>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: YunLiu <[email protected]>
Co-authored-by: Eric Kerfoot <[email protected]>
Signed-off-by: Nikolas Schmitz <[email protected]>
Signed-off-by: Nikolas Schmitz <[email protected]>
Signed-off-by: Nikolas Schmitz <[email protected]>
Signed-off-by: monai-bot <[email protected]>

Signed-off-by: monai-bot <[email protected]>
Signed-off-by: Nikolas Schmitz <[email protected]>
Signed-off-by: Nikolas Schmitz <[email protected]>
NikolasSchmitz and others added 3 commits March 27, 2024 02:04
Signed-off-by: Nikolas Schmitz <[email protected]>
…ject-MONAI#7569)

Fixes Project-MONAI#7451

### Description
Reduces the length of error messages and error messages being propagated
twice. This helps debug better when long `ConfigComponent`s are being
instantiated. Refer to issue Project-MONAI#7451 for more details

### Types of changes
<!--- Put an `x` in all the boxes that apply, and remove the not
applicable items -->
- [x] Non-breaking change (fix or new feature that would not break
existing functionality).
- [ ] Breaking change (fix or new feature that would cause existing
functionality to change).
- [ ] New tests added to cover the changes.
- [ ] Integration tests passed locally by running `./runtests.sh -f -u
--net --coverage`.
- [x] Quick tests passed locally by running `./runtests.sh --quick
--unittests --disttests`.
- [ ] In-line docstrings updated.
- [ ] Documentation updated, tested `make html` command in the `docs/`
folder.

Signed-off-by: Suraj Pai <[email protected]>
Co-authored-by: Eric Kerfoot <[email protected]>
Fixes Project-MONAI#2872 

### Description

Implementation of mixup, cutmix and cutout as described in the original
papers.
Current implementation support both, the dictionary-based batches and
tuples of tensors.

### Types of changes
<!--- Put an `x` in all the boxes that apply, and remove the not
applicable items -->
- [x] Non-breaking change (fix or new feature that would not break
existing functionality).
- [ ] Breaking change (fix or new feature that would cause existing
functionality to change).
- [x] New tests added to cover the changes.
- [ ] Integration tests passed locally by running `./runtests.sh -f -u
--net --coverage`.
- [x] Quick tests passed locally by running `./runtests.sh --quick
--unittests --disttests`.
- [x] In-line docstrings updated.
- [x] Documentation updated, tested `make html` command in the `docs/`
folder.

---------

Signed-off-by: Juan Pablo de la Cruz Gutiérrez <[email protected]>
Signed-off-by: monai-bot <[email protected]>
Signed-off-by: elitap <[email protected]>
Signed-off-by: Felix Schnabel <[email protected]>
Signed-off-by: YanxuanLiu <[email protected]>
Signed-off-by: ytl0623 <[email protected]>
Signed-off-by: Dženan Zukić <[email protected]>
Signed-off-by: KumoLiu <[email protected]>
Signed-off-by: YunLiu <[email protected]>
Signed-off-by: Ishan Dutta <[email protected]>
Signed-off-by: dependabot[bot] <[email protected]>
Signed-off-by: kaibo <[email protected]>
Signed-off-by: heyufan1995 <[email protected]>
Signed-off-by: binliu <[email protected]>
Signed-off-by: axel.vlaminck <[email protected]>
Signed-off-by: Ibrahim Hadzic <[email protected]>
Signed-off-by: Behrooz <[email protected]>
Signed-off-by: Timothy Baker <[email protected]>
Signed-off-by: Mathijs de Boer <[email protected]>
Signed-off-by: Fabian Klopfer <[email protected]>
Signed-off-by: Lucas Robinet <[email protected]>
Signed-off-by: Lucas Robinet <[email protected]>
Signed-off-by: chaoliu <[email protected]>
Signed-off-by: cxlcl <[email protected]>
Signed-off-by: chaoliu <[email protected]>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: monai-bot <[email protected]>
Co-authored-by: elitap <[email protected]>
Co-authored-by: Felix Schnabel <[email protected]>
Co-authored-by: YanxuanLiu <[email protected]>
Co-authored-by: ytl0623 <[email protected]>
Co-authored-by: Dženan Zukić <[email protected]>
Co-authored-by: Eric Kerfoot <[email protected]>
Co-authored-by: YunLiu <[email protected]>
Co-authored-by: Ishan Dutta <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Kaibo Tang <[email protected]>
Co-authored-by: Yufan He <[email protected]>
Co-authored-by: binliunls <[email protected]>
Co-authored-by: Ben Murray <[email protected]>
Co-authored-by: axel.vlaminck <[email protected]>
Co-authored-by: Mingxin Zheng <[email protected]>
Co-authored-by: Ibrahim Hadzic <[email protected]>
Co-authored-by: Dr. Behrooz Hashemian <[email protected]>
Co-authored-by: Timothy J. Baker <[email protected]>
Co-authored-by: Mathijs de Boer <[email protected]>
Co-authored-by: Mathijs de Boer <[email protected]>
Co-authored-by: Fabian Klopfer <[email protected]>
Co-authored-by: Yiheng Wang <[email protected]>
Co-authored-by: Lucas Robinet <[email protected]>
Co-authored-by: Lucas Robinet <[email protected]>
Co-authored-by: cxlcl <[email protected]>
Signed-off-by: Dr. Behrooz Hashemian <[email protected]>
@bhashemian
Copy link
Member

@NikolasSchmitz, I resolved the conflicts and updated the PR. Let's focus on the functionality and make this PR ready for reviewing. We can take care of any other issue once the PR is ready and please feel free to reach out to me or the working group if you still have any questions. Thanks for taking on this feature.

@NikolasSchmitz NikolasSchmitz marked this pull request as ready for review April 9, 2024 22:55
@NikolasSchmitz
Copy link
Author

Thank you @drbeh ! I now marked it as ready for review.

@ericspod ericspod requested a review from bhashemian April 22, 2024 14:53
@ericspod
Copy link
Member

@drbeh would you be able to review this? I think you're best qualified here. I made some minor comments about print and code duplication but otherwise it seems fine to me without having tested it. Thanks!

@JHancox JHancox self-assigned this May 14, 2024
Copy link
Contributor

@JHancox JHancox left a comment

Choose a reason for hiding this comment

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

Thanks for the effort @NikolasSchmitz. A welcome addition! I agree with @ericspod about the refactoring to remove duplication - that would be great.

@NikolasSchmitz
Copy link
Author

Thank you for the feedback, @JHancox . Unfortunately, I couldn't join the meeting because I was on my way home from ICLR.
I will review the code and provide my updates as soon as possible.

… reduce redundancy; for get_mpp of TiffFileWSIReader: added check to prevent division by zero error.
@NikolasSchmitz
Copy link
Author

Hi all, the feature and a few associated tests have been implemented. I ran into an issue with the resizing functions, as they expected different channel orders for the target resolution. That’s been resolved.
The code now passes all ruff checks. However, a few commits are still missing sign-offs. What’s the recommended way to handle that at this point?

I’ve also uploaded the updated TIFF file here: https://gigamove.rwth-aachen.de/en/download/722a2879de053fd9a0ddd7f6e4874624

@ericspod
Copy link
Member

Hi @NikolasSchmitz thanks for getting back to this. There's a few tests which still aren't passing, a few say just that a timeout occurred so it might be internal but the code formatting test did find something. As for DCO, clicking on that brings up advice on how to resolve, specifically doing an empty remedial commit. If that doesn't work we will set the DCO to pass just before merging. Please have a look at the tests and see if we can get that through.

@ericspod
Copy link
Member

We also are putting more test data on Huggingface now so you can propose to upload your image to here https://huggingface.co/datasets/MONAI/testing_data if you like. We're planning to adjust our tests to pull from this dataset.

@ericspod
Copy link
Member

Your new tiff file is added to the hf dataset now, so you need to change setUpModule in test_wsireader.py to be:

@skipUnless(has_cucim or has_osl or has_tiff, "Requires cucim, openslide, or tifffile!")
def setUpModule():
    download_url_or_skip_test(
        testing_data_config("images", WSI_GENERIC_TIFF_KEY, "url"),
        WSI_GENERIC_TIFF_PATH,
        hash_type=testing_data_config("images", WSI_GENERIC_TIFF_KEY, "hash_type"),
        hash_val=testing_data_config("images", WSI_GENERIC_TIFF_KEY, "hash_val"),
    )
    download_url_or_skip_test(
        testing_data_config("images", WSI_GENERIC_TIFF_CORRECT_MPP_KEY, "url"),
        WSI_GENERIC_TIFF_CORRECT_MPP_PATH,
        hash_type=testing_data_config("images", WSI_GENERIC_TIFF_CORRECT_MPP_KEY, "hash_type"),
        hash_val=testing_data_config("images", WSI_GENERIC_TIFF_CORRECT_MPP_KEY, "hash_val"),
    )
    download_url_or_skip_test(
        testing_data_config("images", WSI_APERIO_SVS_KEY, "url"),
        WSI_APERIO_SVS_PATH,
        hash_type=testing_data_config("images", WSI_APERIO_SVS_KEY, "hash_type"),
        hash_val=testing_data_config("images", WSI_APERIO_SVS_KEY, "hash_val"),
    )

Add the following to tests/testing_data/data_config.json just after the first item in the images section:

        "wsi_generic_tiff_corrected": {
            "url": "https://huggingface.co/datasets/MONAI/testing_data/resolve/main/CMU-1_correct_mpp.tiff",
            "hash_type": "sha256",
            "hash_val": "65306e3f8f7f5282d19d942dadc525cd06a80d5fd8268053939751365226c65f"
        },

As you can see, our process for downloading data needs to be revamped to improved this process! Thanks.

NikolasSchmitz and others added 4 commits July 25, 2025 19:07
…en.de>

I, Nikolas Schmitz <[email protected]>, hereby add my Signed-off-by to this commit: d0a4881
I, Nikolas Schmitz <[email protected]>, hereby add my Signed-off-by to this commit: 7545148
I, Nikolas Schmitz <[email protected]>, hereby add my Signed-off-by to this commit: 5c7822f
I, Nikolas Schmitz <[email protected]>, hereby add my Signed-off-by to this commit: 234f23f
I, Nikolas Schmitz <[email protected]>, hereby add my Signed-off-by to this commit: 9d817e7
I, Nikolas Schmitz <[email protected]>, hereby add my Signed-off-by to this commit: 8270658
I, Nikolas Schmitz <[email protected]>, hereby add my Signed-off-by to this commit: 23e4a74
I, Nikolas Schmitz <[email protected]>, hereby add my Signed-off-by to this commit: 787d30f
I, Nikolas Schmitz <[email protected]>, hereby add my Signed-off-by to this commit: 0d9f1dd
I, Nikolas Schmitz <[email protected]>, hereby add my Signed-off-by to this commit: 45182fa
I, Nikolas Schmitz <[email protected]>, hereby add my Signed-off-by to this commit: 832c14e

Signed-off-by: Nikolas Schmitz <[email protected]>
@bhashemian
Copy link
Member

@NikolasSchmitz thanks for your contribution. Please let me know if you need help fixing the DCO issues.

@bhashemian
Copy link
Member

@coderabbitai full review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 21, 2025

✅ Actions performed

Full review triggered.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (1)
monai/data/wsi_reader.py (1)

663-681: Validate mpp input early.

Reject non-tuples, wrong length, or non-positive values to avoid undefined behavior downstream.

 def get_wsi_at_mpp(self, wsi, mpp: tuple, atol: float = 0.00, rtol: float = 0.05) -> np.ndarray:
@@
-        return self.reader.get_wsi_at_mpp(wsi, mpp, atol, rtol)
+        # basic validation
+        if not isinstance(mpp, tuple) or len(mpp) != 2:
+            raise ValueError("mpp must be a tuple (x, y).")
+        if any((not isinstance(v, (int, float))) or v <= 0 for v in mpp):
+            raise ValueError("mpp values must be positive numbers.")
+        return self.reader.get_wsi_at_mpp(wsi, mpp, atol, rtol)
🧹 Nitpick comments (8)
tests/utils/enums/test_wsireader.py (2)

479-505: Strengthen assertions for cross-backend robustness.

Exact H×W can vary a few pixels due to rounding/interp differences. Also assert:

  • dtype and channel count per backend,
  • monotonicity of area vs requested MPP,
  • that returned MPP (via metadata or helper) is within tolerance.

Consider computing expected size from level-0 dims and requested mpp rather than hard-coding tuples to reduce brittleness.


467-478: Remove commented-out test or re-enable behind a marker.

Dead/commented code adds noise. Delete or gate with a feature flag/xfail.

monai/data/wsi_reader.py (5)

843-846: Don’t pass hard-coded tolerances to _find_closest_level; avoid unintended behavior.

The 0,5 literals silently decouple user-provided tolerances. Either:

  • use the provided (atol, rtol), or
  • bypass tolerance checking entirely when only the index of the closest level is needed.

Apply one of the following:

Option A (simple):

-        closest_lvl = self._find_closest_level("mpp", mpp, mpp_list, 0, 5)
+        closest_lvl = self._find_closest_level("mpp", mpp, mpp_list, atol, rtol)

Option B (preferred: no early raising):

-        closest_lvl = self._find_closest_level("mpp", mpp, mpp_list, 0, 5)
+        # Pick closest level without tolerance checks; actual tolerances are evaluated below.
+        closest_lvl = min(range(len(mpp_list)),
+                          key=lambda i: abs(mpp_list[i][0]-mpp[0]) + abs(mpp_list[i][1]-mpp[1]))

Also applies to: 1108-1111, 1345-1348


949-975: Normalize dimension handling to reduce fragility.

closest_lvl_dim is backend-native (often (width, height)); _compute_mpp_target_res expects (height, width), leading to implicit swaps. Unify by passing self.get_size(wsi, closest_lvl) to _compute_mpp_target_res, and use explicit width/height when calling backend resizers.

Example for CuCIM (apply similarly to OpenSlide/TiffFile as appropriate):

-        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))
+        # Consistent dims: (h, w)
+        h, w = self.get_size(wsi, closest_lvl)
+        target_w, target_h = self._compute_mpp_target_res(closest_lvl, (h, w), mpp_list, user_mpp)
+        # cuCIM read_region expects (w, h)
+        wsi_arr = cp.array(wsi.read_region((0, 0), level=closest_lvl, size=(w, h), num_workers=self.num_workers))
-        closest_lvl_wsi = cucim_resize(
-            wsi_arr,
-            (target_res_x, target_res_y),
+        # cucim.resize expects (rows, cols) == (h, w)
+        closest_lvl_wsi = cucim_resize(
+            wsi_arr,
+            (target_h, target_w),
             order=1,
             preserve_range=True,
             anti_aliasing=False).astype(cp.uint8)

For OpenSlide’s PIL.resize, pass (target_w, target_h).

Also applies to: 1200-1221, 1442-1462


1314-1324: Normalize ResolutionUnit and tighten error handling.

Lowercase the unit to match ConvertUnits expectations; use shorter messages (TRY003).

-            convert_to_micron = ConvertUnits(unit, "micrometer")
+            convert_to_micron = ConvertUnits(str(unit).lower(), "micrometer")
@@
-            if xres[0] and yres[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("Invalid TIFF X/YResolution (zero). Cannot compute mpp; use `level` instead.")

960-974: Consider anti-aliasing when downscaling.

Downscaling without anti_aliasing can introduce artifacts. Enable it when target size is smaller than source.

-        closest_lvl_wsi = cucim_resize(
+        downscale = (target_h < h) and (target_w < w)
+        closest_lvl_wsi = cucim_resize(
             wsi_arr,
             (target_h, target_w),
             order=1,
             preserve_range=True,
-            anti_aliasing=False).astype(cp.uint8)
+            anti_aliasing=downscale).astype(cp.uint8)

1099-1105: Docstring: clarify tolerance interplay and behavior on miss.

Explicitly state: closest level is selected ignoring tolerances; tolerances only decide “return as-is vs resize”. Document behavior when requested MPP is finer than level 0 (error vs upsample).

Also applies to: 1336-1342, 833-839

tests/testing_data/data_config.json (1)

8-12: Consider pinning to a specific commit for reproducibility.

Hash verified ✓ and artifact accessible. However, resolve/main remains mutable; prefer pinning to a commit hash (e.g., resolve/abc1234) for CI stability. The SHA256 guard mitigates risk but doesn't prevent future mutations.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between 69f3dd2 and 1c1ecd0.

📒 Files selected for processing (4)
  • monai/data/wsi_reader.py (9 hunks)
  • monai/transforms/regularization/array.py (0 hunks)
  • tests/testing_data/data_config.json (1 hunks)
  • tests/utils/enums/test_wsireader.py (4 hunks)
💤 Files with no reviewable changes (1)
  • monai/transforms/regularization/array.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

⚙️ CodeRabbit configuration file

Review the Python code for quality and correctness. Ensure variable names adhere to PEP8 style guides, are sensible and informative in regards to their function, though permitting simple names for loop and comprehension variables. Ensure routine names are meaningful in regards to their function and use verbs, adjectives, and nouns in a semantically appropriate way. Docstrings should be present for all definition which describe each variable, return value, and raised exception in the appropriate section of the Google-style of docstrings. Examine code for logical error or inconsistencies, and suggest what may be changed to addressed these. Suggest any enhancements for code improving efficiency, maintainability, comprehensibility, and correctness. Ensure new or modified definitions will be covered by existing or new unit tests.

Files:

  • monai/data/wsi_reader.py
  • tests/utils/enums/test_wsireader.py
🪛 Ruff (0.14.1)
monai/data/wsi_reader.py

1322-1323: Avoid specifying long messages outside the exception class

(TRY003)


1324-1324: Avoid specifying long messages outside the exception class

(TRY003)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: quick-py3 (windows-latest)
  • GitHub Check: quick-py3 (macOS-latest)
  • GitHub Check: packaging
  • GitHub Check: quick-py3 (ubuntu-latest)

Comment on lines +470 to +489
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) & (user_mpp_x <= upper_bound_x)
is_within_tolerance_y = (user_mpp_y >= lower_bound_y) & (user_mpp_y <= upper_bound_y)
is_within_tolerance = is_within_tolerance_x & 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 & closest_level_is_bigger_y

return is_within_tolerance, closest_level_is_bigger
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 | 🟡 Minor

Use logical operators for booleans; avoid bitwise semantics.

Replace & with and for clarity and to prevent accidental array-wise ops if types change.

-        is_within_tolerance_x = (user_mpp_x >= lower_bound_x) & (user_mpp_x <= upper_bound_x)
-        is_within_tolerance_y = (user_mpp_y >= lower_bound_y) & (user_mpp_y <= upper_bound_y)
-        is_within_tolerance = is_within_tolerance_x & is_within_tolerance_y
+        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
@@
-        closest_level_is_bigger = closest_level_is_bigger_x & closest_level_is_bigger_y
+        closest_level_is_bigger = closest_level_is_bigger_x and closest_level_is_bigger_y
🤖 Prompt for AI Agents
In monai/data/wsi_reader.py around lines 470 to 489, boolean checks currently
use bitwise operators (&) which should be replaced with logical operators (and)
to avoid array-wise semantics if types change; update is_within_tolerance_x,
is_within_tolerance_y, is_within_tolerance, closest_level_is_bigger_x,
closest_level_is_bigger_y, and closest_level_is_bigger to use "and" between the
boolean comparisons (keeping parentheses as needed for precedence) and return
the updated booleans.

Comment on lines +857 to +861
else:
# If both checks fail, increase resolution (i.e., decrement level) and then downsample it.
closest_lvl = closest_lvl - 1
closest_lvl_wsi = self._resize_to_mpp_res(wsi, closest_lvl, mpp_list, mpp)

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 | 🔴 Critical

Guard against level underflow when increasing resolution.

closest_lvl - 1 can become -1 (wraps to last level), returning the coarsest image by mistake.

Apply per-backend fix (example OpenSlide shown; replicate in CuCIM/TiffFile):

-        else:
-            # If both checks fail, increase resolution (i.e., decrement level) and then downsample it.
-            closest_lvl = closest_lvl - 1
-            closest_lvl_wsi = self._resize_to_mpp_res(wsi, closest_lvl, mpp_list, mpp)
+        else:
+            # Need a higher-resolution source. Use next finer level if available; otherwise use level 0.
+            if closest_lvl == 0:
+                # No finer level exists; either upsample level 0 or raise a clear error.
+                raise ValueError("Requested MPP is finer than level 0; cannot satisfy without upsampling.")
+            next_finer = closest_lvl - 1
+            closest_lvl_wsi = self._resize_to_mpp_res(wsi, next_finer, mpp_list, mpp)

If upsampling from level 0 is acceptable, replace the raise with resizing from level 0.

Also applies to: 1122-1126, 1357-1361

🤖 Prompt for AI Agents
In monai/data/wsi_reader.py around lines 857-861 (and similarly at 1122-1126 and
1357-1361), decrementing closest_lvl with "closest_lvl - 1" can underflow to -1
and wrap to the last level; change the logic to clamp the new level to a minimum
of 0 before using it (i.e., compute new_lvl = max(0, closest_lvl - 1)) and use
that level for _resize_to_mpp_res so you never accidentally select the coarsest
image; apply equivalent per-backend fixes for OpenSlide, CuCIM and TiffFile code
paths, and if upsampling from level 0 is acceptable in your design, replace any
raised error when trying to go above level 0 with resizing from level 0 instead
of raising.

Comment on lines +262 to +309
TEST_CASE_SVS_MPP_1 = [
WSI_APERIO_SVS_PATH,
{"mpp": (4.0, 4.0), "atol": 0.0, "rtol": 0.1},
{"openslide": (4106, 5739, 4), "cucim": (4106, 5739, 3)},
]

TEST_CASE_SVS_MPP_2 = [
WSI_APERIO_SVS_PATH,
{"mpp": (8.0, 8.0)},
{"openslide": (2057, 2875, 4), "cucim": (2057, 2875, 3)},
]

TEST_CASE_SVS_MPP_3 = [
WSI_APERIO_SVS_PATH,
{"mpp": (3.0, 3.0)},
{"openslide": (5475, 7652, 4), "cucim": (5475, 7652, 3)},
]

TEST_CASE_SVS_MPP_4 = [
WSI_APERIO_SVS_PATH,
{"mpp": (1.5, 1.5)},
{"openslide": (10949, 15303, 4), "cucim": (10949, 15303, 3)},
]

TEST_CASE_TIFF_MPP_1 = [
WSI_GENERIC_TIFF_CORRECT_MPP_PATH,
{"mpp": (4.0, 4.0), "atol": 0.0, "rtol": 0.1},
{"openslide": (4114, 5750, 4), "cucim": (4114, 5750, 3), "tifffile": (4106, 5739, 3)},
]

TEST_CASE_TIFF_MPP_2 = [
WSI_GENERIC_TIFF_CORRECT_MPP_PATH,
{"mpp": (8.0, 8.0)},
{"openslide": (2057, 2875, 4), "cucim": (2057, 2875, 3), "tifffile": (2053, 2869, 3)},
]

TEST_CASE_TIFF_MPP_3 = [
WSI_GENERIC_TIFF_CORRECT_MPP_PATH,
{"mpp": (3.0, 3.0)},
{"openslide": (5475, 7652, 4), "cucim": (5475, 7652, 3), "tifffile": (5475, 7651, 3)},
]

TEST_CASE_TIFF_MPP_4 = [
WSI_GENERIC_TIFF_CORRECT_MPP_PATH,
{"mpp": (1.5, 1.5)},
{"openslide": (10949, 15303, 4), "cucim": (10949, 15303, 3), "tifffile": (10949, 15303, 3)},
]

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add a “too-fine MPP” test to catch boundary behavior.

Add a case where requested mpp is smaller than level-0 (i.e., finer than native). Current backend code decrements level and can underflow; a test will guard it.

I can add a parameterized TEST_CASE__MPP_TOO_FINE and expected exception once backend behavior is decided (error vs upsample).

Also applies to: 286-309

@bhashemian
Copy link
Member

@NikolasSchmitz please let me know if I can help to finalize and merge this PR. Thanks

@NikolasSchmitz
Copy link
Author

Hey Bruce, I will finalize this PR in the next days and will let you know whether I need help. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

WSIReader support for reading by mpp or magnification

8 participants