Skip to content

Commit 043d2c8

Browse files
committed
type the deepzoom generator
Signed-off-by: Sam Maxwell <[email protected]>
1 parent 87aae22 commit 043d2c8

File tree

2 files changed

+64
-32
lines changed

2 files changed

+64
-32
lines changed

openslide/deepzoom.py

Lines changed: 63 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
This module provides functionality for generating Deep Zoom images from
2323
OpenSlide objects.
2424
"""
25+
from __future__ import annotations
2526

2627
from io import BytesIO
2728
import math
@@ -44,7 +45,13 @@ class DeepZoomGenerator:
4445
openslide.PROPERTY_NAME_BOUNDS_HEIGHT,
4546
)
4647

47-
def __init__(self, osr, tile_size=254, overlap=1, limit_bounds=False):
48+
def __init__(
49+
self,
50+
osr: openslide.AbstractSlide,
51+
tile_size: int = 254,
52+
overlap: int = 1,
53+
limit_bounds: bool = False,
54+
):
4855
"""Create a DeepZoomGenerator wrapping an OpenSlide object.
4956
5057
osr: a slide object.
@@ -99,7 +106,7 @@ def __init__(self, osr, tile_size=254, overlap=1, limit_bounds=False):
99106
self._z_dimensions = tuple(reversed(z_dimensions))
100107

101108
# Tile
102-
def tiles(z_lim):
109+
def tiles(z_lim: int) -> int:
103110
return int(math.ceil(z_lim / self._z_t_downsample))
104111

105112
self._t_dimensions = tuple(
@@ -110,7 +117,8 @@ def tiles(z_lim):
110117
self._dz_levels = len(self._z_dimensions)
111118

112119
# Total downsamples for each Deep Zoom level
113-
l0_z_downsamples = tuple(
120+
# mypy infers this as a tuple[Any, ...] due to the ** operator
121+
l0_z_downsamples: tuple[int, ...] = tuple(
114122
2 ** (self._dz_levels - dz_level - 1) for dz_level in range(self._dz_levels)
115123
)
116124

@@ -132,7 +140,7 @@ def tiles(z_lim):
132140
openslide.PROPERTY_NAME_BACKGROUND_COLOR, 'ffffff'
133141
)
134142

135-
def __repr__(self):
143+
def __repr__(self) -> str:
136144
return '{}({!r}, tile_size={!r}, overlap={!r}, limit_bounds={!r})'.format(
137145
self.__class__.__name__,
138146
self._osr,
@@ -142,26 +150,26 @@ def __repr__(self):
142150
)
143151

144152
@property
145-
def level_count(self):
153+
def level_count(self) -> int:
146154
"""The number of Deep Zoom levels in the image."""
147155
return self._dz_levels
148156

149157
@property
150-
def level_tiles(self):
158+
def level_tiles(self) -> tuple[tuple[int, int], ...]:
151159
"""A list of (tiles_x, tiles_y) tuples for each Deep Zoom level."""
152160
return self._t_dimensions
153161

154162
@property
155-
def level_dimensions(self):
163+
def level_dimensions(self) -> tuple[tuple[int, ...], ...]:
156164
"""A list of (pixels_x, pixels_y) tuples for each Deep Zoom level."""
157165
return self._z_dimensions
158166

159167
@property
160-
def tile_count(self):
168+
def tile_count(self) -> int:
161169
"""The total number of Deep Zoom tiles in the image."""
162170
return sum(t_cols * t_rows for t_cols, t_rows in self._t_dimensions)
163171

164-
def get_tile(self, level, address):
172+
def get_tile(self, level: int, address: tuple[int, int]) -> Image.Image:
165173
"""Return an RGB PIL.Image for a tile.
166174
167175
level: the Deep Zoom level.
@@ -189,7 +197,9 @@ def get_tile(self, level, address):
189197

190198
return tile
191199

192-
def _get_tile_info(self, dz_level, t_location):
200+
def _get_tile_info(
201+
self, dz_level: int, t_location: tuple[int, int]
202+
) -> tuple[tuple[tuple[int, int], int, tuple[int, int]], tuple[int, int]]:
193203
# Check parameters
194204
if dz_level < 0 or dz_level >= self._dz_levels:
195205
raise ValueError("Invalid level")
@@ -208,42 +218,62 @@ def _get_tile_info(self, dz_level, t_location):
208218
)
209219

210220
# Get final size of the tile
211-
z_size = tuple(
212-
min(self._z_t_downsample, z_lim - self._z_t_downsample * t) + z_tl + z_br
213-
for t, z_lim, z_tl, z_br in zip(
214-
t_location, self._z_dimensions[dz_level], z_overlap_tl, z_overlap_br
221+
z_size = (
222+
min(
223+
self._z_t_downsample,
224+
self._z_dimensions[dz_level][0] - self._z_t_downsample * t_location[0],
215225
)
226+
+ z_overlap_tl[0]
227+
+ z_overlap_br[0],
228+
min(
229+
self._z_t_downsample,
230+
self._z_dimensions[dz_level][1] - self._z_t_downsample * t_location[1],
231+
)
232+
+ z_overlap_tl[1]
233+
+ z_overlap_br[1],
216234
)
217235

218236
# Obtain the region coordinates
219-
z_location = [self._z_from_t(t) for t in t_location]
220-
l_location = [
221-
self._l_from_z(dz_level, z - z_tl)
222-
for z, z_tl in zip(z_location, z_overlap_tl)
223-
]
237+
z_location = (self._z_from_t(t_location[0]), self._z_from_t(t_location[1]))
238+
l_location = (
239+
self._l_from_z(dz_level, z_location[0] - z_overlap_tl[0]),
240+
self._l_from_z(dz_level, z_location[1] - z_overlap_tl[1]),
241+
)
224242
# Round location down and size up, and add offset of active area
225-
l0_location = tuple(
226-
int(self._l0_from_l(slide_level, l) + l0_off)
227-
for l, l0_off in zip(l_location, self._l0_offset)
243+
l0_location = (
244+
int(self._l0_from_l(slide_level, l_location[0]) + self._l0_offset[0]),
245+
int(self._l0_from_l(slide_level, l_location[1]) + self._l0_offset[1]),
228246
)
229-
l_size = tuple(
230-
int(min(math.ceil(self._l_from_z(dz_level, dz)), l_lim - math.ceil(l)))
231-
for l, dz, l_lim in zip(l_location, z_size, self._l_dimensions[slide_level])
247+
l_size = (
248+
int(
249+
min(
250+
math.ceil(self._l_from_z(dz_level, z_size[0])),
251+
self._l_dimensions[slide_level][0] - math.ceil(l_location[0]),
252+
)
253+
),
254+
int(
255+
min(
256+
math.ceil(self._l_from_z(dz_level, z_size[1])),
257+
self._l_dimensions[slide_level][1] - math.ceil(l_location[1]),
258+
)
259+
),
232260
)
233261

234262
# Return read_region() parameters plus tile size for final scaling
235263
return ((l0_location, slide_level, l_size), z_size)
236264

237-
def _l0_from_l(self, slide_level, l):
265+
def _l0_from_l(self, slide_level: int, l: float) -> float:
238266
return self._l0_l_downsamples[slide_level] * l
239267

240-
def _l_from_z(self, dz_level, z):
268+
def _l_from_z(self, dz_level: int, z: int) -> float:
241269
return self._l_z_downsamples[dz_level] * z
242270

243-
def _z_from_t(self, t):
271+
def _z_from_t(self, t: int) -> int:
244272
return self._z_t_downsample * t
245273

246-
def get_tile_coordinates(self, level, address):
274+
def get_tile_coordinates(
275+
self, level: int, address: tuple[int, int]
276+
) -> tuple[tuple[int, int], int, tuple[int, int]]:
247277
"""Return the OpenSlide.read_region() arguments for the specified tile.
248278
249279
Most users should call get_tile() rather than calling
@@ -254,15 +284,17 @@ def get_tile_coordinates(self, level, address):
254284
tuple."""
255285
return self._get_tile_info(level, address)[0]
256286

257-
def get_tile_dimensions(self, level, address):
287+
def get_tile_dimensions(
288+
self, level: int, address: tuple[int, int]
289+
) -> tuple[int, int]:
258290
"""Return a (pixels_x, pixels_y) tuple for the specified tile.
259291
260292
level: the Deep Zoom level.
261293
address: the address of the tile within the level as a (col, row)
262294
tuple."""
263295
return self._get_tile_info(level, address)[1]
264296

265-
def get_dzi(self, format):
297+
def get_dzi(self, format: str) -> str:
266298
"""Return a string containing the XML metadata for the .dzi file.
267299
268300
format: the format of the individual tiles ('png' or 'jpeg')"""

tests/test_deepzoom.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def tearDown(self):
3636
def test_repr(self):
3737
self.assertEqual(
3838
repr(self.dz),
39-
('DeepZoomGenerator(%r, tile_size=254, overlap=1, ' + 'limit_bounds=False)')
39+
('DeepZoomGenerator(%r, tile_size=254, overlap=1, " + "limit_bounds=False)')
4040
% self.osr,
4141
)
4242

0 commit comments

Comments
 (0)