Skip to content

Commit d986427

Browse files
authored
Merge pull request #200 from bgilbert/flask
examples/deepzoom: do app initialization from a factory function
2 parents 7557499 + ee0359b commit d986427

File tree

2 files changed

+170
-161
lines changed

2 files changed

+170
-161
lines changed

examples/deepzoom/deepzoom_multiserver.py

Lines changed: 98 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -47,18 +47,96 @@
4747
from openslide import OpenSlide, OpenSlideCache, OpenSlideError, OpenSlideVersionError
4848
from openslide.deepzoom import DeepZoomGenerator
4949

50-
SLIDE_DIR = '.'
51-
SLIDE_CACHE_SIZE = 10
52-
SLIDE_TILE_CACHE_MB = 128
53-
DEEPZOOM_FORMAT = 'jpeg'
54-
DEEPZOOM_TILE_SIZE = 254
55-
DEEPZOOM_OVERLAP = 1
56-
DEEPZOOM_LIMIT_BOUNDS = True
57-
DEEPZOOM_TILE_QUALITY = 75
5850

59-
app = Flask(__name__)
60-
app.config.from_object(__name__)
61-
app.config.from_envvar('DEEPZOOM_MULTISERVER_SETTINGS', silent=True)
51+
def create_app(config=None, config_file=None):
52+
# Create and configure app
53+
app = Flask(__name__)
54+
app.config.from_mapping(
55+
SLIDE_DIR='.',
56+
SLIDE_CACHE_SIZE=10,
57+
SLIDE_TILE_CACHE_MB=128,
58+
DEEPZOOM_FORMAT='jpeg',
59+
DEEPZOOM_TILE_SIZE=254,
60+
DEEPZOOM_OVERLAP=1,
61+
DEEPZOOM_LIMIT_BOUNDS=True,
62+
DEEPZOOM_TILE_QUALITY=75,
63+
)
64+
app.config.from_envvar('DEEPZOOM_MULTISERVER_SETTINGS', silent=True)
65+
if config_file is not None:
66+
app.config.from_pyfile(config_file)
67+
if config is not None:
68+
app.config.from_mapping(config)
69+
70+
# Set up cache
71+
app.basedir = os.path.abspath(app.config['SLIDE_DIR'])
72+
config_map = {
73+
'DEEPZOOM_TILE_SIZE': 'tile_size',
74+
'DEEPZOOM_OVERLAP': 'overlap',
75+
'DEEPZOOM_LIMIT_BOUNDS': 'limit_bounds',
76+
}
77+
opts = {v: app.config[k] for k, v in config_map.items()}
78+
app.cache = _SlideCache(
79+
app.config['SLIDE_CACHE_SIZE'], app.config['SLIDE_TILE_CACHE_MB'], opts
80+
)
81+
82+
# Helper functions
83+
def get_slide(path):
84+
path = os.path.abspath(os.path.join(app.basedir, path))
85+
if not path.startswith(app.basedir + os.path.sep):
86+
# Directory traversal
87+
abort(404)
88+
if not os.path.exists(path):
89+
abort(404)
90+
try:
91+
slide = app.cache.get(path)
92+
slide.filename = os.path.basename(path)
93+
return slide
94+
except OpenSlideError:
95+
abort(404)
96+
97+
# Set up routes
98+
@app.route('/')
99+
def index():
100+
return render_template('files.html', root_dir=_Directory(app.basedir))
101+
102+
@app.route('/<path:path>')
103+
def slide(path):
104+
slide = get_slide(path)
105+
slide_url = url_for('dzi', path=path)
106+
return render_template(
107+
'slide-fullpage.html',
108+
slide_url=slide_url,
109+
slide_filename=slide.filename,
110+
slide_mpp=slide.mpp,
111+
)
112+
113+
@app.route('/<path:path>.dzi')
114+
def dzi(path):
115+
slide = get_slide(path)
116+
format = app.config['DEEPZOOM_FORMAT']
117+
resp = make_response(slide.get_dzi(format))
118+
resp.mimetype = 'application/xml'
119+
return resp
120+
121+
@app.route('/<path:path>_files/<int:level>/<int:col>_<int:row>.<format>')
122+
def tile(path, level, col, row, format):
123+
slide = get_slide(path)
124+
format = format.lower()
125+
if format != 'jpeg' and format != 'png':
126+
# Not supported by Deep Zoom
127+
abort(404)
128+
try:
129+
tile = slide.get_tile(level, (col, row))
130+
except ValueError:
131+
# Invalid level or coordinates
132+
abort(404)
133+
buf = BytesIO()
134+
tile.save(buf, format, quality=app.config['DEEPZOOM_TILE_QUALITY'])
135+
resp = make_response(buf.getvalue())
136+
resp.mimetype = 'image/%s' % format
137+
return resp
138+
139+
return app
62140

63141

64142
class _SlideCache:
@@ -121,80 +199,6 @@ def __init__(self, relpath):
121199
self.url_path = relpath
122200

123201

124-
@app.before_first_request
125-
def _setup():
126-
app.basedir = os.path.abspath(app.config['SLIDE_DIR'])
127-
config_map = {
128-
'DEEPZOOM_TILE_SIZE': 'tile_size',
129-
'DEEPZOOM_OVERLAP': 'overlap',
130-
'DEEPZOOM_LIMIT_BOUNDS': 'limit_bounds',
131-
}
132-
opts = {v: app.config[k] for k, v in config_map.items()}
133-
app.cache = _SlideCache(
134-
app.config['SLIDE_CACHE_SIZE'], app.config['SLIDE_TILE_CACHE_MB'], opts
135-
)
136-
137-
138-
def _get_slide(path):
139-
path = os.path.abspath(os.path.join(app.basedir, path))
140-
if not path.startswith(app.basedir + os.path.sep):
141-
# Directory traversal
142-
abort(404)
143-
if not os.path.exists(path):
144-
abort(404)
145-
try:
146-
slide = app.cache.get(path)
147-
slide.filename = os.path.basename(path)
148-
return slide
149-
except OpenSlideError:
150-
abort(404)
151-
152-
153-
@app.route('/')
154-
def index():
155-
return render_template('files.html', root_dir=_Directory(app.basedir))
156-
157-
158-
@app.route('/<path:path>')
159-
def slide(path):
160-
slide = _get_slide(path)
161-
slide_url = url_for('dzi', path=path)
162-
return render_template(
163-
'slide-fullpage.html',
164-
slide_url=slide_url,
165-
slide_filename=slide.filename,
166-
slide_mpp=slide.mpp,
167-
)
168-
169-
170-
@app.route('/<path:path>.dzi')
171-
def dzi(path):
172-
slide = _get_slide(path)
173-
format = app.config['DEEPZOOM_FORMAT']
174-
resp = make_response(slide.get_dzi(format))
175-
resp.mimetype = 'application/xml'
176-
return resp
177-
178-
179-
@app.route('/<path:path>_files/<int:level>/<int:col>_<int:row>.<format>')
180-
def tile(path, level, col, row, format):
181-
slide = _get_slide(path)
182-
format = format.lower()
183-
if format != 'jpeg' and format != 'png':
184-
# Not supported by Deep Zoom
185-
abort(404)
186-
try:
187-
tile = slide.get_tile(level, (col, row))
188-
except ValueError:
189-
# Invalid level or coordinates
190-
abort(404)
191-
buf = BytesIO()
192-
tile.save(buf, format, quality=app.config['DEEPZOOM_TILE_QUALITY'])
193-
resp = make_response(buf.getvalue())
194-
resp.mimetype = 'image/%s' % format
195-
return resp
196-
197-
198202
if __name__ == '__main__':
199203
parser = OptionParser(usage='Usage: %prog [options] [slide-directory]')
200204
parser.add_option(
@@ -265,18 +269,18 @@ def tile(path, level, col, row, format):
265269
)
266270

267271
(opts, args) = parser.parse_args()
268-
# Load config file if specified
269-
if opts.config is not None:
270-
app.config.from_pyfile(opts.config)
271-
# Overwrite only those settings specified on the command line
272+
config = {}
273+
config_file = opts.config
274+
# Set only those settings specified on the command line
272275
for k in dir(opts):
273-
if not k.startswith('_') and getattr(opts, k) is None:
274-
delattr(opts, k)
275-
app.config.from_object(opts)
276-
# Set slide directory
276+
v = getattr(opts, k)
277+
if not k.startswith('_') and v is not None:
278+
config[k] = v
279+
# Set slide directory if specified
277280
try:
278-
app.config['SLIDE_DIR'] = args[0]
281+
config['SLIDE_DIR'] = args[0]
279282
except IndexError:
280283
pass
284+
app = create_app(config, config_file)
281285

282286
app.run(host=opts.host, port=opts.port, threaded=True)

examples/deepzoom/deepzoom_server.py

Lines changed: 72 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -46,21 +46,27 @@
4646
from openslide import ImageSlide, open_slide
4747
from openslide.deepzoom import DeepZoomGenerator
4848

49-
DEEPZOOM_SLIDE = None
50-
DEEPZOOM_FORMAT = 'jpeg'
51-
DEEPZOOM_TILE_SIZE = 254
52-
DEEPZOOM_OVERLAP = 1
53-
DEEPZOOM_LIMIT_BOUNDS = True
54-
DEEPZOOM_TILE_QUALITY = 75
5549
SLIDE_NAME = 'slide'
5650

57-
app = Flask(__name__)
58-
app.config.from_object(__name__)
59-
app.config.from_envvar('DEEPZOOM_TILER_SETTINGS', silent=True)
6051

52+
def create_app(config=None, config_file=None):
53+
# Create and configure app
54+
app = Flask(__name__)
55+
app.config.from_mapping(
56+
DEEPZOOM_SLIDE=None,
57+
DEEPZOOM_FORMAT='jpeg',
58+
DEEPZOOM_TILE_SIZE=254,
59+
DEEPZOOM_OVERLAP=1,
60+
DEEPZOOM_LIMIT_BOUNDS=True,
61+
DEEPZOOM_TILE_QUALITY=75,
62+
)
63+
app.config.from_envvar('DEEPZOOM_TILER_SETTINGS', silent=True)
64+
if config_file is not None:
65+
app.config.from_pyfile(config_file)
66+
if config is not None:
67+
app.config.from_mapping(config)
6168

62-
@app.before_first_request
63-
def load_slide():
69+
# Open slide
6470
slidefile = app.config['DEEPZOOM_SLIDE']
6571
if slidefile is None:
6672
raise ValueError('No slide file specified')
@@ -85,53 +91,53 @@ def load_slide():
8591
except (KeyError, ValueError):
8692
app.slide_mpp = 0
8793

88-
89-
@app.route('/')
90-
def index():
91-
slide_url = url_for('dzi', slug=SLIDE_NAME)
92-
associated_urls = {
93-
name: url_for('dzi', slug=slugify(name)) for name in app.associated_images
94-
}
95-
return render_template(
96-
'slide-multipane.html',
97-
slide_url=slide_url,
98-
associated=associated_urls,
99-
properties=app.slide_properties,
100-
slide_mpp=app.slide_mpp,
101-
)
102-
103-
104-
@app.route('/<slug>.dzi')
105-
def dzi(slug):
106-
format = app.config['DEEPZOOM_FORMAT']
107-
try:
108-
resp = make_response(app.slides[slug].get_dzi(format))
109-
resp.mimetype = 'application/xml'
94+
# Set up routes
95+
@app.route('/')
96+
def index():
97+
slide_url = url_for('dzi', slug=SLIDE_NAME)
98+
associated_urls = {
99+
name: url_for('dzi', slug=slugify(name)) for name in app.associated_images
100+
}
101+
return render_template(
102+
'slide-multipane.html',
103+
slide_url=slide_url,
104+
associated=associated_urls,
105+
properties=app.slide_properties,
106+
slide_mpp=app.slide_mpp,
107+
)
108+
109+
@app.route('/<slug>.dzi')
110+
def dzi(slug):
111+
format = app.config['DEEPZOOM_FORMAT']
112+
try:
113+
resp = make_response(app.slides[slug].get_dzi(format))
114+
resp.mimetype = 'application/xml'
115+
return resp
116+
except KeyError:
117+
# Unknown slug
118+
abort(404)
119+
120+
@app.route('/<slug>_files/<int:level>/<int:col>_<int:row>.<format>')
121+
def tile(slug, level, col, row, format):
122+
format = format.lower()
123+
if format != 'jpeg' and format != 'png':
124+
# Not supported by Deep Zoom
125+
abort(404)
126+
try:
127+
tile = app.slides[slug].get_tile(level, (col, row))
128+
except KeyError:
129+
# Unknown slug
130+
abort(404)
131+
except ValueError:
132+
# Invalid level or coordinates
133+
abort(404)
134+
buf = BytesIO()
135+
tile.save(buf, format, quality=app.config['DEEPZOOM_TILE_QUALITY'])
136+
resp = make_response(buf.getvalue())
137+
resp.mimetype = 'image/%s' % format
110138
return resp
111-
except KeyError:
112-
# Unknown slug
113-
abort(404)
114139

115-
116-
@app.route('/<slug>_files/<int:level>/<int:col>_<int:row>.<format>')
117-
def tile(slug, level, col, row, format):
118-
format = format.lower()
119-
if format != 'jpeg' and format != 'png':
120-
# Not supported by Deep Zoom
121-
abort(404)
122-
try:
123-
tile = app.slides[slug].get_tile(level, (col, row))
124-
except KeyError:
125-
# Unknown slug
126-
abort(404)
127-
except ValueError:
128-
# Invalid level or coordinates
129-
abort(404)
130-
buf = BytesIO()
131-
tile.save(buf, format, quality=app.config['DEEPZOOM_TILE_QUALITY'])
132-
resp = make_response(buf.getvalue())
133-
resp.mimetype = 'image/%s' % format
134-
return resp
140+
return app
135141

136142

137143
def slugify(text):
@@ -209,19 +215,18 @@ def slugify(text):
209215
)
210216

211217
(opts, args) = parser.parse_args()
212-
# Load config file if specified
213-
if opts.config is not None:
214-
app.config.from_pyfile(opts.config)
215-
# Overwrite only those settings specified on the command line
218+
config = {}
219+
config_file = opts.config
220+
# Set only those settings specified on the command line
216221
for k in dir(opts):
217-
if not k.startswith('_') and getattr(opts, k) is None:
218-
delattr(opts, k)
219-
app.config.from_object(opts)
220-
# Set slide file
222+
v = getattr(opts, k)
223+
if not k.startswith('_') and v is not None:
224+
config[k] = v
225+
# Set slide file if specified
221226
try:
222-
app.config['DEEPZOOM_SLIDE'] = args[0]
227+
config['DEEPZOOM_SLIDE'] = args[0]
223228
except IndexError:
224-
if app.config['DEEPZOOM_SLIDE'] is None:
225-
parser.error('No slide file specified')
229+
pass
230+
app = create_app(config, config_file)
226231

227232
app.run(host=opts.host, port=opts.port, threaded=True)

0 commit comments

Comments
 (0)