1+ import math
12import numpy as np
23import re
34import tensorflow as tf
@@ -45,13 +46,6 @@ def __call__(self, study_description):
4546 # cProfile.runctx("self._designate_chunks_for_tiles(study_description)", globals=globals(), locals=locals(), sort="cumulative")
4647 # print("_designate_chunks_for_tiles done")
4748
48- self .number_pixel_rows_for_tile = tf .convert_to_tensor (
49- study_description ["number_pixel_rows_for_tile" ]
50- )
51- self .number_pixel_columns_for_tile = tf .convert_to_tensor (
52- study_description ["number_pixel_columns_for_tile" ]
53- )
54-
5549 # Start converting our description into tensors.
5650 study_as_tensors = {
5751 study_key : [tf .convert_to_tensor (study_description [study_key ])]
@@ -67,7 +61,7 @@ def __call__(self, study_description):
6761 ** {
6862 slide_key : [tf .convert_to_tensor (slide_description [slide_key ])]
6963 for slide_key in slide_description .keys ()
70- if slide_key != " chunks"
64+ if slide_key not in [ "tiles" , " chunks"]
7165 },
7266 }
7367
@@ -119,7 +113,8 @@ def __call__(self, study_description):
119113 # on additional elements to the tuple so that the form is (inputs, targets,
120114 # sample_weights).
121115 study_dataset = study_dataset .map (
122- lambda elem : ((elem .pop ("tile_pixels" ), elem ), None , None ), ** self .dataset_map_options
116+ lambda elem : ((elem .pop ("tile_pixels" ), elem ), None , None ),
117+ ** self .dataset_map_options ,
123118 )
124119 # print("pop done")
125120
@@ -154,7 +149,6 @@ def _designate_chunks_for_tiles(self, study_description):
154149 number_pixel_columns_for_chunk = slide ["number_pixel_columns_for_chunk" ]
155150
156151 tiles_as_sorted_list = list (slide ["tiles" ].items ())
157- del slide ["tiles" ]
158152 tiles_as_sorted_list .sort (
159153 key = lambda x : x [1 ]["tile_left" ]
160154 ) # second priority key
@@ -174,51 +168,23 @@ def _designate_chunks_for_tiles(self, study_description):
174168 }
175169 number_of_chunks += 1
176170
177- if True :
178- # This implementations has a run time that is quadratic in the
179- # number of tiles that a slide has. It is too slow; we should make
180- # it faster.
181- tiles = chunk ["tiles" ] = {}
182- subsequent_chunks = []
183- for tile in tiles_as_sorted_list :
184- if (
185- tile [1 ]["tile_top" ] + number_pixel_rows_for_tile
186- <= chunk ["chunk_bottom" ]
187- and tile [1 ]["tile_left" ] + number_pixel_columns_for_tile
188- <= chunk ["chunk_right" ]
189- and tile [1 ]["tile_left" ] >= chunk ["chunk_left" ]
190- and tile [1 ]["tile_top" ] >= chunk ["chunk_top" ]
191- ):
192- tiles [tile [0 ]] = tile [1 ]
193- else :
194- subsequent_chunks .append (tile )
195-
196- else :
197- # This implementations has a run time that is quadratic in the
198- # number of tiles that a slide has. It is even slower than the
199- # above.
200- tiles = chunk ["tiles" ] = {
201- tile [0 ]: tile [1 ]
202- for tile in tiles_as_sorted_list
203- if tile [1 ]["tile_top" ] + number_pixel_rows_for_tile
171+ # This implementation has a run time that is quadratic
172+ # in the number of tiles that a slide has. It is too
173+ # slow; we should make it faster.
174+ tiles = chunk ["tiles" ] = {}
175+ subsequent_chunks = []
176+ for tile in tiles_as_sorted_list :
177+ if (
178+ tile [1 ]["tile_top" ] + number_pixel_rows_for_tile
204179 <= chunk ["chunk_bottom" ]
205180 and tile [1 ]["tile_left" ] + number_pixel_columns_for_tile
206181 <= chunk ["chunk_right" ]
207182 and tile [1 ]["tile_left" ] >= chunk ["chunk_left" ]
208183 and tile [1 ]["tile_top" ] >= chunk ["chunk_top" ]
209- }
210- subsequent_chunks = [
211- tile
212- for tile in tiles_as_sorted_list
213- if not (
214- tile [1 ]["tile_top" ] + number_pixel_rows_for_tile
215- <= chunk ["chunk_bottom" ]
216- and tile [1 ]["tile_left" ] + number_pixel_columns_for_tile
217- <= chunk ["chunk_right" ]
218- and tile [1 ]["tile_left" ] >= chunk ["chunk_left" ]
219- and tile [1 ]["tile_top" ] >= chunk ["chunk_top" ]
220- )
221- ]
184+ ):
185+ tiles [tile [0 ]] = tile [1 ]
186+ else :
187+ subsequent_chunks .append (tile )
222188
223189 # Update the list of tiles that are not yet in chunks
224190 tiles_as_sorted_list = subsequent_chunks
@@ -242,7 +208,11 @@ def _designate_chunks_for_tiles(self, study_description):
242208
243209 @tf .function
244210 def _read_and_split_chunk_pixels (self , elem ):
245- # Get chunk's pixel data from disk and load it into chunk_pixels_as_tensor
211+ # Get chunk's pixel data from disk and load it into
212+ # chunk_pixels_as_tensor. Note that if elem["factor"] differs
213+ # from 1.0 then this chunk will have number_of_rows
214+ # ((chunk_bottom - chunk_top) / factor, and number_of_columns
215+ # = ((chunk_right - chunk_left) / factor.
246216 chunk_pixels_as_tensor = tf .py_function (
247217 func = self ._py_read_chunk_pixels ,
248218 inp = [
@@ -252,12 +222,42 @@ def _read_and_split_chunk_pixels(self, elem):
252222 elem ["chunk_right" ],
253223 elem ["filename" ],
254224 elem ["level" ],
225+ elem ["factor" ],
255226 ],
256227 Tout = tf .uint8 ,
257228 )
258229 number_of_tiles = tf .size (elem ["tiles_top" ])
259230 tiles = tf .TensorArray (dtype = tf .uint8 , size = number_of_tiles )
260231
232+ scaled_number_pixel_rows_for_tile = tf .cast (
233+ tf .math .floor (
234+ tf .cast (elem ["number_pixel_rows_for_tile" ], dtype = tf .float64 )
235+ / elem ["factor" ]
236+ + 0.01
237+ ),
238+ dtype = tf .int32 ,
239+ )
240+ scaled_number_pixel_columns_for_tile = tf .cast (
241+ tf .math .floor (
242+ tf .cast (elem ["number_pixel_columns_for_tile" ], dtype = tf .float64 )
243+ / elem ["factor" ]
244+ + 0.01
245+ ),
246+ dtype = tf .int32 ,
247+ )
248+ scaled_chunk_top = tf .cast (
249+ tf .math .floor (
250+ tf .cast (elem ["chunk_top" ], dtype = tf .float64 ) / elem ["factor" ] + 0.01
251+ ),
252+ dtype = tf .int32 ,
253+ )
254+ scaled_chunk_left = tf .cast (
255+ tf .math .floor (
256+ tf .cast (elem ["chunk_left" ], dtype = tf .float64 ) / elem ["factor" ] + 0.01
257+ ),
258+ dtype = tf .int32 ,
259+ )
260+
261261 def condition (i , _ ):
262262 return tf .less (i , number_of_tiles )
263263
@@ -268,10 +268,30 @@ def body(i, tiles):
268268 i ,
269269 tf .image .crop_to_bounding_box (
270270 chunk_pixels_as_tensor ,
271- tf .gather (elem ["tiles_top" ], i ) - elem ["chunk_top" ],
272- tf .gather (elem ["tiles_left" ], i ) - elem ["chunk_left" ],
273- elem ["number_pixel_rows_for_tile" ],
274- elem ["number_pixel_columns_for_tile" ],
271+ tf .cast (
272+ tf .math .floor (
273+ tf .cast (
274+ tf .gather (elem ["tiles_top" ], i ), dtype = tf .float64
275+ )
276+ / elem ["factor" ]
277+ + 0.01
278+ ),
279+ dtype = tf .int32 ,
280+ )
281+ - scaled_chunk_top ,
282+ tf .cast (
283+ tf .math .floor (
284+ tf .cast (
285+ tf .gather (elem ["tiles_left" ], i ), dtype = tf .float64
286+ )
287+ / elem ["factor" ]
288+ + 0.01
289+ ),
290+ dtype = tf .int32 ,
291+ )
292+ - scaled_chunk_left ,
293+ scaled_number_pixel_rows_for_tile ,
294+ scaled_number_pixel_columns_for_tile ,
275295 ),
276296 ),
277297 )
@@ -293,15 +313,15 @@ def body(i, tiles):
293313 return response
294314
295315 def _py_read_chunk_pixels (
296- self , chunk_top , chunk_left , chunk_bottom , chunk_right , filename , level = - 1
316+ self , chunk_top , chunk_left , chunk_bottom , chunk_right , filename , level , factor
297317 ):
298318 """Read from disk all the pixel data for a specific chunk of the whole slide."""
299319
300320 filename = filename .numpy ().decode ("utf-8" )
301- chunk_top = chunk_top .numpy ()
302- chunk_left = chunk_left .numpy ()
303- chunk_bottom = chunk_bottom .numpy ()
304- chunk_right = chunk_right .numpy ()
321+ chunk_top = math . floor ( chunk_top .numpy () / factor . numpy () + 0.01 )
322+ chunk_left = math . floor ( chunk_left .numpy () / factor . numpy () + 0.01 )
323+ chunk_bottom = math . floor ( chunk_bottom .numpy () / factor . numpy () + 0.01 )
324+ chunk_right = math . floor ( chunk_right .numpy () / factor . numpy () + 0.01 )
305325 level = level .numpy ()
306326
307327 if re .compile (r"\.svs$" ).search (filename ):
0 commit comments