Skip to content

Commit ca48866

Browse files
committed
openslide.deepzoom: Optionally render only the non-empty region
1 parent d3e824e commit ca48866

File tree

3 files changed

+49
-16
lines changed

3 files changed

+49
-16
lines changed

examples/deepzoom/deepzoom_server.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
#
33
# deepzoom_server - Example web application for serving whole-slide images
44
#
5-
# Copyright (c) 2010-2013 Carnegie Mellon University
5+
# Copyright (c) 2010-2014 Carnegie Mellon University
66
#
77
# This library is free software; you can redistribute it and/or modify it
88
# under the terms of version 2.1 of the GNU Lesser General Public License
@@ -30,6 +30,7 @@
3030
DEEPZOOM_FORMAT = 'jpeg'
3131
DEEPZOOM_TILE_SIZE = 256
3232
DEEPZOOM_OVERLAP = 1
33+
DEEPZOOM_LIMIT_BOUNDS = True
3334
DEEPZOOM_TILE_QUALITY = 75
3435
SLIDE_NAME = 'slide'
3536

@@ -46,6 +47,7 @@ def load_slide():
4647
config_map = {
4748
'DEEPZOOM_TILE_SIZE': 'tile_size',
4849
'DEEPZOOM_OVERLAP': 'overlap',
50+
'DEEPZOOM_LIMIT_BOUNDS': 'limit_bounds',
4951
}
5052
opts = dict((v, app.config[k]) for k, v in config_map.items())
5153
slide = open_slide(slidefile)
@@ -109,6 +111,9 @@ def slugify(text):
109111

110112
if __name__ == '__main__':
111113
parser = OptionParser(usage='Usage: %prog [options] [slide]')
114+
parser.add_option('-B', '--ignore-bounds', dest='DEEPZOOM_LIMIT_BOUNDS',
115+
default=True, action='store_false',
116+
help='display entire scan area')
112117
parser.add_option('-c', '--config', metavar='FILE', dest='config',
113118
help='config file')
114119
parser.add_option('-d', '--debug', dest='DEBUG', action='store_true',

examples/deepzoom/deepzoom_tile.py

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
#
33
# deepzoom_tile - Convert whole-slide images to Deep Zoom format
44
#
5-
# Copyright (c) 2010-2013 Carnegie Mellon University
5+
# Copyright (c) 2010-2014 Carnegie Mellon University
66
#
77
# This library is free software; you can redistribute it and/or modify it
88
# under the terms of version 2.1 of the GNU Lesser General Public License
@@ -37,13 +37,15 @@
3737
class TileWorker(Process):
3838
"""A child process that generates and writes tiles."""
3939

40-
def __init__(self, queue, slidepath, tile_size, overlap, quality):
40+
def __init__(self, queue, slidepath, tile_size, overlap, limit_bounds,
41+
quality):
4142
Process.__init__(self, name='TileWorker')
4243
self.daemon = True
4344
self._queue = queue
4445
self._slidepath = slidepath
4546
self._tile_size = tile_size
4647
self._overlap = overlap
48+
self._limit_bounds = limit_bounds
4749
self._quality = quality
4850
self._slide = None
4951

@@ -69,7 +71,8 @@ def _get_dz(self, associated=None):
6971
image = ImageSlide(self._slide.associated_images[associated])
7072
else:
7173
image = self._slide
72-
return DeepZoomGenerator(image, self._tile_size, self._overlap)
74+
return DeepZoomGenerator(image, self._tile_size, self._overlap,
75+
limit_bounds=self._limit_bounds)
7376

7477

7578
class DeepZoomImageTiler(object):
@@ -124,7 +127,7 @@ class DeepZoomStaticTiler(object):
124127
"""Handles generation of tiles and metadata for all images in a slide."""
125128

126129
def __init__(self, slidepath, basename, format, tile_size, overlap,
127-
quality, workers, with_viewer):
130+
limit_bounds, quality, workers, with_viewer):
128131
if with_viewer:
129132
# Check extra dependency before doing a bunch of work
130133
import jinja2
@@ -133,12 +136,14 @@ def __init__(self, slidepath, basename, format, tile_size, overlap,
133136
self._format = format
134137
self._tile_size = tile_size
135138
self._overlap = overlap
139+
self._limit_bounds = limit_bounds
136140
self._queue = JoinableQueue(2 * workers)
137141
self._workers = workers
138142
self._with_viewer = with_viewer
139143
self._dzi_data = {}
140144
for _i in range(workers):
141-
TileWorker(self._queue, slidepath, tile_size, overlap, quality).start()
145+
TileWorker(self._queue, slidepath, tile_size, overlap,
146+
limit_bounds, quality).start()
142147

143148
def run(self):
144149
self._run_image()
@@ -160,7 +165,8 @@ def _run_image(self, associated=None):
160165
else:
161166
image = ImageSlide(self._slide.associated_images[associated])
162167
basename = os.path.join(self._basename, self._slugify(associated))
163-
dz = DeepZoomGenerator(image, self._tile_size, self._overlap)
168+
dz = DeepZoomGenerator(image, self._tile_size, self._overlap,
169+
limit_bounds=self._limit_bounds)
164170
tiler = DeepZoomImageTiler(dz, basename, self._format, associated,
165171
self._queue)
166172
tiler.run()
@@ -219,6 +225,9 @@ def _shutdown(self):
219225

220226
if __name__ == '__main__':
221227
parser = OptionParser(usage='Usage: %prog [options] <slide>')
228+
parser.add_option('-B', '--ignore-bounds', dest='limit_bounds',
229+
default=True, action='store_false',
230+
help='display entire scan area')
222231
parser.add_option('-e', '--overlap', metavar='PIXELS', dest='overlap',
223232
type='int', default=1,
224233
help='overlap of adjacent tiles [1]')
@@ -249,5 +258,5 @@ def _shutdown(self):
249258
opts.basename = os.path.splitext(os.path.basename(slidepath))[0]
250259

251260
DeepZoomStaticTiler(slidepath, opts.basename, opts.format,
252-
opts.tile_size, opts.overlap, opts.quality, opts.workers,
253-
opts.with_viewer).run()
261+
opts.tile_size, opts.overlap, opts.limit_bounds, opts.quality,
262+
opts.workers, opts.with_viewer).run()

openslide/deepzoom.py

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#
22
# openslide-python - Python bindings for the OpenSlide library
33
#
4-
# Copyright (c) 2010-2012 Carnegie Mellon University
4+
# Copyright (c) 2010-2014 Carnegie Mellon University
55
#
66
# This library is free software; you can redistribute it and/or modify it
77
# under the terms of version 2.1 of the GNU Lesser General Public License
@@ -33,7 +33,12 @@
3333
class DeepZoomGenerator(object):
3434
"""Generates Deep Zoom tiles and metadata."""
3535

36-
def __init__(self, osr, tile_size=256, overlap=1):
36+
BOUNDS_OFFSET_PROPS = (openslide.PROPERTY_NAME_BOUNDS_X,
37+
openslide.PROPERTY_NAME_BOUNDS_Y)
38+
BOUNDS_SIZE_PROPS = (openslide.PROPERTY_NAME_BOUNDS_WIDTH,
39+
openslide.PROPERTY_NAME_BOUNDS_HEIGHT)
40+
41+
def __init__(self, osr, tile_size=256, overlap=1, limit_bounds=False):
3742
"""Create a DeepZoomGenerator wrapping an OpenSlide object.
3843
3944
osr: a slide object.
@@ -52,8 +57,22 @@ def __init__(self, osr, tile_size=256, overlap=1):
5257
self._z_overlap = overlap
5358

5459
# Precompute dimensions
55-
# Slide level
56-
self._l_dimensions = osr.level_dimensions
60+
# Slide level and offset
61+
if limit_bounds:
62+
# Level 0 coordinate offset
63+
self._l0_offset = tuple(int(osr.properties.get(prop, 0))
64+
for prop in self.BOUNDS_OFFSET_PROPS)
65+
# Slide level dimensions scale factor in each axis
66+
size_scale = tuple(int(osr.properties.get(prop, l0_lim)) / l0_lim
67+
for prop, l0_lim in zip(self.BOUNDS_SIZE_PROPS,
68+
osr.dimensions))
69+
# Dimensions of active area
70+
self._l_dimensions = tuple(tuple(int(math.ceil(l_lim * scale))
71+
for l_lim, scale in zip(l_size, size_scale))
72+
for l_size in osr.level_dimensions)
73+
else:
74+
self._l_dimensions = osr.level_dimensions
75+
self._l0_offset = (0, 0)
5776
self._l0_dimensions = self._l_dimensions[0]
5877
# Deep Zoom level
5978
z_size = self._l0_dimensions
@@ -160,9 +179,9 @@ def _get_tile_info(self, dz_level, t_location):
160179
z_location = [self._z_from_t(t) for t in t_location]
161180
l_location = [self._l_from_z(dz_level, z - z_tl)
162181
for z, z_tl in zip(z_location, z_overlap_tl)]
163-
# Round location down and size up
164-
l0_location = tuple(int(self._l0_from_l(slide_level, l))
165-
for l in l_location)
182+
# Round location down and size up, and add offset of active area
183+
l0_location = tuple(int(self._l0_from_l(slide_level, l) + l0_off)
184+
for l, l0_off in zip(l_location, self._l0_offset))
166185
l_size = tuple(int(min(math.ceil(self._l_from_z(dz_level, dz)),
167186
l_lim - math.ceil(l)))
168187
for l, dz, l_lim in

0 commit comments

Comments
 (0)