Skip to content

Commit cc28a12

Browse files
authored
Merge branch 'develop' into dev-define-engines-abc
2 parents c9dfba2 + a6fceef commit cc28a12

File tree

11 files changed

+61
-53
lines changed

11 files changed

+61
-53
lines changed

.github/workflows/python-package.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ jobs:
3030
sudo apt update
3131
sudo apt-get install -y libopenslide-dev openslide-tools libopenjp2-7 libopenjp2-tools
3232
python -m pip install --upgrade pip
33-
python -m pip install ruff==0.5.6 "pytest<8.3.0" pytest-cov pytest-runner
33+
python -m pip install ruff==0.5.7 "pytest<8.3.0" pytest-cov pytest-runner
3434
pip install -r requirements/requirements.txt
3535
- name: Cache tiatoolbox static assets
3636
uses: actions/cache@v3

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ repos:
6060
- id: rst-inline-touching-normal # Detect mistake of inline code touching normal text in rst.
6161
- repo: https://github.com/astral-sh/ruff-pre-commit
6262
# Ruff version.
63-
rev: v0.5.6
63+
rev: v0.5.7
6464
hooks:
6565
- id: ruff
6666
args: [--fix, --exit-non-zero-on-fix]

requirements/requirements_dev.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ pytest>=7.2.0, <8.3.0
1111
pytest-cov>=4.0.0
1212
pytest-runner>=6.0
1313
pytest-xdist[psutil]
14-
ruff==0.5.6 # This will be updated by pre-commit bot to latest version
14+
ruff==0.5.7 # This will be updated by pre-commit bot to latest version
1515
toml>=0.10.2
1616
twine>=4.0.1
1717
wheel>=0.37.1

tiatoolbox/tools/graph.py

Lines changed: 30 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from collections import defaultdict
66
from numbers import Number
7-
from typing import TYPE_CHECKING, Callable
7+
from typing import TYPE_CHECKING, Callable, cast
88

99
import numpy as np
1010
import torch
@@ -18,15 +18,15 @@
1818
from numpy.typing import ArrayLike
1919

2020

21-
def delaunay_adjacency(points: ArrayLike, dthresh: float) -> list:
21+
def delaunay_adjacency(points: np.ndarray, dthresh: float) -> np.ndarray:
2222
"""Create an adjacency matrix via Delaunay triangulation from a list of coordinates.
2323
2424
Points which are further apart than dthresh will not be connected.
2525
2626
See https://en.wikipedia.org/wiki/Adjacency_matrix.
2727
2828
Args:
29-
points (ArrayLike):
29+
points (np.ndarray):
3030
An nxm list of coordinates.
3131
dthresh (float):
3232
Distance threshold for triangulation.
@@ -113,11 +113,11 @@ def triangle_signed_area(triangle: ArrayLike) -> int:
113113
)
114114

115115

116-
def edge_index_to_triangles(edge_index: ArrayLike) -> ArrayLike:
116+
def edge_index_to_triangles(edge_index: np.ndarray) -> np.ndarray:
117117
"""Convert an edged index to triangle simplices (triplets of coordinate indices).
118118
119119
Args:
120-
edge_index (ArrayLike):
120+
edge_index (np.ndarray):
121121
An Nx2 array of edges.
122122
123123
Returns:
@@ -157,24 +157,24 @@ def edge_index_to_triangles(edge_index: ArrayLike) -> ArrayLike:
157157

158158

159159
def affinity_to_edge_index(
160-
affinity_matrix: torch.Tensor | ArrayLike,
160+
affinity_matrix: torch.Tensor | np.ndarray,
161161
threshold: float = 0.5,
162-
) -> torch.tensor | ArrayLike:
162+
) -> torch.Tensor | np.ndarray:
163163
"""Convert an affinity matrix (similarity matrix) to an edge index.
164164
165165
Converts an NxN affinity matrix to a 2xM edge index, where M is the
166166
number of node pairs with a similarity greater than the threshold
167167
value (defaults to 0.5).
168168
169169
Args:
170-
affinity_matrix:
170+
affinity_matrix (torch.Tensor | np.ndarray):
171171
An NxN matrix of affinities between nodes.
172172
threshold (Number):
173173
Threshold above which to be considered connected. Defaults
174174
to 0.5.
175175
176176
Returns:
177-
ArrayLike or torch.Tensor:
177+
torch.Tensor | np.ndarray:
178178
The edge index of shape (2, M).
179179
180180
Example:
@@ -191,9 +191,9 @@ def affinity_to_edge_index(
191191
raise ValueError(msg)
192192
# Handle cases for pytorch and numpy inputs
193193
if isinstance(affinity_matrix, torch.Tensor):
194-
return (affinity_matrix > threshold).nonzero().t().contiguous()
194+
return (affinity_matrix > threshold).nonzero().t().contiguous().to(torch.int64)
195195
return np.ascontiguousarray(
196-
np.stack((affinity_matrix > threshold).nonzero(), axis=1).T,
196+
np.stack((affinity_matrix > threshold).nonzero(), axis=1).T.astype(np.int64),
197197
)
198198

199199

@@ -208,7 +208,7 @@ class SlideGraphConstructor:
208208
"""
209209

210210
@staticmethod
211-
def _umap_reducer(graph: dict[str, ArrayLike]) -> ArrayLike:
211+
def _umap_reducer(graph: dict[str, np.ndarray]) -> np.ndarray:
212212
"""Default reduction which reduces `graph["x"]` to 3D values.
213213
214214
Reduces graph features to 3D values using UMAP which are suitable
@@ -220,7 +220,7 @@ def _umap_reducer(graph: dict[str, ArrayLike]) -> ArrayLike:
220220
"coordinates".
221221
222222
Returns:
223-
ArrayLike:
223+
np.ndarray:
224224
A UMAP embedding of `graph["x"]` with shape (N, 3) and
225225
values ranging from 0 to 1.
226226
"""
@@ -232,15 +232,15 @@ def _umap_reducer(graph: dict[str, ArrayLike]) -> ArrayLike:
232232

233233
@staticmethod
234234
def build(
235-
points: ArrayLike,
236-
features: ArrayLike,
235+
points: np.ndarray,
236+
features: np.ndarray,
237237
lambda_d: float = 3.0e-3,
238238
lambda_f: float = 1.0e-3,
239239
lambda_h: float = 0.8,
240240
connectivity_distance: int = 4000,
241241
neighbour_search_radius: int = 2000,
242242
feature_range_thresh: float | None = 1e-4,
243-
) -> dict[str, ArrayLike]:
243+
) -> dict[str, np.ndarray]:
244244
"""Build a graph via hybrid clustering in spatial and feature space.
245245
246246
The graph is constructed via hybrid hierarchical clustering
@@ -266,10 +266,10 @@ def build(
266266
connected.
267267
268268
Args:
269-
points (ArrayLike):
269+
points (np.ndarray):
270270
A list of (x, y) spatial coordinates, e.g. pixel
271271
locations within a WSI.
272-
features (ArrayLike):
272+
features (np.ndarray):
273273
A list of features associated with each coordinate in
274274
`points`. Must be the same length as `points`.
275275
lambda_d (Number):
@@ -400,27 +400,27 @@ def build(
400400
# Find the xy and feature space averages of the cluster
401401
point_centroids.append(np.round(points[idx, :].mean(axis=0)))
402402
feature_centroids.append(features[idx, :].mean(axis=0))
403-
point_centroids = np.array(point_centroids)
404-
feature_centroids = np.array(feature_centroids)
403+
point_centroids_arr = np.array(point_centroids)
404+
feature_centroids_arr = np.array(feature_centroids)
405405

406406
adjacency_matrix = delaunay_adjacency(
407-
points=point_centroids,
407+
points=point_centroids_arr,
408408
dthresh=connectivity_distance,
409409
)
410410
edge_index = affinity_to_edge_index(adjacency_matrix)
411-
411+
edge_index = cast(np.ndarray, edge_index)
412412
return {
413-
"x": feature_centroids,
414-
"edge_index": edge_index.astype(np.int64),
415-
"coordinates": point_centroids,
413+
"x": feature_centroids_arr,
414+
"edge_index": edge_index,
415+
"coordinates": point_centroids_arr,
416416
}
417417

418418
@classmethod
419419
def visualise(
420420
cls: type[SlideGraphConstructor],
421-
graph: dict[str, ArrayLike],
422-
color: ArrayLike | str | Callable | None = None,
423-
node_size: Number | ArrayLike | Callable = 25,
421+
graph: dict[str, np.ndarray],
422+
color: np.ndarray | str | Callable | None = None,
423+
node_size: int | np.ndarray | Callable = 25,
424424
edge_color: str | ArrayLike = (0, 0, 0, 0.33),
425425
ax: Axes | None = None,
426426
) -> Axes:
@@ -510,7 +510,8 @@ def visualise(
510510

511511
# Plot the nodes
512512
plt.scatter(
513-
*nodes.T,
513+
x=nodes.T[0],
514+
y=nodes.T[1],
514515
c=color(graph) if callable(color) else color,
515516
s=node_size(graph) if callable(node_size) else node_size,
516517
zorder=2,

tiatoolbox/tools/patchextraction.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ class ExtractorParams(TypedDict, total=False):
4545
pad_mode: str
4646
pad_constant_values: int | tuple[int, int]
4747
within_bound: bool
48-
input_mask: str | Path | np.ndarray | wsireader.WSIReader
48+
input_mask: str | Path | np.ndarray | wsireader.VirtualWSIReader
4949
stride: int | tuple[int, int]
5050
min_mask_ratio: float
5151

@@ -81,7 +81,7 @@ class SlidingWindowPatchExtractorParams(TypedDict):
8181
pad_mode: str
8282
pad_constant_values: int | tuple[int, int]
8383
within_bound: bool
84-
input_mask: str | Path | np.ndarray | wsireader.WSIReader | None
84+
input_mask: str | Path | np.ndarray | wsireader.VirtualWSIReader | None
8585
stride: int | tuple[int, int] | None
8686
min_mask_ratio: float
8787

@@ -113,7 +113,8 @@ class PatchExtractor(PatchExtractorABC):
113113
Input image for patch extraction.
114114
patch_size(int or tuple(int)):
115115
Patch size tuple (width, height).
116-
input_mask(str, pathlib.Path, :class:`numpy.ndarray`, or :obj:`WSIReader`):
116+
input_mask
117+
(str, pathlib.Path, :class:`numpy.ndarray`, or :obj:`VirtualWSIReader`):
117118
Input mask that is used for position filtering when
118119
extracting patches i.e., patches will only be extracted
119120
based on the highlighted regions in the input_mask.
@@ -187,7 +188,7 @@ def __init__(
187188
self: PatchExtractor,
188189
input_img: str | Path | np.ndarray,
189190
patch_size: int | tuple[int, int],
190-
input_mask: str | Path | np.ndarray | wsireader.WSIReader | None = None,
191+
input_mask: str | Path | np.ndarray | wsireader.VirtualWSIReader | None = None,
191192
resolution: Resolution = 0,
192193
units: Units = "level",
193194
pad_mode: str = "constant",
@@ -391,7 +392,7 @@ def filter_coordinates(
391392
0,
392393
tissue_mask.shape[0],
393394
)
394-
scaled_coords = list((scaled_coords).astype(np.int32))
395+
scaled_coords_list = list((scaled_coords).astype(np.int32))
395396

396397
def default_sel_func(
397398
tissue_mask: np.ndarray,
@@ -412,7 +413,7 @@ def default_sel_func(
412413
) and (pos_area > 0 and patch_area > 0)
413414

414415
func = default_sel_func if func is None else func
415-
flag_list = [func(tissue_mask, coord) for coord in scaled_coords]
416+
flag_list = [func(tissue_mask, coord) for coord in scaled_coords_list]
416417

417418
return np.array(flag_list)
418419

@@ -529,7 +530,7 @@ def get_coordinates(
529530
msg = f"`stride_shape` value {stride_shape_arr} must > 1."
530531
raise ValueError(msg)
531532

532-
def flat_mesh_grid_coord(x: int, y: int) -> np.ndarray:
533+
def flat_mesh_grid_coord(x: np.ndarray, y: np.ndarray) -> np.ndarray:
533534
"""Helper function to obtain coordinate grid."""
534535
xv, yv = np.meshgrid(x, y)
535536
return np.stack([xv.flatten(), yv.flatten()], axis=-1)
@@ -577,7 +578,8 @@ class SlidingWindowPatchExtractor(PatchExtractor):
577578
Input image for patch extraction.
578579
patch_size(int or tuple(int)):
579580
Patch size tuple (width, height).
580-
input_mask(str, pathlib.Path, :class:`numpy.ndarray`, or :obj:`WSIReader`):
581+
input_mask
582+
(str, pathlib.Path, :class:`numpy.ndarray`, or :obj:`VirtualWSIReader`):
581583
Input mask that is used for position filtering when
582584
extracting patches i.e., patches will only be extracted
583585
based on the highlighted regions in the `input_mask`.
@@ -627,7 +629,7 @@ def __init__(
627629
self: SlidingWindowPatchExtractor,
628630
input_img: str | Path | np.ndarray,
629631
patch_size: int | tuple[int, int],
630-
input_mask: str | Path | np.ndarray | wsireader.WSIReader | None = None,
632+
input_mask: str | Path | np.ndarray | wsireader.VirtualWSIReader | None = None,
631633
resolution: Resolution = 0,
632634
units: Units = "level",
633635
stride: int | tuple[int, int] | None = None,

tiatoolbox/tools/pyramid.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ def get_tile(
223223
output_size = self.output_tile_size // 2 ** (
224224
self.sub_tile_level_count - level
225225
)
226-
output_size = np.repeat(output_size, 2).astype(int)
226+
output_size = np.repeat(output_size, 2).astype(int).tolist()
227227
thumb = self.get_thumb_tile()
228228
thumb.thumbnail((output_size[0], output_size[1]))
229229
return thumb
@@ -236,7 +236,7 @@ def get_tile(
236236
logger.addFilter(duplicate_filter)
237237
tile = self.wsi.read_rect(
238238
coord,
239-
size=[v * res for v in output_size],
239+
size=(output_size[0] * res, output_size[1] * res),
240240
resolution=res / scale,
241241
units="baseline",
242242
pad_mode=pad_mode,
@@ -510,7 +510,7 @@ class AnnotationTileGenerator(ZoomifyGenerator):
510510
511511
"""
512512

513-
def __init__(
513+
def __init__( # skipcq: PYL-W0231
514514
self: AnnotationTileGenerator,
515515
info: WSIMeta,
516516
store: AnnotationStore,
@@ -520,9 +520,11 @@ def __init__(
520520
overlap: int = 0,
521521
) -> None:
522522
"""Initialize :class:`AnnotationTileGenerator`."""
523-
super().__init__(None, tile_size, downsample, overlap)
524523
self.info = info
525524
self.store = store
525+
self.tile_size = tile_size
526+
self.downsample = downsample
527+
self.overlap = overlap
526528
if renderer is None:
527529
renderer = AnnotationRenderer()
528530
self.renderer = renderer

tiatoolbox/tools/registration/wsi_registration.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1688,6 +1688,7 @@ def read_rect(
16881688
transformed_patch = transformed_patch[start_row:end_row, start_col:end_col, :]
16891689

16901690
# Resize to desired size
1691+
post_read_scale = float(post_read_scale[0]), float(post_read_scale[1])
16911692
return imresize(
16921693
img=transformed_patch,
16931694
scale_factor=post_read_scale,

tiatoolbox/tools/stainaugment.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from __future__ import annotations
44

55
import copy
6+
from typing import cast
67

78
import numpy as np
89
from albumentations.core.transforms_interface import ImageOnlyTransform
@@ -132,7 +133,7 @@ def __init__(
132133

133134
self.alpha: float
134135
self.beta: float
135-
self.img_shape = None
136+
self.img_shape: tuple[int, ...]
136137
self.tissue_mask: np.ndarray
137138
self.n_stains: int
138139
self.source_concentrations: np.ndarray
@@ -196,6 +197,7 @@ def augment(self: StainAugmentor) -> np.ndarray:
196197
else:
197198
augmented_concentrations[self.tissue_mask, i] *= self.alpha
198199
augmented_concentrations[self.tissue_mask, i] += self.beta
200+
self.stain_matrix = cast(np.ndarray, self.stain_matrix)
199201
img_augmented = 255 * np.exp(
200202
-1 * np.dot(augmented_concentrations, self.stain_matrix),
201203
)

tiatoolbox/tools/tissuemask.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ def fit(
124124
# Convert RGB images to greyscale
125125
grey_images = [x[..., 0] for x in images]
126126
if images_shape[-1] == 3: # noqa: PLR2004
127-
grey_images = np.zeros(images_shape[:-1], dtype=np.uint8)
127+
grey_images = np.zeros(images_shape[:-1], dtype=np.uint8).tolist()
128128
for n, image in enumerate(images):
129129
grey_images[n] = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
130130

@@ -206,7 +206,7 @@ def __init__(
206206
*,
207207
mpp: float | tuple[float, float] | None = None,
208208
power: float | tuple[float, float] | None = None,
209-
kernel_size: int | tuple[int, int] | None = None,
209+
kernel_size: int | tuple[int, int] | np.ndarray | None = None,
210210
min_region_size: int | None = None,
211211
) -> None:
212212
"""Initialise a morphological masker.
@@ -249,7 +249,7 @@ def __init__(
249249
mpp_array = np.array(mpp)
250250
if mpp_array.size != 2: # noqa: PLR2004
251251
mpp_array = mpp_array.repeat(2)
252-
kernel_size = np.max([32 / mpp_array, [1, 1]], axis=0)
252+
kernel_size = np.max([32 / mpp_array, np.array([1, 1])], axis=0)
253253

254254
# Ensure kernel_size is a length 2 numpy array
255255
kernel_size_array = np.array(kernel_size)

0 commit comments

Comments
 (0)