Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🐛 Fix TIFFWSIReader read_bound #777

Merged
merged 27 commits into from
Jul 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
cfba642
UPD: fix bound
vqdang Jan 29, 2024
8979008
Merge branch 'develop' into bugfix-tiffwsireader-readbounds
shaneahmed Feb 2, 2024
84af337
Merge branch 'develop' into bugfix-tiffwsireader-readbounds
shaneahmed Feb 2, 2024
6aab409
Merge branch 'develop' into bugfix-tiffwsireader-readbounds
shaneahmed Feb 16, 2024
e09e216
Merge branch 'develop' into bugfix-tiffwsireader-readbounds
shaneahmed Feb 21, 2024
249a716
Merge branch 'develop' into bugfix-tiffwsireader-readbounds
shaneahmed Mar 1, 2024
246b2c8
Merge branch 'develop' into bugfix-tiffwsireader-readbounds
shaneahmed Mar 15, 2024
817a526
Merge branch 'develop' into bugfix-tiffwsireader-readbounds
shaneahmed Mar 19, 2024
28a324f
Merge branch 'develop' into bugfix-tiffwsireader-readbounds
shaneahmed Apr 16, 2024
ade11ba
Merge branch 'develop' into bugfix-tiffwsireader-readbounds
shaneahmed Apr 26, 2024
f4ac670
Merge branch 'develop' into bugfix-tiffwsireader-readbounds
shaneahmed Apr 29, 2024
87450b2
Merge branch 'develop' into bugfix-tiffwsireader-readbounds
Abdol May 13, 2024
6fdf9e7
✅ Add test
May 13, 2024
13d3293
Merge branch 'develop' into bugfix-tiffwsireader-readbounds
Abdol May 14, 2024
b5c8861
Merge branch 'develop' into bugfix-tiffwsireader-readbounds
Abdol Jun 7, 2024
a1acbfd
Merge branch 'develop' into bugfix-tiffwsireader-readbounds
shaneahmed Jun 14, 2024
60361c2
Merge branch 'develop' into bugfix-tiffwsireader-readbounds
shaneahmed Jun 21, 2024
318e482
Merge branch 'develop' into bugfix-tiffwsireader-readbounds
Abdol Jun 25, 2024
dccf4db
fix read_rect
measty Jun 26, 2024
c1a851b
✅ Replace test with a level consistency test
Jun 27, 2024
d65c55b
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 27, 2024
5835b34
Merge branch 'bugfix-tiffwsireader-readbounds' of https://github.com/…
measty Jun 27, 2024
e891502
deepsource
measty Jun 27, 2024
c40443d
Merge branch 'develop' into bugfix-tiffwsireader-readbounds
Abdol Jun 28, 2024
b4cd533
✅ Remove test and update sample OME image paths
Jun 28, 2024
a2c8afe
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 28, 2024
5b4a405
[skip ci] :memo: Fix indentation.
shaneahmed Jul 5, 2024
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
12 changes: 11 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,17 @@ def sample_ome_tiff(remote_sample: Callable) -> Path:
Download ome-tiff image for pytest.

"""
return remote_sample("ome-brightfield-pyramid-1-small")
return remote_sample("ome-brightfield-small-pyramid")


@pytest.fixture(scope="session")
def sample_ome_tiff_level_0(remote_sample: Callable) -> Path:
"""Sample pytest fixture for ome-tiff image with one level.

Download ome-tiff image for pytest.

"""
return remote_sample("ome-brightfield-small-level-0")


@pytest.fixture(scope="session")
Expand Down
8 changes: 4 additions & 4 deletions tests/test_tiffreader.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def test_ome_missing_instrument_ref(
remote_sample: Callable,
) -> None:
"""Test that an OME-TIFF can be read without instrument reference."""
sample = remote_sample("ome-brightfield-pyramid-1-small")
sample = remote_sample("ome-brightfield-small-level-0")
wsi = wsireader.TIFFWSIReader(sample)
page = wsi.tiff.pages[0]
description = page.description
Expand All @@ -37,7 +37,7 @@ def test_ome_missing_physicalsize(
remote_sample: Callable,
) -> None:
"""Test that an OME-TIFF can be read without physical size."""
sample = remote_sample("ome-brightfield-pyramid-1-small")
sample = remote_sample("ome-brightfield-small-level-0")
wsi = wsireader.TIFFWSIReader(sample)
page = wsi.tiff.pages[0]
description = page.description
Expand All @@ -62,7 +62,7 @@ def test_ome_missing_physicalsizey(
remote_sample: Callable,
) -> None:
"""Test that an OME-TIFF can be read without physical size."""
sample = remote_sample("ome-brightfield-pyramid-1-small")
sample = remote_sample("ome-brightfield-small-level-0")
wsi = wsireader.TIFFWSIReader(sample)
page = wsi.tiff.pages[0]
description = page.description
Expand All @@ -86,7 +86,7 @@ def test_tiffreader_non_tiled_metadata(
remote_sample: Callable,
) -> None:
"""Test that fetching metadata for non-tiled TIFF works."""
sample = remote_sample("ome-brightfield-pyramid-1-small")
sample = remote_sample("ome-brightfield-small-level-0")
wsi = wsireader.TIFFWSIReader(sample)
monkeypatch.setattr(wsi.tiff, "is_ome", False)
monkeypatch.setattr(
Expand Down
18 changes: 13 additions & 5 deletions tests/test_wsireader.py
Original file line number Diff line number Diff line change
Expand Up @@ -959,15 +959,23 @@ def test_read_bounds_interpolated(sample_svs: Path) -> None:


def test_read_bounds_level_consistency_openslide(sample_ndpi: Path) -> None:
"""Test read_bounds produces the same visual field across resolution levels."""
"""Test read_bounds produces the same visual field across resolution levels.

with OpenSlideWSIReader.

"""
wsi = wsireader.OpenSlideWSIReader(sample_ndpi)
bounds = NDPI_TEST_TISSUE_BOUNDS

read_bounds_level_consistency(wsi, bounds)


def test_read_bounds_level_consistency_jp2(sample_jp2: Path) -> None:
"""Test read_bounds produces the same visual field across resolution levels."""
"""Test read_bounds produces the same visual field across resolution levels.

Using JP2WSIReader.

"""
bounds = JP2_TEST_TISSUE_BOUNDS
wsi = wsireader.JP2WSIReader(sample_jp2)

Expand Down Expand Up @@ -1883,11 +1891,11 @@ def test_tiffwsireader_invalid_svs_metadata(


def test_tiffwsireader_invalid_ome_metadata(
sample_ome_tiff: Path,
sample_ome_tiff_level_0: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Test exception raised for invalid OME-XML metadata instrument."""
wsi = wsireader.TIFFWSIReader(sample_ome_tiff)
wsi = wsireader.TIFFWSIReader(sample_ome_tiff_level_0)
monkeypatch.setattr(
wsi.tiff.pages[0],
"description",
Expand Down Expand Up @@ -2545,7 +2553,7 @@ def test_jp2_no_header(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
},
{
"reader_class": TIFFWSIReader,
"sample_key": "ome-brightfield-pyramid-1-small",
"sample_key": "ome-brightfield-small-pyramid",
"kwargs": {},
},
{
Expand Down
6 changes: 4 additions & 2 deletions tiatoolbox/data/remote_samples.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@ files:
url: [*wsis, "CMU-1-Small-Region.jpeg.tiff"]
tiled-tiff-1-small-jp2k:
url: [*wsis, "CMU-1-Small-Region.jp2k.tiff"]
ome-brightfield-pyramid-1-small:
url: [*wsis, "CMU-1-Small-Region.ome.tiff"]
ome-brightfield-small-level-0:
url: [*wsis, "CMU-1-Small-Region-Level-0.ome.tiff"]
ome-brightfield-small-pyramid:
url: [*wsis, "CMU-1-Small-Region-Pyramid.ome.tif"]
two-tiled-pages:
url: [*wsis, "two-tiled-pages.tiff"]
ventana-tif:
Expand Down
2 changes: 1 addition & 1 deletion tiatoolbox/visualization/bokeh_app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2086,7 +2086,7 @@ def setup_doc(self: DocConfig, base_doc: Document) -> tuple[Row, Tabs]:

# Set initial slide to first one in base folder
slide_list = []
for ext in ["*.svs", "*ndpi", "*.tiff", "*.mrxs", "*.png", "*.jpg"]:
for ext in ["*.svs", "*ndpi", "*.tiff", "*.tif", "*.mrxs", "*.png", "*.jpg"]:
slide_list.extend(list(doc_config["slide_folder"].glob(ext)))
slide_list.extend(
list(doc_config["slide_folder"].glob(str(Path("*") / ext))),
Expand Down
48 changes: 31 additions & 17 deletions tiatoolbox/wsicore/wsireader.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

from __future__ import annotations

import copy
import json
import logging
import math
Expand Down Expand Up @@ -380,7 +379,9 @@ def open( # noqa: PLR0911
)
return NGFFWSIReader(input_path, mpp=mpp, power=power)

if suffixes[-2:] in ([".ome", ".tiff"],):
if suffixes[-2:] in ([".ome", ".tiff"],) or suffixes[-2:] in (
[".ome", ".tif"],
):
return TIFFWSIReader(input_path, mpp=mpp, power=power)

if last_suffix in (".tif", ".tiff"):
Expand Down Expand Up @@ -477,7 +478,7 @@ def info(self: WSIReader) -> WSIMeta:

"""
if self._m_info is not None:
return copy.deepcopy(self._m_info)
return self._m_info
self._m_info = self._info()
if self._manual_mpp:
self._m_info.mpp = np.array(self._manual_mpp)
Expand Down Expand Up @@ -3472,7 +3473,14 @@ def __init__(
len(self.tiff.pages) == 1,
],
)
if not any([self.tiff.is_svs, self.tiff.is_ome, is_single_page_tiled]):
if not any(
[
self.tiff.is_svs,
self.tiff.is_ome,
is_single_page_tiled,
self.tiff.is_bigtiff,
]
):
msg = "Unsupported TIFF WSI format."
raise ValueError(msg)

Expand Down Expand Up @@ -3503,9 +3511,16 @@ def page_area(page: tifffile.TiffPage) -> float:
group[0] = self._zarr_group
self._zarr_group = group
self.level_arrays = {
int(key): ArrayView(array, axes=self.info.axes)
int(key): ArrayView(array, axes=self._axes)
for key, array in self._zarr_group.items()
}
# ensure level arrays are sorted by descending area
self.level_arrays = dict(
sorted(
self.level_arrays.items(),
key=lambda x: -np.prod(self._canonical_shape(x[1].array.shape[:2])),
)
)

def _canonical_shape(self: TIFFWSIReader, shape: IntPair) -> tuple:
"""Make a level shape tuple in YXS order.
Expand Down Expand Up @@ -3761,10 +3776,10 @@ def _info(self: TIFFWSIReader) -> WSIMeta:
Containing metadata.

"""
level_count = len(self._zarr_group)
level_count = len(self.level_arrays)
level_dimensions = [
np.array(self._canonical_shape(p.shape)[:2][::-1])
for p in self._zarr_group.values()
np.array(self._canonical_shape(p.array.shape)[:2][::-1])
for p in self.level_arrays.values()
]
slide_dimensions = level_dimensions[0]
level_downsamples = [(level_dimensions[0] / x)[0] for x in level_dimensions]
Expand Down Expand Up @@ -4007,10 +4022,10 @@ def read_rect(
# Find parameters for optimal read
(
read_level,
_,
_,
level_read_location,
level_read_size,
post_read_scale,
baseline_read_size,
_,
) = self.find_read_rect_params(
location=location,
size=size,
Expand All @@ -4019,16 +4034,15 @@ def read_rect(
)

bounds = utils.transforms.locsize2bounds(
location=location,
size=baseline_read_size,
location=level_read_location,
size=level_read_size,
)
im_region = utils.image.safe_padded_read(
image=self.level_arrays[read_level],
bounds=bounds,
pad_mode=pad_mode,
pad_constant_values=pad_constant_values,
)

im_region = utils.transforms.imresize(
img=im_region,
scale_factor=post_read_scale,
Expand Down Expand Up @@ -4163,7 +4177,7 @@ class docstrings for more information.
# but base image is of different scale)
(
read_level,
_,
bounds_at_read_level,
_,
post_read_scale,
) = self._find_read_bounds_params(
Expand All @@ -4175,7 +4189,7 @@ class docstrings for more information.
# Find parameters for optimal read
(
read_level,
_,
bounds_at_read_level,
size_at_requested,
post_read_scale,
) = self._find_read_bounds_params(
Expand All @@ -4186,7 +4200,7 @@ class docstrings for more information.

im_region = utils.image.sub_pixel_read(
image=self.level_arrays[read_level],
bounds=bounds_at_baseline,
bounds=bounds_at_read_level,
output_size=size_at_requested,
interpolation=interpolation,
pad_mode=pad_mode,
Expand Down