22
22
This module provides functionality for generating Deep Zoom images from
23
23
OpenSlide objects.
24
24
"""
25
+ from __future__ import annotations
25
26
26
27
from io import BytesIO
27
28
import math
@@ -44,7 +45,13 @@ class DeepZoomGenerator:
44
45
openslide .PROPERTY_NAME_BOUNDS_HEIGHT ,
45
46
)
46
47
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
+ ):
48
55
"""Create a DeepZoomGenerator wrapping an OpenSlide object.
49
56
50
57
osr: a slide object.
@@ -99,7 +106,7 @@ def __init__(self, osr, tile_size=254, overlap=1, limit_bounds=False):
99
106
self ._z_dimensions = tuple (reversed (z_dimensions ))
100
107
101
108
# Tile
102
- def tiles (z_lim ) :
109
+ def tiles (z_lim : int ) -> int :
103
110
return int (math .ceil (z_lim / self ._z_t_downsample ))
104
111
105
112
self ._t_dimensions = tuple (
@@ -110,7 +117,8 @@ def tiles(z_lim):
110
117
self ._dz_levels = len (self ._z_dimensions )
111
118
112
119
# 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 (
114
122
2 ** (self ._dz_levels - dz_level - 1 ) for dz_level in range (self ._dz_levels )
115
123
)
116
124
@@ -132,7 +140,7 @@ def tiles(z_lim):
132
140
openslide .PROPERTY_NAME_BACKGROUND_COLOR , 'ffffff'
133
141
)
134
142
135
- def __repr__ (self ):
143
+ def __repr__ (self ) -> str :
136
144
return '{}({!r}, tile_size={!r}, overlap={!r}, limit_bounds={!r})' .format (
137
145
self .__class__ .__name__ ,
138
146
self ._osr ,
@@ -142,26 +150,26 @@ def __repr__(self):
142
150
)
143
151
144
152
@property
145
- def level_count (self ):
153
+ def level_count (self ) -> int :
146
154
"""The number of Deep Zoom levels in the image."""
147
155
return self ._dz_levels
148
156
149
157
@property
150
- def level_tiles (self ):
158
+ def level_tiles (self ) -> tuple [ tuple [ int , int ], ...] :
151
159
"""A list of (tiles_x, tiles_y) tuples for each Deep Zoom level."""
152
160
return self ._t_dimensions
153
161
154
162
@property
155
- def level_dimensions (self ):
163
+ def level_dimensions (self ) -> tuple [ tuple [ int , ...], ...] :
156
164
"""A list of (pixels_x, pixels_y) tuples for each Deep Zoom level."""
157
165
return self ._z_dimensions
158
166
159
167
@property
160
- def tile_count (self ):
168
+ def tile_count (self ) -> int :
161
169
"""The total number of Deep Zoom tiles in the image."""
162
170
return sum (t_cols * t_rows for t_cols , t_rows in self ._t_dimensions )
163
171
164
- def get_tile (self , level , address ) :
172
+ def get_tile (self , level : int , address : tuple [ int , int ]) -> Image . Image :
165
173
"""Return an RGB PIL.Image for a tile.
166
174
167
175
level: the Deep Zoom level.
@@ -189,7 +197,9 @@ def get_tile(self, level, address):
189
197
190
198
return tile
191
199
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 ]]:
193
203
# Check parameters
194
204
if dz_level < 0 or dz_level >= self ._dz_levels :
195
205
raise ValueError ("Invalid level" )
@@ -208,42 +218,62 @@ def _get_tile_info(self, dz_level, t_location):
208
218
)
209
219
210
220
# 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 ],
215
225
)
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 ],
216
234
)
217
235
218
236
# 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
+ )
224
242
# 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 ]),
228
246
)
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
+ ),
232
260
)
233
261
234
262
# Return read_region() parameters plus tile size for final scaling
235
263
return ((l0_location , slide_level , l_size ), z_size )
236
264
237
- def _l0_from_l (self , slide_level , l ) :
265
+ def _l0_from_l (self , slide_level : int , l : float ) -> float :
238
266
return self ._l0_l_downsamples [slide_level ] * l
239
267
240
- def _l_from_z (self , dz_level , z ) :
268
+ def _l_from_z (self , dz_level : int , z : int ) -> float :
241
269
return self ._l_z_downsamples [dz_level ] * z
242
270
243
- def _z_from_t (self , t ) :
271
+ def _z_from_t (self , t : int ) -> int :
244
272
return self ._z_t_downsample * t
245
273
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 ]]:
247
277
"""Return the OpenSlide.read_region() arguments for the specified tile.
248
278
249
279
Most users should call get_tile() rather than calling
@@ -254,15 +284,17 @@ def get_tile_coordinates(self, level, address):
254
284
tuple."""
255
285
return self ._get_tile_info (level , address )[0 ]
256
286
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 ]:
258
290
"""Return a (pixels_x, pixels_y) tuple for the specified tile.
259
291
260
292
level: the Deep Zoom level.
261
293
address: the address of the tile within the level as a (col, row)
262
294
tuple."""
263
295
return self ._get_tile_info (level , address )[1 ]
264
296
265
- def get_dzi (self , format ) :
297
+ def get_dzi (self , format : str ) -> str :
266
298
"""Return a string containing the XML metadata for the .dzi file.
267
299
268
300
format: the format of the individual tiles ('png' or 'jpeg')"""
0 commit comments