diff --git a/.github/workflows/mypy-type-check.yml b/.github/workflows/mypy-type-check.yml index 02befe4d4..987db45ad 100644 --- a/.github/workflows/mypy-type-check.yml +++ b/.github/workflows/mypy-type-check.yml @@ -51,3 +51,6 @@ jobs: tiatoolbox/models/models_abc.py \ tiatoolbox/models/architecture/__init__.py \ tiatoolbox/models/architecture/utils.py \ + tiatoolbox/wsicore/__init__.py \ + tiatoolbox/wsicore/wsimeta.py \ + tiatoolbox/wsicore/metadata/ diff --git a/tiatoolbox/wsicore/metadata/ngff.py b/tiatoolbox/wsicore/metadata/ngff.py index 464281e36..da99cb3ce 100644 --- a/tiatoolbox/wsicore/metadata/ngff.py +++ b/tiatoolbox/wsicore/metadata/ngff.py @@ -9,13 +9,14 @@ from __future__ import annotations -from dataclasses import dataclass, field from typing import TYPE_CHECKING, Literal -from tiatoolbox import __version__ as tiatoolbox_version +if TYPE_CHECKING: + from collections.abc import Iterator -if TYPE_CHECKING: # pragma: no cover - from numbers import Number +from dataclasses import dataclass, field + +from tiatoolbox import __version__ as tiatoolbox_version SpaceUnits = Literal[ "angstrom", @@ -169,6 +170,14 @@ class Multiscales: datasets: list[Dataset] = field(default_factory=lambda: [Dataset()]) version: str = "0.4" + def __iter__(self: Multiscales) -> Iterator: + """Iterate over the values of the attributes in the `Multiscales` object. + + Yields: + Iterator: An iterator + """ + yield from self.__dict__.values() + @dataclass class Window: @@ -186,10 +195,10 @@ class Window: """ - end: Number = 255 - max: Number = 255 - min: Number = 0 - start: Number = 0 + end: int = 255 + max: int = 255 + min: int = 0 + start: int = 0 @dataclass diff --git a/tiatoolbox/wsicore/wsimeta.py b/tiatoolbox/wsicore/wsimeta.py index 97145e5d6..2b1ad6f59 100644 --- a/tiatoolbox/wsicore/wsimeta.py +++ b/tiatoolbox/wsicore/wsimeta.py @@ -11,7 +11,7 @@ from numbers import Number from pathlib import Path -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, cast import numpy as np @@ -121,7 +121,7 @@ def __init__( self.level_downsamples = ( [float(x) for x in level_downsamples] if level_downsamples is not None - else None + else [1.0] ) self.level_count = ( int(level_count) if level_count is not None else len(self.level_dimensions) @@ -212,7 +212,9 @@ def level_downsample( ceil = int(np.ceil(level)) floor_downsample = level_downsamples[floor] ceil_downsample = level_downsamples[ceil] - return np.interp(level, [floor, ceil], [floor_downsample, ceil_downsample]) + return float( + np.interp(level, [floor, ceil], [floor_downsample, ceil_downsample]) + ) def relative_level_scales( self: WSIMeta, @@ -260,13 +262,16 @@ def relative_level_scales( [0.125, 0.25, 0.5, 1.0, 2.0, 4.0, 8.0, 16.0, 32.0] """ + base_scale: np.ndarray | float + msg: str + if units not in ("mpp", "power", "level", "baseline"): msg = "Invalid units" raise ValueError(msg) level_downsamples = self.level_downsamples - def np_pair(x: Number | np.array) -> np.ndarray: + def np_pair(x: Resolution) -> np.ndarray: """Ensure input x is a numpy array of length 2.""" # If one number is given, the same value is used for x and y if isinstance(x, Number): @@ -274,6 +279,7 @@ def np_pair(x: Number | np.array) -> np.ndarray: return np.array(x) if units == "level": + resolution = cast("float", resolution) if resolution >= len(level_downsamples): msg = ( f"Target scale level {resolution} > " @@ -296,7 +302,7 @@ def np_pair(x: Number | np.array) -> np.ndarray: if self.objective_power is None: msg = ( "Objective power is None. " - "Cannot determine scale in terms of objective power.", + "Cannot determine scale in terms of objective power." ) raise ValueError( msg,