Skip to content

Commit fc6e02d

Browse files
authored
🐛 Fix Code Complexity in wsireader (#814)
Fix Code Complexity in `wsireader` - Fixes `PLR0912`, `C901` - Improves coverage to include cases with regular tiff files.
1 parent c3ae5d5 commit fc6e02d

File tree

4 files changed

+111
-31
lines changed

4 files changed

+111
-31
lines changed

tests/conftest.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,16 @@ def sample_ventana_tif(remote_sample: Callable) -> Path:
130130
return remote_sample("ventana-tif")
131131

132132

133+
@pytest.fixture(scope="session")
134+
def sample_regular_tif(remote_sample: Callable) -> Path:
135+
"""Sample pytest fixture for non-tiled tif Ventana images.
136+
137+
Download Ventana tif image for pytest.
138+
139+
"""
140+
return remote_sample("regular-tif")
141+
142+
133143
@pytest.fixture(scope="session")
134144
def sample_jp2(remote_sample: Callable) -> Path:
135145
"""Sample pytest fixture for JP2 images.

tests/test_wsireader.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1473,6 +1473,7 @@ def test_wsireader_open(
14731473
sample_jp2: Path,
14741474
sample_ome_tiff: Path,
14751475
sample_ventana_tif: Path,
1476+
sample_regular_tif: Path,
14761477
source_image: Path,
14771478
tmp_path: pytest.TempPathFactory,
14781479
) -> None:
@@ -1498,6 +1499,9 @@ def test_wsireader_open(
14981499
wsi = WSIReader.open(sample_ventana_tif)
14991500
assert isinstance(wsi, wsireader.OpenSlideWSIReader)
15001501

1502+
wsi = WSIReader.open(sample_regular_tif)
1503+
assert isinstance(wsi, wsireader.VirtualWSIReader)
1504+
15011505
wsi = WSIReader.open(Path(source_image))
15021506
assert isinstance(wsi, wsireader.VirtualWSIReader)
15031507

tiatoolbox/data/remote_samples.yaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@ files:
3030
two-tiled-pages:
3131
url: [*wsis, "two-tiled-pages.tiff"]
3232
ventana-tif:
33-
url: [*wsis, "ventana_sample.tif"]
33+
url: [*wsis, "ventana-sample.tif"]
34+
regular-tif:
35+
url: [*wsis, "sample-regular.tif"]
3436
jp2-omnyx-small:
3537
url: [*wsis, "CMU-1-Small-Region.omnyx.jp2"]
3638
jp2-omnyx-1:

tiatoolbox/wsicore/wsireader.py

Lines changed: 94 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,90 @@ def is_ngff( # noqa: PLR0911
205205
return is_zarr(path)
206206

207207

208+
def _handle_virtual_wsi(
209+
last_suffix: str,
210+
input_path: Path,
211+
mpp: tuple[Number, Number] | None,
212+
power: Number | None,
213+
) -> VirtualWSIReader | None:
214+
"""Handle virtual WSI cases.
215+
216+
Args:
217+
last_suffix (str):
218+
Suffix of the file to read.
219+
input_path (Path):
220+
Input path to virtual WSI.
221+
mpp (:obj:`tuple` or :obj:`list` or :obj:`None`, optional):
222+
The MPP of the WSI. If not provided, the MPP is approximated
223+
from the objective power.
224+
power (:obj:`float` or :obj:`None`, optional):
225+
The objective power of the WSI. If not provided, the power
226+
is approximated from the MPP.
227+
228+
Returns:
229+
VirtualWSIReader | None:
230+
:class:`VirtualWSIReader` if input_path is valid path to virtual WSI
231+
otherwise None.
232+
233+
"""
234+
235+
# Handle homogeneous cases (based on final suffix)
236+
def np_virtual_wsi(
237+
input_path: np.ndarray,
238+
*args: Number | tuple | str | WSIMeta | None,
239+
**kwargs: dict,
240+
) -> VirtualWSIReader:
241+
"""Create a virtual WSI from a numpy array."""
242+
return VirtualWSIReader(input_path, *args, **kwargs)
243+
244+
suffix_to_reader = {
245+
".npy": np_virtual_wsi,
246+
".jp2": JP2WSIReader,
247+
".jpeg": VirtualWSIReader,
248+
".jpg": VirtualWSIReader,
249+
".png": VirtualWSIReader,
250+
".tif": VirtualWSIReader,
251+
".tiff": VirtualWSIReader,
252+
}
253+
254+
if last_suffix in suffix_to_reader:
255+
return suffix_to_reader[last_suffix](input_path, mpp=mpp, power=power)
256+
257+
return None
258+
259+
260+
def _handle_tiff_wsi(
261+
input_path: Path, mpp: tuple[Number, Number] | None, power: Number | None
262+
) -> TIFFWSIReader | OpenSlideWSIReader | None:
263+
"""Handle TIFF WSI cases.
264+
265+
Args:
266+
input_path (Path):
267+
Input path to virtual WSI.
268+
mpp (:obj:`tuple` or :obj:`list` or :obj:`None`, optional):
269+
The MPP of the WSI. If not provided, the MPP is approximated
270+
from the objective power.
271+
power (:obj:`float` or :obj:`None`, optional):
272+
The objective power of the WSI. If not provided, the power
273+
is approximated from the MPP.
274+
275+
Returns:
276+
OpenSlideWSIReader | TIFFWSIReader | None:
277+
:class:`OpenSlideWSIReader` or :class:`TIFFWSIReader` if input_path is
278+
valid path to tiff WSI otherwise None.
279+
280+
"""
281+
if openslide.OpenSlide.detect_format(input_path) is not None:
282+
try:
283+
return OpenSlideWSIReader(input_path, mpp=mpp, power=power)
284+
except openslide.OpenSlideError:
285+
pass
286+
if is_tiled_tiff(input_path):
287+
return TIFFWSIReader(input_path, mpp=mpp, power=power)
288+
289+
return None
290+
291+
208292
class WSIReader:
209293
"""Base whole slide image (WSI) reader class.
210294
@@ -228,7 +312,7 @@ class WSIReader:
228312
"""
229313

230314
@staticmethod
231-
def open( # noqa: PLR0911, PLR0912, C901
315+
def open( # noqa: PLR0911
232316
input_img: str | Path | np.ndarray | WSIReader,
233317
mpp: tuple[Number, Number] | None = None,
234318
power: Number | None = None,
@@ -279,7 +363,6 @@ def open( # noqa: PLR0911, PLR0912, C901
279363
WSIReader.verify_supported_wsi(input_path)
280364

281365
# Handle special cases first (DICOM, Zarr/NGFF, OME-TIFF)
282-
283366
if is_dicom(input_path):
284367
return DICOMWSIReader(input_path, mpp=mpp, power=power)
285368

@@ -301,35 +384,16 @@ def open( # noqa: PLR0911, PLR0912, C901
301384
return TIFFWSIReader(input_path, mpp=mpp, power=power)
302385

303386
if last_suffix in (".tif", ".tiff"):
304-
if openslide.OpenSlide.detect_format(input_path) is not None:
305-
try:
306-
return OpenSlideWSIReader(input_path, mpp=mpp, power=power)
307-
except openslide.OpenSlideError:
308-
pass
309-
if is_tiled_tiff(input_path):
310-
return TIFFWSIReader(input_path, mpp=mpp, power=power)
311-
312-
# Handle homogeneous cases (based on final suffix)
313-
def np_virtual_wsi(
314-
input_path: np.ndarray,
315-
*args: Number | tuple | str | WSIMeta | None,
316-
**kwargs: dict,
317-
) -> VirtualWSIReader:
318-
"""Create a virtual WSI from a numpy array."""
319-
return VirtualWSIReader(input_path, *args, **kwargs)
320-
321-
suffix_to_reader = {
322-
".npy": np_virtual_wsi,
323-
".jp2": JP2WSIReader,
324-
".jpeg": VirtualWSIReader,
325-
".jpg": VirtualWSIReader,
326-
".png": VirtualWSIReader,
327-
".tif": VirtualWSIReader,
328-
".tiff": VirtualWSIReader,
329-
}
387+
tiff_wsi = _handle_tiff_wsi(input_path, mpp=mpp, power=power)
388+
if tiff_wsi is not None:
389+
return tiff_wsi
390+
391+
virtual_wsi = _handle_virtual_wsi(
392+
last_suffix=last_suffix, input_path=input_path, mpp=mpp, power=power
393+
)
330394

331-
if last_suffix in suffix_to_reader:
332-
return suffix_to_reader[last_suffix](input_path, mpp=mpp, power=power)
395+
if virtual_wsi is not None:
396+
return virtual_wsi
333397

334398
# Try openslide last
335399
return OpenSlideWSIReader(input_path, mpp=mpp, power=power)

0 commit comments

Comments
 (0)