Skip to content

Commit

Permalink
WSIReader read by power and mpp (#6244)
Browse files Browse the repository at this point in the history
Fixes #6289  

### Description

This PR enable WSIReader to be provided with objective power and
resolution (micron per pixel) to decide which WSI level to load. It also
set absolute and relative tolerances for power and mpp.

### 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).
- [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: Behrooz <[email protected]>
  • Loading branch information
drbeh authored Apr 11, 2023
1 parent 9ef42ff commit 0a29bc1
Show file tree
Hide file tree
Showing 7 changed files with 729 additions and 259 deletions.
644 changes: 431 additions & 213 deletions monai/data/wsi_reader.py

Large diffs are not rendered by default.

77 changes: 77 additions & 0 deletions monai/utils/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from ast import literal_eval
from collections.abc import Callable, Iterable, Sequence
from distutils.util import strtobool
from math import log10
from pathlib import Path
from typing import TYPE_CHECKING, Any, TypeVar, cast, overload

Expand Down Expand Up @@ -68,6 +69,9 @@
"label_union",
"path_to_uri",
"pprint_edges",
"check_key_duplicates",
"CheckKeyDuplicatesYamlLoader",
"ConvertUnits",
]

_seed = None
Expand Down Expand Up @@ -723,3 +727,76 @@ def construct_mapping(self, node, deep=False):
warnings.warn(f"Duplicate key: `{key}`")
mapping.add(key)
return super().construct_mapping(node, deep)


class ConvertUnits:
"""
Convert the values from input unit to the target unit
Args:
input_unit: the unit of the input quantity
target_unit: the unit of the target quantity
"""

imperial_unit_of_length = {"inch": 0.0254, "foot": 0.3048, "yard": 0.9144, "mile": 1609.344}

unit_prefix = {
"peta": 15,
"tera": 12,
"giga": 9,
"mega": 6,
"kilo": 3,
"hecto": 2,
"deca": 1,
"deci": -1,
"centi": -2,
"milli": -3,
"micro": -6,
"nano": -9,
"pico": -12,
"femto": -15,
}
base_units = ["meter", "byte", "bit"]

def __init__(self, input_unit: str, target_unit: str) -> None:
self.input_unit, input_base = self._get_valid_unit_and_base(input_unit)
self.target_unit, target_base = self._get_valid_unit_and_base(target_unit)
if input_base == target_base:
self.unit_base = input_base
else:
raise ValueError(
"Both input and target units should be from the same quantity. "
f"Input quantity is {input_base} while target quantity is {target_base}"
)
self._calculate_conversion_factor()

def _get_valid_unit_and_base(self, unit):
unit = str(unit).lower()
if unit in self.imperial_unit_of_length:
return unit, "meter"
for base_unit in self.base_units:
if unit.endswith(base_unit):
return unit, base_unit
raise ValueError(f"Currently, it only supports length conversion but `{unit}` is given.")

def _get_unit_power(self, unit):
"""Calculate the power of the unit factor with respect to the base unit"""
if unit in self.imperial_unit_of_length:
return log10(self.imperial_unit_of_length[unit])

prefix = unit[: len(self.unit_base)]
if prefix == "":
return 1.0
return self.unit_prefix[prefix]

def _calculate_conversion_factor(self):
"""Calculate unit conversion factor with respect to the input unit"""
if self.input_unit == self.target_unit:
return 1.0
input_power = self._get_unit_power(self.input_unit)
target_power = self._get_unit_power(self.target_unit)
self.conversion_factor = 10 ** (input_power - target_power)

def __call__(self, value: int | float) -> Any:
return float(value) * self.conversion_factor
5 changes: 2 additions & 3 deletions tests/test_masked_patch_wsi_dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,9 @@
_, has_codec = optional_import("imagecodecs")
has_tiff = has_tiff and has_codec

FILE_KEY = "wsi_img"
FILE_KEY = "wsi_generic_tiff"
FILE_URL = testing_data_config("images", FILE_KEY, "url")
base_name, extension = os.path.basename(f"{FILE_URL}"), ".tiff"
FILE_PATH = os.path.join(os.path.dirname(__file__), "testing_data", "temp_" + base_name + extension)
FILE_PATH = os.path.join(os.path.dirname(__file__), "testing_data", f"temp_{FILE_KEY}.tiff")

TEST_CASE_0 = [
{"data": [{"image": FILE_PATH, WSIPatchKeys.LEVEL: 8, WSIPatchKeys.SIZE: (2, 2)}], "mask_level": 8},
Expand Down
5 changes: 2 additions & 3 deletions tests/test_patch_wsi_dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,9 @@
_, has_codec = optional_import("imagecodecs")
has_tiff = has_tiff and has_codec

FILE_KEY = "wsi_img"
FILE_KEY = "wsi_generic_tiff"
FILE_URL = testing_data_config("images", FILE_KEY, "url")
base_name, extension = os.path.basename(f"{FILE_URL}"), ".tiff"
FILE_PATH = os.path.join(os.path.dirname(__file__), "testing_data", "temp_" + base_name + extension)
FILE_PATH = os.path.join(os.path.dirname(__file__), "testing_data", f"temp_{FILE_KEY}.tiff")

TEST_CASE_0 = [
{
Expand Down
5 changes: 2 additions & 3 deletions tests/test_sliding_patch_wsi_dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,9 @@
_, has_codec = optional_import("imagecodecs")
has_tiff = has_tiff and has_codec

FILE_KEY = "wsi_img"
FILE_KEY = "wsi_generic_tiff"
FILE_URL = testing_data_config("images", FILE_KEY, "url")
base_name, extension = os.path.basename(f"{FILE_URL}"), ".tiff"
FILE_PATH = os.path.join(os.path.dirname(__file__), "testing_data", "temp_" + base_name + extension)
FILE_PATH = os.path.join(os.path.dirname(__file__), "testing_data", f"temp_{FILE_KEY}.tiff")

FILE_PATH_SMALL_0 = os.path.join(os.path.dirname(__file__), "testing_data", "temp_wsi_inference_0.tiff")
FILE_PATH_SMALL_1 = os.path.join(os.path.dirname(__file__), "testing_data", "temp_wsi_inference_1.tiff")
Expand Down
Loading

0 comments on commit 0a29bc1

Please sign in to comment.