2525
2626from io import BytesIO
2727import math
28+ from typing import List , Tuple
2829from xml .etree .ElementTree import Element , ElementTree , SubElement
2930
3031from PIL import Image
@@ -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.
@@ -79,13 +86,13 @@ def __init__(self, osr, tile_size=254, overlap=1, limit_bounds=False):
7986 for prop , l0_lim in zip (self .BOUNDS_SIZE_PROPS , osr .dimensions )
8087 )
8188 # Dimensions of active area
82- self ._l_dimensions = tuple (
83- tuple (
84- int (math .ceil (l_lim * scale ))
85- for l_lim , scale in zip ( l_size , size_scale )
89+ self ._l_dimensions = [
90+ (
91+ int (math .ceil (l_size [ 0 ] * size_scale [ 0 ])),
92+ int ( math . ceil ( l_size [ 1 ] * size_scale [ 1 ])),
8693 )
8794 for l_size in osr .level_dimensions
88- )
95+ ]
8996 else :
9097 self ._l_dimensions = osr .level_dimensions
9198 self ._l0_offset = (0 , 0 )
@@ -94,25 +101,28 @@ def __init__(self, osr, tile_size=254, overlap=1, limit_bounds=False):
94101 z_size = self ._l0_dimensions
95102 z_dimensions = [z_size ]
96103 while z_size [0 ] > 1 or z_size [1 ] > 1 :
97- z_size = tuple (max (1 , int (math .ceil (z / 2 ))) for z in z_size )
104+ z_size = (
105+ max (1 , int (math .ceil (z_size [0 ] / 2 ))),
106+ max (1 , int (math .ceil (z_size [1 ] / 2 ))),
107+ )
98108 z_dimensions .append (z_size )
99- self ._z_dimensions = tuple (reversed (z_dimensions ))
109+ self ._z_dimensions = list (reversed (z_dimensions ))
100110
101111 # Tile
102- def tiles (z_lim ) :
112+ def tiles (z_lim : int ) -> int :
103113 return int (math .ceil (z_lim / self ._z_t_downsample ))
104114
105- self ._t_dimensions = tuple (
115+ self ._t_dimensions = [
106116 (tiles (z_w ), tiles (z_h )) for z_w , z_h in self ._z_dimensions
107- )
117+ ]
108118
109119 # Deep Zoom level count
110120 self ._dz_levels = len (self ._z_dimensions )
111121
112122 # Total downsamples for each Deep Zoom level
113- l0_z_downsamples = tuple (
123+ l0_z_downsamples : List [ int ] = [
114124 2 ** (self ._dz_levels - dz_level - 1 ) for dz_level in range (self ._dz_levels )
115- )
125+ ]
116126
117127 # Preferred slide levels for each Deep Zoom level
118128 self ._slide_from_dz_level = tuple (
@@ -121,19 +131,19 @@ def tiles(z_lim):
121131
122132 # Piecewise downsamples
123133 self ._l0_l_downsamples = self ._osr .level_downsamples
124- self ._l_z_downsamples = tuple (
134+ self ._l_z_downsamples = [
125135 l0_z_downsamples [dz_level ]
126136 / self ._l0_l_downsamples [self ._slide_from_dz_level [dz_level ]]
127137 for dz_level in range (self ._dz_levels )
128- )
138+ ]
129139
130140 # Slide background color
131- self ._bg_color = '#' + self ._osr .properties .get (
132- openslide .PROPERTY_NAME_BACKGROUND_COLOR , ' ffffff'
141+ self ._bg_color = "#" + self ._osr .properties .get (
142+ openslide .PROPERTY_NAME_BACKGROUND_COLOR , " ffffff"
133143 )
134144
135- def __repr__ (self ):
136- return ' {}({!r}, tile_size={!r}, overlap={!r}, limit_bounds={!r})' .format (
145+ def __repr__ (self ) -> str :
146+ return " {}({!r}, tile_size={!r}, overlap={!r}, limit_bounds={!r})" .format (
137147 self .__class__ .__name__ ,
138148 self ._osr ,
139149 self ._z_t_downsample ,
@@ -142,26 +152,26 @@ def __repr__(self):
142152 )
143153
144154 @property
145- def level_count (self ):
155+ def level_count (self ) -> int :
146156 """The number of Deep Zoom levels in the image."""
147157 return self ._dz_levels
148158
149159 @property
150- def level_tiles (self ):
160+ def level_tiles (self ) -> List [ Tuple [ int , int ]] :
151161 """A list of (tiles_x, tiles_y) tuples for each Deep Zoom level."""
152162 return self ._t_dimensions
153163
154164 @property
155- def level_dimensions (self ):
165+ def level_dimensions (self ) -> List [ Tuple [ int , int ]] :
156166 """A list of (pixels_x, pixels_y) tuples for each Deep Zoom level."""
157167 return self ._z_dimensions
158168
159169 @property
160- def tile_count (self ):
170+ def tile_count (self ) -> int :
161171 """The total number of Deep Zoom tiles in the image."""
162172 return sum (t_cols * t_rows for t_cols , t_rows in self ._t_dimensions )
163173
164- def get_tile (self , level , address ) :
174+ def get_tile (self , level : int , address : Tuple [ int , int ]) -> Image . Image :
165175 """Return an RGB PIL.Image for a tile.
166176
167177 level: the Deep Zoom level.
@@ -171,25 +181,27 @@ def get_tile(self, level, address):
171181 # Read tile
172182 args , z_size = self ._get_tile_info (level , address )
173183 tile = self ._osr .read_region (* args )
174- profile = tile .info .get (' icc_profile' )
184+ profile = tile .info .get (" icc_profile" )
175185
176186 # Apply on solid background
177- bg = Image .new (' RGB' , tile .size , self ._bg_color )
187+ bg = Image .new (" RGB" , tile .size , self ._bg_color )
178188 tile = Image .composite (tile , bg , tile )
179189
180190 # Scale to the correct size
181191 if tile .size != z_size :
182192 # Image.Resampling added in Pillow 9.1.0
183193 # Image.LANCZOS removed in Pillow 10
184- tile .thumbnail (z_size , getattr (Image , ' Resampling' , Image ).LANCZOS )
194+ tile .thumbnail (z_size , getattr (Image , " Resampling" , Image ).LANCZOS )
185195
186196 # Reference ICC profile
187197 if profile is not None :
188- tile .info [' icc_profile' ] = profile
198+ tile .info [" icc_profile" ] = profile
189199
190200 return tile
191201
192- def _get_tile_info (self , dz_level , t_location ):
202+ def _get_tile_info (
203+ self , dz_level : int , t_location : Tuple [int , int ]
204+ ) -> Tuple [Tuple [Tuple [int , int ], int , Tuple [int , int ]], Tuple [int , int ]]:
193205 # Check parameters
194206 if dz_level < 0 or dz_level >= self ._dz_levels :
195207 raise ValueError ("Invalid level" )
@@ -208,42 +220,62 @@ def _get_tile_info(self, dz_level, t_location):
208220 )
209221
210222 # 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
223+ z_size = (
224+ min (
225+ self ._z_t_downsample ,
226+ self ._z_dimensions [dz_level ][0 ] - self ._z_t_downsample * t_location [0 ],
227+ )
228+ + z_overlap_tl [0 ]
229+ + z_overlap_br [0 ],
230+ min (
231+ self ._z_t_downsample ,
232+ self ._z_dimensions [dz_level ][1 ] - self ._z_t_downsample * t_location [1 ],
215233 )
234+ + z_overlap_tl [1 ]
235+ + z_overlap_br [1 ],
216236 )
217237
218238 # 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- ]
239+ z_location = ( self ._z_from_t (t_location [ 0 ]), self . _z_from_t ( t_location [ 1 ]))
240+ l_location = (
241+ self ._l_from_z (dz_level , z_location [ 0 ] - z_overlap_tl [ 0 ]),
242+ self . _l_from_z ( dz_level , z_location [ 1 ] - z_overlap_tl [ 1 ]),
243+ )
224244 # 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 )
245+ l0_location = (
246+ int (self ._l0_from_l (slide_level , l_location [ 0 ] ) + self . _l0_offset [ 0 ]),
247+ int ( self . _l0_from_l ( slide_level , l_location [ 1 ]) + self ._l0_offset [ 1 ]),
228248 )
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 ])
249+ l_size = (
250+ int (
251+ min (
252+ math .ceil (self ._l_from_z (dz_level , z_size [0 ])),
253+ self ._l_dimensions [slide_level ][0 ] - math .ceil (l_location [0 ]),
254+ )
255+ ),
256+ int (
257+ min (
258+ math .ceil (self ._l_from_z (dz_level , z_size [1 ])),
259+ self ._l_dimensions [slide_level ][1 ] - math .ceil (l_location [1 ]),
260+ )
261+ ),
232262 )
233263
234264 # Return read_region() parameters plus tile size for final scaling
235265 return ((l0_location , slide_level , l_size ), z_size )
236266
237- def _l0_from_l (self , slide_level , l ) :
267+ def _l0_from_l (self , slide_level : int , l : float ) -> float :
238268 return self ._l0_l_downsamples [slide_level ] * l
239269
240- def _l_from_z (self , dz_level , z ) :
270+ def _l_from_z (self , dz_level : int , z : int ) -> float :
241271 return self ._l_z_downsamples [dz_level ] * z
242272
243- def _z_from_t (self , t ) :
273+ def _z_from_t (self , t : int ) -> int :
244274 return self ._z_t_downsample * t
245275
246- def get_tile_coordinates (self , level , address ):
276+ def get_tile_coordinates (
277+ self , level : int , address : Tuple [int , int ]
278+ ) -> Tuple [Tuple [int , int ], int , Tuple [int , int ]]:
247279 """Return the OpenSlide.read_region() arguments for the specified tile.
248280
249281 Most users should call get_tile() rather than calling
@@ -254,28 +286,30 @@ def get_tile_coordinates(self, level, address):
254286 tuple."""
255287 return self ._get_tile_info (level , address )[0 ]
256288
257- def get_tile_dimensions (self , level , address ):
289+ def get_tile_dimensions (
290+ self , level : int , address : Tuple [int , int ]
291+ ) -> Tuple [int , int ]:
258292 """Return a (pixels_x, pixels_y) tuple for the specified tile.
259293
260294 level: the Deep Zoom level.
261295 address: the address of the tile within the level as a (col, row)
262296 tuple."""
263297 return self ._get_tile_info (level , address )[1 ]
264298
265- def get_dzi (self , format ) :
299+ def get_dzi (self , format : str ) -> str :
266300 """Return a string containing the XML metadata for the .dzi file.
267301
268302 format: the format of the individual tiles ('png' or 'jpeg')"""
269303 image = Element (
270- ' Image' ,
304+ " Image" ,
271305 TileSize = str (self ._z_t_downsample ),
272306 Overlap = str (self ._z_overlap ),
273307 Format = format ,
274- xmlns = ' http://schemas.microsoft.com/deepzoom/2008' ,
308+ xmlns = " http://schemas.microsoft.com/deepzoom/2008" ,
275309 )
276310 w , h = self ._l0_dimensions
277- SubElement (image , ' Size' , Width = str (w ), Height = str (h ))
311+ SubElement (image , " Size" , Width = str (w ), Height = str (h ))
278312 tree = ElementTree (element = image )
279313 buf = BytesIO ()
280- tree .write (buf , encoding = ' UTF-8' )
281- return buf .getvalue ().decode (' UTF-8' )
314+ tree .write (buf , encoding = " UTF-8" )
315+ return buf .getvalue ().decode (" UTF-8" )
0 commit comments