Skip to content

Commit 1eee555

Browse files
authored
Merge branch 'develop' into fix-zarr-check
2 parents 1b4da41 + b564590 commit 1eee555

File tree

5 files changed

+75
-23
lines changed

5 files changed

+75
-23
lines changed

.github/workflows/mypy-type-check.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,7 @@ jobs:
5050
tiatoolbox/models/__init__.py \
5151
tiatoolbox/models/models_abc.py \
5252
tiatoolbox/models/architecture/__init__.py \
53-
tiatoolbox/models/architecture/utils.py
53+
tiatoolbox/models/architecture/utils.py \
54+
tiatoolbox/wsicore/__init__.py \
55+
tiatoolbox/wsicore/wsimeta.py \
56+
tiatoolbox/wsicore/metadata/

tests/test_meta_ngff_dataclasses.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,21 @@ def test_multiscales_defaults() -> None:
2929
"""Test :class:`ngff.Multiscales` init with default args."""
3030
ngff.Multiscales()
3131

32+
@staticmethod
33+
def test_multiscales_iter() -> None:
34+
"""Test :class:`ngff.Multiscales` init."""
35+
multiscales = ngff.Multiscales()
36+
iter_values = list(iter(multiscales))
37+
38+
# Check if all attributes are present in the yielded values
39+
assert multiscales.axes in iter_values
40+
assert multiscales.datasets in iter_values
41+
assert multiscales.version in iter_values
42+
43+
# Check the order of yielded values matches __dict__ order
44+
expected = list(multiscales.__dict__.values())
45+
assert iter_values == expected
46+
3247
@staticmethod
3348
def test_omero_defaults() -> None:
3449
"""Test :class:`ngff.Omero` init with default args."""

tests/test_wsireader.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import glymur
1717
import numpy as np
1818
import pytest
19+
import tifffile
1920
import zarr
2021
from click.testing import CliRunner
2122
from packaging.version import Version
@@ -770,6 +771,17 @@ def test_is_tiled_tiff(source_image: Path) -> None:
770771
source_image.with_suffix(".tiff").replace(source_image)
771772

772773

774+
def test_is_not_tiled_tiff(tmp_samples_path: Path) -> None:
775+
"""Test if source_image is not a tiled tiff."""
776+
temp_tiff_path = tmp_samples_path / "not_tiled.tiff"
777+
images = [np.zeros(shape=(4, 4)) for _ in range(3)]
778+
# Write multi-page TIFF with all pages not tiled
779+
with tifffile.TiffWriter(temp_tiff_path) as tif:
780+
for image in images:
781+
tif.write(image, compression=None, tile=None)
782+
assert wsireader.is_tiled_tiff(temp_tiff_path) is False
783+
784+
773785
def test_read_rect_openslide_levels(sample_ndpi: Path) -> None:
774786
"""Test openslide read rect with resolution in levels.
775787

tiatoolbox/wsicore/metadata/ngff.py

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,14 @@
99

1010
from __future__ import annotations
1111

12-
from dataclasses import dataclass, field
1312
from typing import TYPE_CHECKING, Literal
1413

15-
from tiatoolbox import __version__ as tiatoolbox_version
16-
1714
if TYPE_CHECKING: # pragma: no cover
18-
from numbers import Number
15+
from collections.abc import Iterator
16+
17+
from dataclasses import dataclass, field
18+
19+
from tiatoolbox import __version__ as tiatoolbox_version
1920

2021
SpaceUnits = Literal[
2122
"angstrom",
@@ -169,6 +170,15 @@ class Multiscales:
169170
datasets: list[Dataset] = field(default_factory=lambda: [Dataset()])
170171
version: str = "0.4"
171172

173+
def __iter__(self: Multiscales) -> Iterator:
174+
"""Iterate over the values of the attributes in the `Multiscales` object.
175+
176+
Yields:
177+
Iterator: An iterator
178+
179+
"""
180+
yield from self.__dict__.values()
181+
172182

173183
@dataclass
174184
class Window:
@@ -186,10 +196,10 @@ class Window:
186196
187197
"""
188198

189-
end: Number = 255
190-
max: Number = 255
191-
min: Number = 0
192-
start: Number = 0
199+
end: int = 255
200+
max: int = 255
201+
min: int = 0
202+
start: int = 0
193203

194204

195205
@dataclass

tiatoolbox/wsicore/wsimeta.py

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
from numbers import Number
1313
from pathlib import Path
14-
from typing import TYPE_CHECKING
14+
from typing import TYPE_CHECKING, cast
1515

1616
import numpy as np
1717

@@ -121,7 +121,7 @@ def __init__(
121121
self.level_downsamples = (
122122
[float(x) for x in level_downsamples]
123123
if level_downsamples is not None
124-
else None
124+
else [1.0]
125125
)
126126
self.level_count = (
127127
int(level_count) if level_count is not None else len(self.level_dimensions)
@@ -212,7 +212,9 @@ def level_downsample(
212212
ceil = int(np.ceil(level))
213213
floor_downsample = level_downsamples[floor]
214214
ceil_downsample = level_downsamples[ceil]
215-
return np.interp(level, [floor, ceil], [floor_downsample, ceil_downsample])
215+
return float(
216+
np.interp(level, [floor, ceil], [floor_downsample, ceil_downsample])
217+
)
216218

217219
def relative_level_scales(
218220
self: WSIMeta,
@@ -260,20 +262,25 @@ def relative_level_scales(
260262
[0.125, 0.25, 0.5, 1.0, 2.0, 4.0, 8.0, 16.0, 32.0]
261263
262264
"""
265+
base_scale: np.ndarray
266+
resolution_array: np.ndarray
267+
msg: str
268+
263269
if units not in ("mpp", "power", "level", "baseline"):
264270
msg = "Invalid units"
265271
raise ValueError(msg)
266272

267273
level_downsamples = self.level_downsamples
268274

269-
def np_pair(x: Number | np.array) -> np.ndarray:
275+
def np_pair(x: Resolution) -> np.ndarray:
270276
"""Ensure input x is a numpy array of length 2."""
271277
# If one number is given, the same value is used for x and y
272278
if isinstance(x, Number):
273279
return np.array([x] * 2)
274280
return np.array(x)
275281

276282
if units == "level":
283+
resolution = cast("float", resolution)
277284
if resolution >= len(level_downsamples):
278285
msg = (
279286
f"Target scale level {resolution} > "
@@ -282,32 +289,37 @@ def np_pair(x: Number | np.array) -> np.ndarray:
282289
raise ValueError(
283290
msg,
284291
)
285-
base_scale, resolution = 1, self.level_downsample(resolution)
286-
287-
resolution = np_pair(resolution)
292+
resolution_array = np.array(
293+
[self.level_downsample(resolution)] * 2, dtype=float
294+
)
295+
base_scale = np.array([1.0, 1.0], dtype=float)
288296

289-
if units == "mpp":
297+
elif units == "mpp":
290298
if self.mpp is None:
291299
msg = "MPP is None. Cannot determine scale in terms of MPP."
292300
raise ValueError(msg)
293301
base_scale = self.mpp
302+
resolution_array = np_pair(resolution)
294303

295-
if units == "power":
304+
elif units == "power":
296305
if self.objective_power is None:
297306
msg = (
298307
"Objective power is None. "
299-
"Cannot determine scale in terms of objective power.",
308+
"Cannot determine scale in terms of objective power."
300309
)
301310
raise ValueError(
302311
msg,
303312
)
304-
base_scale, resolution = 1 / self.objective_power, 1 / resolution
313+
base_scale = np.array([1 / self.objective_power] * 2, dtype=float)
314+
resolution_array = 1.0 / np_pair(resolution)
305315

306-
if units == "baseline":
307-
base_scale, resolution = 1, 1 / resolution
316+
else: # units == "baseline"
317+
base_scale = np.array([1.0, 1.0], dtype=float)
318+
resolution_array = 1.0 / np_pair(resolution)
308319

309320
return [
310-
(base_scale * downsample) / resolution for downsample in level_downsamples
321+
(base_scale * downsample) / resolution_array
322+
for downsample in level_downsamples
311323
]
312324

313325
def as_dict(self: WSIMeta) -> dict:

0 commit comments

Comments
 (0)