Skip to content

Commit aa29826

Browse files
authored
Merge pull request #49 from Leengit/use_factor
ENH: Change the tile size based upon the value of the magnification factor.
2 parents 704ea5b + 87ab660 commit aa29826

File tree

4 files changed

+108
-77
lines changed

4 files changed

+108
-77
lines changed

histomics_stream/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Whole-slide image streamer for TensorFlow."""
22

3-
__version__ = "2.1.3"
3+
__version__ = "2.1.4"
44

55
"""
66

histomics_stream/configure.py

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -103,10 +103,21 @@ def __call__(self, slide):
103103
self.desired_magnification, estimated, self.magnification_tolerance
104104
)
105105

106-
number_pixel_columns_for_slide = int(
107-
ts.sizeX * estimated[level] // objective
106+
# First compute the size of the slide using the chosen level
107+
number_pixel_rows_for_slide = math.floor(
108+
ts.sizeY * estimated[level] / objective
109+
)
110+
number_pixel_columns_for_slide = math.floor(
111+
ts.sizeX * estimated[level] / objective
112+
)
113+
# Then adjust the size of the slide as if the desired magnification were
114+
# exactly obtained
115+
number_pixel_rows_for_slide = math.floor(
116+
number_pixel_rows_for_slide * factor
117+
)
118+
number_pixel_columns_for_slide = math.floor(
119+
number_pixel_columns_for_slide * factor
108120
)
109-
number_pixel_rows_for_slide = int(ts.sizeY * estimated[level] // objective)
110121

111122
# Rather than as the index into preferred_levels, change
112123
# level to be the value that large_image uses
@@ -129,10 +140,9 @@ def __call__(self, slide):
129140
self.desired_magnification, estimated, self.magnification_tolerance
130141
)
131142

132-
# get slide number_pixel_columns_for_slide,
133-
# number_pixel_rows_for_slide at desired
134-
# magnification. (Note number_pixel_columns_for_slide
135-
# before number_pixel_rows_for_slide)
143+
# get slide number_pixel_columns_for_slide, number_pixel_rows_for_slide at
144+
# desired magnification. (Note that number_pixel_columns_for_slide is before
145+
# number_pixel_rows_for_slide)
136146
(
137147
number_pixel_columns_for_slide,
138148
number_pixel_rows_for_slide,
@@ -156,9 +166,8 @@ def __call__(self, slide):
156166
self.desired_magnification, estimated, self.magnification_tolerance
157167
)
158168

159-
# get slide number_pixel_columns_for_slide,
160-
# number_pixel_rows_for_slide at desired
161-
# magnification. (Note number_pixel_rows_for_slide before
169+
# get slide number_pixel_columns_for_slide, number_pixel_rows_for_slide at
170+
# desired magnification. (Note that number_pixel_rows_for_slide is before
162171
# number_pixel_columns_for_slide)
163172
number_pixel_rows_for_slide, number_pixel_columns_for_slide = source_group[
164173
format(level)
@@ -172,10 +181,16 @@ def __call__(self, slide):
172181
level = 0
173182
factor = 1.0
174183
pil_obj = Image.open(filename)
184+
# (Note that number_pixel_columns_for_slide is before
185+
# number_pixel_rows_for_slide)
175186
number_pixel_columns_for_slide, number_pixel_rows_for_slide = pil_obj.size
176187

177188
slide["level"] = level
178189
slide["factor"] = factor
190+
# Note that slide size is defined by the requested magnification, which may not
191+
# be the same as the magnification for the selected level. To get the slide
192+
# size for the magnification that we are using, these values must later be
193+
# divided by slide["factor"].
179194
slide["number_pixel_rows_for_slide"] = number_pixel_rows_for_slide
180195
slide["number_pixel_columns_for_slide"] = number_pixel_columns_for_slide
181196

@@ -553,11 +568,7 @@ class TilesRandomly:
553568
554569
"""
555570

556-
def __init__(
557-
self,
558-
study,
559-
randomly_select=1, # Defaults to select one
560-
):
571+
def __init__(self, study, randomly_select=1): # Defaults to select one
561572
"""Sanity check the supplied parameters and store them for later use."""
562573
# Check values.
563574
if not ("version" in study and study["version"] == "version-1"):

histomics_stream/tensorflow.py

Lines changed: 80 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import math
12
import numpy as np
23
import re
34
import 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):

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
setuptools.setup(
77
name="histomics_stream",
8-
version="2.1.3",
8+
version="2.1.4",
99
author="Lee Newberg",
1010
author_email="lee.newberg@kitware.com",
1111
description="A TensorFlow 2 package for reading whole slide images",

0 commit comments

Comments
 (0)