Skip to content

Commit 82e9d8f

Browse files
vqdangAbdolmeastypre-commit-ci[bot]shaneahmed
authored
🐛 Fix TIFFWSIReader read_bound (#777)
![image](https://github.com/TissueImageAnalytics/tiatoolbox/assets/24943262/20c0fda0-19f6-46cb-97f8-9ab125d3b3be) Emergency bugfix per @John-P request. The culprit is reading bound doesn't use the adjusted bounds as have been done in OpenSlideReader. --------- Co-authored-by: Abdol <[email protected]> Co-authored-by: measty <[email protected]> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Shan E Ahmed Raza <[email protected]>
1 parent 6643e7a commit 82e9d8f

File tree

6 files changed

+64
-30
lines changed

6 files changed

+64
-30
lines changed

tests/conftest.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,17 @@ def sample_ome_tiff(remote_sample: Callable) -> Path:
117117
Download ome-tiff image for pytest.
118118
119119
"""
120-
return remote_sample("ome-brightfield-pyramid-1-small")
120+
return remote_sample("ome-brightfield-small-pyramid")
121+
122+
123+
@pytest.fixture(scope="session")
124+
def sample_ome_tiff_level_0(remote_sample: Callable) -> Path:
125+
"""Sample pytest fixture for ome-tiff image with one level.
126+
127+
Download ome-tiff image for pytest.
128+
129+
"""
130+
return remote_sample("ome-brightfield-small-level-0")
121131

122132

123133
@pytest.fixture(scope="session")

tests/test_tiffreader.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ def test_ome_missing_instrument_ref(
1313
remote_sample: Callable,
1414
) -> None:
1515
"""Test that an OME-TIFF can be read without instrument reference."""
16-
sample = remote_sample("ome-brightfield-pyramid-1-small")
16+
sample = remote_sample("ome-brightfield-small-level-0")
1717
wsi = wsireader.TIFFWSIReader(sample)
1818
page = wsi.tiff.pages[0]
1919
description = page.description
@@ -37,7 +37,7 @@ def test_ome_missing_physicalsize(
3737
remote_sample: Callable,
3838
) -> None:
3939
"""Test that an OME-TIFF can be read without physical size."""
40-
sample = remote_sample("ome-brightfield-pyramid-1-small")
40+
sample = remote_sample("ome-brightfield-small-level-0")
4141
wsi = wsireader.TIFFWSIReader(sample)
4242
page = wsi.tiff.pages[0]
4343
description = page.description
@@ -62,7 +62,7 @@ def test_ome_missing_physicalsizey(
6262
remote_sample: Callable,
6363
) -> None:
6464
"""Test that an OME-TIFF can be read without physical size."""
65-
sample = remote_sample("ome-brightfield-pyramid-1-small")
65+
sample = remote_sample("ome-brightfield-small-level-0")
6666
wsi = wsireader.TIFFWSIReader(sample)
6767
page = wsi.tiff.pages[0]
6868
description = page.description
@@ -86,7 +86,7 @@ def test_tiffreader_non_tiled_metadata(
8686
remote_sample: Callable,
8787
) -> None:
8888
"""Test that fetching metadata for non-tiled TIFF works."""
89-
sample = remote_sample("ome-brightfield-pyramid-1-small")
89+
sample = remote_sample("ome-brightfield-small-level-0")
9090
wsi = wsireader.TIFFWSIReader(sample)
9191
monkeypatch.setattr(wsi.tiff, "is_ome", False)
9292
monkeypatch.setattr(

tests/test_wsireader.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -959,15 +959,23 @@ def test_read_bounds_interpolated(sample_svs: Path) -> None:
959959

960960

961961
def test_read_bounds_level_consistency_openslide(sample_ndpi: Path) -> None:
962-
"""Test read_bounds produces the same visual field across resolution levels."""
962+
"""Test read_bounds produces the same visual field across resolution levels.
963+
964+
with OpenSlideWSIReader.
965+
966+
"""
963967
wsi = wsireader.OpenSlideWSIReader(sample_ndpi)
964968
bounds = NDPI_TEST_TISSUE_BOUNDS
965969

966970
read_bounds_level_consistency(wsi, bounds)
967971

968972

969973
def test_read_bounds_level_consistency_jp2(sample_jp2: Path) -> None:
970-
"""Test read_bounds produces the same visual field across resolution levels."""
974+
"""Test read_bounds produces the same visual field across resolution levels.
975+
976+
Using JP2WSIReader.
977+
978+
"""
971979
bounds = JP2_TEST_TISSUE_BOUNDS
972980
wsi = wsireader.JP2WSIReader(sample_jp2)
973981

@@ -1883,11 +1891,11 @@ def test_tiffwsireader_invalid_svs_metadata(
18831891

18841892

18851893
def test_tiffwsireader_invalid_ome_metadata(
1886-
sample_ome_tiff: Path,
1894+
sample_ome_tiff_level_0: Path,
18871895
monkeypatch: pytest.MonkeyPatch,
18881896
) -> None:
18891897
"""Test exception raised for invalid OME-XML metadata instrument."""
1890-
wsi = wsireader.TIFFWSIReader(sample_ome_tiff)
1898+
wsi = wsireader.TIFFWSIReader(sample_ome_tiff_level_0)
18911899
monkeypatch.setattr(
18921900
wsi.tiff.pages[0],
18931901
"description",
@@ -2545,7 +2553,7 @@ def test_jp2_no_header(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
25452553
},
25462554
{
25472555
"reader_class": TIFFWSIReader,
2548-
"sample_key": "ome-brightfield-pyramid-1-small",
2556+
"sample_key": "ome-brightfield-small-pyramid",
25492557
"kwargs": {},
25502558
},
25512559
{

tiatoolbox/data/remote_samples.yaml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,10 @@ files:
2525
url: [*wsis, "CMU-1-Small-Region.jpeg.tiff"]
2626
tiled-tiff-1-small-jp2k:
2727
url: [*wsis, "CMU-1-Small-Region.jp2k.tiff"]
28-
ome-brightfield-pyramid-1-small:
29-
url: [*wsis, "CMU-1-Small-Region.ome.tiff"]
28+
ome-brightfield-small-level-0:
29+
url: [*wsis, "CMU-1-Small-Region-Level-0.ome.tiff"]
30+
ome-brightfield-small-pyramid:
31+
url: [*wsis, "CMU-1-Small-Region-Pyramid.ome.tif"]
3032
two-tiled-pages:
3133
url: [*wsis, "two-tiled-pages.tiff"]
3234
ventana-tif:

tiatoolbox/visualization/bokeh_app/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2086,7 +2086,7 @@ def setup_doc(self: DocConfig, base_doc: Document) -> tuple[Row, Tabs]:
20862086

20872087
# Set initial slide to first one in base folder
20882088
slide_list = []
2089-
for ext in ["*.svs", "*ndpi", "*.tiff", "*.mrxs", "*.png", "*.jpg"]:
2089+
for ext in ["*.svs", "*ndpi", "*.tiff", "*.tif", "*.mrxs", "*.png", "*.jpg"]:
20902090
slide_list.extend(list(doc_config["slide_folder"].glob(ext)))
20912091
slide_list.extend(
20922092
list(doc_config["slide_folder"].glob(str(Path("*") / ext))),

tiatoolbox/wsicore/wsireader.py

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
from __future__ import annotations
44

5-
import copy
65
import json
76
import logging
87
import math
@@ -380,7 +379,9 @@ def open( # noqa: PLR0911
380379
)
381380
return NGFFWSIReader(input_path, mpp=mpp, power=power)
382381

383-
if suffixes[-2:] in ([".ome", ".tiff"],):
382+
if suffixes[-2:] in ([".ome", ".tiff"],) or suffixes[-2:] in (
383+
[".ome", ".tif"],
384+
):
384385
return TIFFWSIReader(input_path, mpp=mpp, power=power)
385386

386387
if last_suffix in (".tif", ".tiff"):
@@ -477,7 +478,7 @@ def info(self: WSIReader) -> WSIMeta:
477478
478479
"""
479480
if self._m_info is not None:
480-
return copy.deepcopy(self._m_info)
481+
return self._m_info
481482
self._m_info = self._info()
482483
if self._manual_mpp:
483484
self._m_info.mpp = np.array(self._manual_mpp)
@@ -3472,7 +3473,14 @@ def __init__(
34723473
len(self.tiff.pages) == 1,
34733474
],
34743475
)
3475-
if not any([self.tiff.is_svs, self.tiff.is_ome, is_single_page_tiled]):
3476+
if not any(
3477+
[
3478+
self.tiff.is_svs,
3479+
self.tiff.is_ome,
3480+
is_single_page_tiled,
3481+
self.tiff.is_bigtiff,
3482+
]
3483+
):
34763484
msg = "Unsupported TIFF WSI format."
34773485
raise ValueError(msg)
34783486

@@ -3503,9 +3511,16 @@ def page_area(page: tifffile.TiffPage) -> float:
35033511
group[0] = self._zarr_group
35043512
self._zarr_group = group
35053513
self.level_arrays = {
3506-
int(key): ArrayView(array, axes=self.info.axes)
3514+
int(key): ArrayView(array, axes=self._axes)
35073515
for key, array in self._zarr_group.items()
35083516
}
3517+
# ensure level arrays are sorted by descending area
3518+
self.level_arrays = dict(
3519+
sorted(
3520+
self.level_arrays.items(),
3521+
key=lambda x: -np.prod(self._canonical_shape(x[1].array.shape[:2])),
3522+
)
3523+
)
35093524

35103525
def _canonical_shape(self: TIFFWSIReader, shape: IntPair) -> tuple:
35113526
"""Make a level shape tuple in YXS order.
@@ -3761,10 +3776,10 @@ def _info(self: TIFFWSIReader) -> WSIMeta:
37613776
Containing metadata.
37623777
37633778
"""
3764-
level_count = len(self._zarr_group)
3779+
level_count = len(self.level_arrays)
37653780
level_dimensions = [
3766-
np.array(self._canonical_shape(p.shape)[:2][::-1])
3767-
for p in self._zarr_group.values()
3781+
np.array(self._canonical_shape(p.array.shape)[:2][::-1])
3782+
for p in self.level_arrays.values()
37683783
]
37693784
slide_dimensions = level_dimensions[0]
37703785
level_downsamples = [(level_dimensions[0] / x)[0] for x in level_dimensions]
@@ -4007,10 +4022,10 @@ def read_rect(
40074022
# Find parameters for optimal read
40084023
(
40094024
read_level,
4010-
_,
4011-
_,
4025+
level_read_location,
4026+
level_read_size,
40124027
post_read_scale,
4013-
baseline_read_size,
4028+
_,
40144029
) = self.find_read_rect_params(
40154030
location=location,
40164031
size=size,
@@ -4019,16 +4034,15 @@ def read_rect(
40194034
)
40204035

40214036
bounds = utils.transforms.locsize2bounds(
4022-
location=location,
4023-
size=baseline_read_size,
4037+
location=level_read_location,
4038+
size=level_read_size,
40244039
)
40254040
im_region = utils.image.safe_padded_read(
40264041
image=self.level_arrays[read_level],
40274042
bounds=bounds,
40284043
pad_mode=pad_mode,
40294044
pad_constant_values=pad_constant_values,
40304045
)
4031-
40324046
im_region = utils.transforms.imresize(
40334047
img=im_region,
40344048
scale_factor=post_read_scale,
@@ -4163,7 +4177,7 @@ class docstrings for more information.
41634177
# but base image is of different scale)
41644178
(
41654179
read_level,
4166-
_,
4180+
bounds_at_read_level,
41674181
_,
41684182
post_read_scale,
41694183
) = self._find_read_bounds_params(
@@ -4175,7 +4189,7 @@ class docstrings for more information.
41754189
# Find parameters for optimal read
41764190
(
41774191
read_level,
4178-
_,
4192+
bounds_at_read_level,
41794193
size_at_requested,
41804194
post_read_scale,
41814195
) = self._find_read_bounds_params(
@@ -4186,7 +4200,7 @@ class docstrings for more information.
41864200

41874201
im_region = utils.image.sub_pixel_read(
41884202
image=self.level_arrays[read_level],
4189-
bounds=bounds_at_baseline,
4203+
bounds=bounds_at_read_level,
41904204
output_size=size_at_requested,
41914205
interpolation=interpolation,
41924206
pad_mode=pad_mode,

0 commit comments

Comments
 (0)