large-image 1.23.4.dev24__tar.gz → 1.23.5.dev2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {large-image-1.23.4.dev24/large_image.egg-info → large-image-1.23.5.dev2}/PKG-INFO +1 -1
- large-image-1.23.5.dev2/large_image/tilesource/jupyter.py +415 -0
- {large-image-1.23.4.dev24 → large-image-1.23.5.dev2/large_image.egg-info}/PKG-INFO +1 -1
- large-image-1.23.5.dev2/large_image.egg-info/requires.txt +148 -0
- large-image-1.23.4.dev24/large_image/tilesource/jupyter.py +0 -248
- large-image-1.23.4.dev24/large_image.egg-info/requires.txt +0 -148
- {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/LICENSE +0 -0
- {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/NOTICE +0 -0
- {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/README.rst +0 -0
- {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/docs/annotations.rst +0 -0
- {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/docs/conf.py +0 -0
- {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/docs/config_options.rst +0 -0
- {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/docs/development.rst +0 -0
- {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/docs/example_usage.rst +0 -0
- {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/docs/girder_annotation_config_options.rst +0 -0
- {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/docs/girder_config_options.rst +0 -0
- {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/docs/image_conversion.rst +0 -0
- {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/docs/index.rst +0 -0
- {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/docs/make_docs.sh +0 -0
- {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/docs/multi_source_specification.rst +0 -0
- {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/docs/static/custom.css +0 -0
- {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/docs/tilesource_options.rst +0 -0
- {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/docs/upgrade.rst +0 -0
- {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/large_image/__init__.py +0 -0
- {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/large_image/cache_util/__init__.py +0 -0
- {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/large_image/cache_util/base.py +0 -0
- {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/large_image/cache_util/cache.py +0 -0
- {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/large_image/cache_util/cachefactory.py +0 -0
- {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/large_image/cache_util/memcache.py +0 -0
- {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/large_image/config.py +0 -0
- {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/large_image/constants.py +0 -0
- {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/large_image/exceptions.py +0 -0
- {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/large_image/tilesource/__init__.py +0 -0
- {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/large_image/tilesource/base.py +0 -0
- {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/large_image/tilesource/geo.py +0 -0
- {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/large_image/tilesource/stylefuncs.py +0 -0
- {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/large_image/tilesource/tiledict.py +0 -0
- {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/large_image/tilesource/utilities.py +0 -0
- {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/large_image.egg-info/SOURCES.txt +0 -0
- {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/large_image.egg-info/dependency_links.txt +0 -0
- {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/large_image.egg-info/not-zip-safe +0 -0
- {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/large_image.egg-info/top_level.txt +0 -0
- {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/setup.cfg +0 -0
- {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/setup.py +0 -0
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
"""A vanilla REST interface to a ``TileSource``.
|
|
2
|
+
|
|
3
|
+
This is intended for use in JupyterLab and not intended to be used as a full
|
|
4
|
+
fledged REST API. Only two endpoints are exposed with minimal options:
|
|
5
|
+
|
|
6
|
+
* `/metadata`
|
|
7
|
+
* `/tile?z={z}&x={x}&y={y}&encoding=png`
|
|
8
|
+
|
|
9
|
+
We use Tornado because it is Jupyter's web server and will not require Jupyter
|
|
10
|
+
users to install any additional dependencies. Also, Tornado doesn't require us
|
|
11
|
+
to manage a separate thread for the web server.
|
|
12
|
+
|
|
13
|
+
Please note that this webserver will not work with Classic Notebook and will
|
|
14
|
+
likely lead to crashes. This is only for use in JupyterLab.
|
|
15
|
+
|
|
16
|
+
"""
|
|
17
|
+
import json
|
|
18
|
+
import os
|
|
19
|
+
import weakref
|
|
20
|
+
|
|
21
|
+
from large_image.exceptions import TileSourceXYZRangeError
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
import ipyleaflet
|
|
25
|
+
except ImportError: # pragma: no cover
|
|
26
|
+
ipyleaflet = None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def launch_tile_server(tile_source, port=0):
|
|
30
|
+
import tornado.httpserver
|
|
31
|
+
import tornado.netutil
|
|
32
|
+
import tornado.web
|
|
33
|
+
|
|
34
|
+
class RequestManager:
|
|
35
|
+
def __init__(self, tile_source):
|
|
36
|
+
self._tile_source_ = weakref.ref(tile_source)
|
|
37
|
+
self._ports = ()
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def tile_source(self):
|
|
41
|
+
return self._tile_source_()
|
|
42
|
+
|
|
43
|
+
@tile_source.setter
|
|
44
|
+
def tile_source(self, source):
|
|
45
|
+
self._tile_source_ = weakref.ref(source)
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def ports(self):
|
|
49
|
+
return self._ports
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def port(self):
|
|
53
|
+
return self.ports[0]
|
|
54
|
+
|
|
55
|
+
manager = RequestManager(tile_source)
|
|
56
|
+
# NOTE: set `ports` manually after launching server
|
|
57
|
+
|
|
58
|
+
class TileSourceMetadataHandler(tornado.web.RequestHandler):
|
|
59
|
+
"""REST endpoint to get image metadata."""
|
|
60
|
+
|
|
61
|
+
def get(self):
|
|
62
|
+
self.write(json.dumps(manager.tile_source.getMetadata()))
|
|
63
|
+
self.set_header('Content-Type', 'application/json')
|
|
64
|
+
|
|
65
|
+
class TileSourceTileHandler(tornado.web.RequestHandler):
|
|
66
|
+
"""REST endpoint to serve tiles from image in slippy maps standard."""
|
|
67
|
+
|
|
68
|
+
def get(self):
|
|
69
|
+
x = int(self.get_argument('x'))
|
|
70
|
+
y = int(self.get_argument('y'))
|
|
71
|
+
z = int(self.get_argument('z'))
|
|
72
|
+
encoding = self.get_argument('encoding', 'PNG')
|
|
73
|
+
try:
|
|
74
|
+
tile_binary = manager.tile_source.getTile(x, y, z, encoding=encoding)
|
|
75
|
+
except TileSourceXYZRangeError as e:
|
|
76
|
+
self.clear()
|
|
77
|
+
self.set_status(404)
|
|
78
|
+
self.finish(f'<html><body>{e}</body></html>')
|
|
79
|
+
else:
|
|
80
|
+
self.write(tile_binary)
|
|
81
|
+
self.set_header('Content-Type', 'image/png')
|
|
82
|
+
|
|
83
|
+
app = tornado.web.Application([
|
|
84
|
+
(r'/metadata', TileSourceMetadataHandler),
|
|
85
|
+
(r'/tile', TileSourceTileHandler),
|
|
86
|
+
])
|
|
87
|
+
sockets = tornado.netutil.bind_sockets(port, '')
|
|
88
|
+
server = tornado.httpserver.HTTPServer(app)
|
|
89
|
+
server.add_sockets(sockets)
|
|
90
|
+
|
|
91
|
+
manager._ports = tuple(s.getsockname()[1] for s in sockets)
|
|
92
|
+
return manager
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class Map:
|
|
96
|
+
"""
|
|
97
|
+
An IPyLeafletMap representation of a large image.
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
def __init__(self, *, ts=None, metadata=None, url=None, gc=None, id=None, resource=None):
|
|
101
|
+
"""
|
|
102
|
+
Specify the large image to be used with the IPyLeaflet Map. One of (a)
|
|
103
|
+
a tile source, (b) metadata dictionary and tile url, (c) girder client
|
|
104
|
+
and item or file id, or (d) girder client and resource path must be
|
|
105
|
+
specified.
|
|
106
|
+
|
|
107
|
+
:param ts: a TileSource.
|
|
108
|
+
:param metadata: a metadata dictionary as returned by a tile source or
|
|
109
|
+
a girder item/{id}/tiles endpoint.
|
|
110
|
+
:param url: a slippy map template url to fetch tiles (e.g.,
|
|
111
|
+
.../item/{id}/tiles/zxy/{z}/{x}/{y}?params=...)
|
|
112
|
+
:param gc: an authenticated girder client.
|
|
113
|
+
:param id: an item id that exists on the girder client.
|
|
114
|
+
:param resource: a girder resource path of an item or file that exists
|
|
115
|
+
on the girder client.
|
|
116
|
+
"""
|
|
117
|
+
self._layer = self._map = self._metadata = None
|
|
118
|
+
self._ts = ts
|
|
119
|
+
if (not url or not metadata) and gc and (id or resource):
|
|
120
|
+
if id is None:
|
|
121
|
+
entry = gc.get('resource/lookup', parameters={'path': resource})
|
|
122
|
+
if entry:
|
|
123
|
+
id = entry['itemId'] if entry.get('_modelType') == 'file' else entry['_id']
|
|
124
|
+
if id:
|
|
125
|
+
try:
|
|
126
|
+
metadata = gc.get(f'item/{id}/tiles')
|
|
127
|
+
except Exception:
|
|
128
|
+
pass
|
|
129
|
+
if metadata:
|
|
130
|
+
url = gc.urlBase + f'item/{id}/tiles' + '/zxy/{z}/{x}/{y}'
|
|
131
|
+
if metadata.get('geospatial'):
|
|
132
|
+
suffix = '?projection=EPSG:3857&encoding=PNG'
|
|
133
|
+
metadata = gc.get(f'item/{id}/tiles' + suffix)
|
|
134
|
+
if metadata.get('geospatial') and metadata.get('projection'):
|
|
135
|
+
url += suffix
|
|
136
|
+
else:
|
|
137
|
+
self._ts = self._get_temp_source(gc, id)
|
|
138
|
+
if url and metadata:
|
|
139
|
+
self._metadata = metadata
|
|
140
|
+
self._url = url
|
|
141
|
+
self._layer = self.make_layer(metadata, url)
|
|
142
|
+
self._map = self.make_map(metadata)
|
|
143
|
+
|
|
144
|
+
if ipyleaflet:
|
|
145
|
+
def _ipython_display_(self):
|
|
146
|
+
from IPython.display import display
|
|
147
|
+
|
|
148
|
+
if self._map:
|
|
149
|
+
return display(self._map)
|
|
150
|
+
if self._ts:
|
|
151
|
+
t = self._ts.as_leaflet_layer()
|
|
152
|
+
return display(self._ts._map.make_map(
|
|
153
|
+
self._ts.metadata, t, self._ts.getCenter(srs='EPSG:4326')))
|
|
154
|
+
|
|
155
|
+
def _get_temp_source(self, gc, id):
|
|
156
|
+
"""
|
|
157
|
+
If the server isn't large_image enabled, download the file to view it.
|
|
158
|
+
"""
|
|
159
|
+
import tempfile
|
|
160
|
+
|
|
161
|
+
import large_image
|
|
162
|
+
|
|
163
|
+
try:
|
|
164
|
+
item = gc.get(f'item/{id}')
|
|
165
|
+
except Exception:
|
|
166
|
+
item = None
|
|
167
|
+
if not item:
|
|
168
|
+
file = gc.get(f'file/{id}')
|
|
169
|
+
else:
|
|
170
|
+
file = gc.get(f'item/{id}/files', parameters={'limit': 1})[0]
|
|
171
|
+
self._tempfile = tempfile.NamedTemporaryFile(suffix='.' + file['name'].split('.', 1)[-1])
|
|
172
|
+
gc.downloadFile(file['_id'], self._tempfile)
|
|
173
|
+
return large_image.open(self._tempfile.name)
|
|
174
|
+
|
|
175
|
+
def make_layer(self, metadata, url, **kwargs):
|
|
176
|
+
"""
|
|
177
|
+
Create an ipyleaflet tile layer given large_image metadata and a tile
|
|
178
|
+
url.
|
|
179
|
+
"""
|
|
180
|
+
from ipyleaflet import TileLayer
|
|
181
|
+
|
|
182
|
+
self._geospatial = metadata.get('geospatial') and metadata.get('projection')
|
|
183
|
+
if 'bounds' not in kwargs and not self._geospatial:
|
|
184
|
+
kwargs = kwargs.copy()
|
|
185
|
+
kwargs['bounds'] = [[0, 0], [metadata['sizeY'], metadata['sizeX']]]
|
|
186
|
+
layer = TileLayer(
|
|
187
|
+
url=url,
|
|
188
|
+
# attribution='Tiles served with large-image',
|
|
189
|
+
min_zoom=0,
|
|
190
|
+
max_native_zoom=metadata['levels'] - 1,
|
|
191
|
+
max_zoom=20,
|
|
192
|
+
tile_size=metadata['tileWidth'],
|
|
193
|
+
**kwargs,
|
|
194
|
+
)
|
|
195
|
+
self._layer = layer
|
|
196
|
+
if not self._metadata:
|
|
197
|
+
self._metadata = metadata
|
|
198
|
+
return layer
|
|
199
|
+
|
|
200
|
+
def make_map(self, metadata, layer=None, center=None):
|
|
201
|
+
"""
|
|
202
|
+
Create an ipyleaflet map given large_image metadata, an optional
|
|
203
|
+
ipyleaflet layer, and the center of the tile source.
|
|
204
|
+
"""
|
|
205
|
+
from ipyleaflet import Map, basemaps, projections
|
|
206
|
+
|
|
207
|
+
try:
|
|
208
|
+
default_zoom = metadata['levels'] - metadata['sourceLevels']
|
|
209
|
+
except KeyError:
|
|
210
|
+
default_zoom = 0
|
|
211
|
+
|
|
212
|
+
self._geospatial = metadata.get('geospatial') and metadata.get('projection')
|
|
213
|
+
|
|
214
|
+
if self._geospatial:
|
|
215
|
+
# TODO: better handle other projections
|
|
216
|
+
crs = projections.EPSG3857
|
|
217
|
+
else:
|
|
218
|
+
crs = dict(
|
|
219
|
+
name='PixelSpace',
|
|
220
|
+
custom=True,
|
|
221
|
+
# Why does this need to be 256?
|
|
222
|
+
resolutions=[2 ** (metadata['levels'] - 1 - l) for l in range(20)],
|
|
223
|
+
|
|
224
|
+
# This works but has x and y reversed
|
|
225
|
+
proj4def='+proj=longlat +axis=esu',
|
|
226
|
+
bounds=[[0, 0], [metadata['sizeY'], metadata['sizeX']]],
|
|
227
|
+
# Why is origin X, Y but bounds Y, X?
|
|
228
|
+
origin=[0, metadata['sizeY']],
|
|
229
|
+
|
|
230
|
+
# This almost works to fix the x, y reversal, but
|
|
231
|
+
# - bounds are weird and other issues occur
|
|
232
|
+
# proj4def='+proj=longlat +axis=seu',
|
|
233
|
+
# bounds=[[-metadata['sizeX'],-metadata['sizeY']],
|
|
234
|
+
# [metadata['sizeX'],metadata['sizeY']]],
|
|
235
|
+
# origin=[0,0],
|
|
236
|
+
)
|
|
237
|
+
layer = layer or self._layer
|
|
238
|
+
|
|
239
|
+
if center is None:
|
|
240
|
+
if 'bounds' in metadata and 'projection' in metadata:
|
|
241
|
+
import pyproj
|
|
242
|
+
|
|
243
|
+
bounds = metadata['bounds']
|
|
244
|
+
center = (
|
|
245
|
+
(bounds['ymax'] + bounds['ymin']) / 2,
|
|
246
|
+
(bounds['xmax'] + bounds['xmin']) / 2,
|
|
247
|
+
)
|
|
248
|
+
transf = pyproj.Transformer.from_crs(
|
|
249
|
+
metadata['projection'], 'EPSG:4326', always_xy=True)
|
|
250
|
+
center = tuple(transf.transform(center[1], center[0])[::-1])
|
|
251
|
+
else:
|
|
252
|
+
center = (metadata['sizeY'] / 2, metadata['sizeX'] / 2)
|
|
253
|
+
|
|
254
|
+
m = Map(
|
|
255
|
+
crs=crs,
|
|
256
|
+
basemap=basemaps.OpenStreetMap.Mapnik if self._geospatial else layer,
|
|
257
|
+
center=center,
|
|
258
|
+
zoom=default_zoom,
|
|
259
|
+
max_zoom=metadata['levels'] + 1,
|
|
260
|
+
min_zoom=0,
|
|
261
|
+
scroll_wheel_zoom=True,
|
|
262
|
+
dragging=True,
|
|
263
|
+
attribution_control=False,
|
|
264
|
+
)
|
|
265
|
+
if self._geospatial:
|
|
266
|
+
m.add_layer(layer)
|
|
267
|
+
self._map = m
|
|
268
|
+
return m
|
|
269
|
+
|
|
270
|
+
@property
|
|
271
|
+
def layer(self):
|
|
272
|
+
return self._layer
|
|
273
|
+
|
|
274
|
+
@property
|
|
275
|
+
def map(self):
|
|
276
|
+
return self._map
|
|
277
|
+
|
|
278
|
+
def to_map(self, coordinate):
|
|
279
|
+
"""
|
|
280
|
+
Convert a coordinate from the image or projected image space to the map
|
|
281
|
+
space.
|
|
282
|
+
|
|
283
|
+
:param coordinate: a two-tuple that is x, y in pixel space or x, y in
|
|
284
|
+
image projection space.
|
|
285
|
+
:returns: a two-tuple that is in the map space coordinates.
|
|
286
|
+
"""
|
|
287
|
+
x, y = coordinate[:2]
|
|
288
|
+
if self._geospatial:
|
|
289
|
+
import pyproj
|
|
290
|
+
|
|
291
|
+
transf = pyproj.Transformer.from_crs(
|
|
292
|
+
self._metadata['projection'], 'EPSG:4326', always_xy=True)
|
|
293
|
+
return tuple(transf.transform(x, y)[::-1])
|
|
294
|
+
else:
|
|
295
|
+
return self._metadata['sizeY'] - y, x
|
|
296
|
+
|
|
297
|
+
def from_map(self, coordinate):
|
|
298
|
+
"""
|
|
299
|
+
:param coordinate: a two-tuple that is in the map space coordinates.
|
|
300
|
+
:returns: a two-tuple that is x, y in pixel space or x, y in image
|
|
301
|
+
projection space.
|
|
302
|
+
"""
|
|
303
|
+
y, x = coordinate[:2]
|
|
304
|
+
if self._geospatial:
|
|
305
|
+
import pyproj
|
|
306
|
+
|
|
307
|
+
transf = pyproj.Transformer.from_crs(
|
|
308
|
+
'EPSG:4326', self._metadata['projection'], always_xy=True)
|
|
309
|
+
return transf.transform(x, y)
|
|
310
|
+
else:
|
|
311
|
+
return x, self._metadata['sizeY'] - y
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
class IPyLeafletMixin:
|
|
315
|
+
"""Mixin class to support interactive visualization in JupyterLab.
|
|
316
|
+
|
|
317
|
+
This class implements ``_ipython_display_`` with ``ipyleaflet``
|
|
318
|
+
to display an interactive image visualizer for the tile source
|
|
319
|
+
in JupyterLab.
|
|
320
|
+
|
|
321
|
+
Install `ipyleaflet <https://github.com/jupyter-widgets/ipyleaflet>`_
|
|
322
|
+
to interactively visualize tile sources in JupyterLab.
|
|
323
|
+
|
|
324
|
+
For remote JupyterHub environments, you may need to configure
|
|
325
|
+
the class variables ``JUPYTER_HOST`` or ``JUPYTER_PROXY``.
|
|
326
|
+
|
|
327
|
+
If ``JUPYTER_PROXY`` is set, it overrides ``JUPYTER_HOST``.
|
|
328
|
+
|
|
329
|
+
Use ``JUPYTER_HOST`` to set the host name of the machine such
|
|
330
|
+
that the tile URL can be accessed at
|
|
331
|
+
``'http://{JUPYTER_HOST}:{port}'``.
|
|
332
|
+
|
|
333
|
+
Use ``JUPYTER_PROXY`` to leverage ``jupyter-server-proxy`` to
|
|
334
|
+
proxy the tile serving port through Jupyter's authenticated web
|
|
335
|
+
interface. This is useful in Docker and cloud JupyterHub
|
|
336
|
+
environments. You can set the environment variable
|
|
337
|
+
``LARGE_IMAGE_JUPYTER_PROXY`` to control the default value of
|
|
338
|
+
``JUPYTER_PROXY``. If ``JUPYTER_PROXY`` is set to ``True``, the
|
|
339
|
+
default will be ``'/proxy/`` which will work for most Docker
|
|
340
|
+
Jupyter configurations. If in a cloud JupyterHub environment,
|
|
341
|
+
this will get a bit more nuanced as the
|
|
342
|
+
``JUPYTERHUB_SERVICE_PREFIX`` may need to prefix the
|
|
343
|
+
``'/proxy/'``.
|
|
344
|
+
|
|
345
|
+
To programmatically set these values:
|
|
346
|
+
|
|
347
|
+
.. code::
|
|
348
|
+
|
|
349
|
+
from large_image.tilesource.jupyter import IPyLeafletMixin
|
|
350
|
+
|
|
351
|
+
# Only set one of these values
|
|
352
|
+
|
|
353
|
+
# Use a custom domain (avoids port proxying)
|
|
354
|
+
IPyLeafletMixin.JUPYTER_HOST = 'mydomain'
|
|
355
|
+
|
|
356
|
+
# Proxy in a standard JupyterLab environment
|
|
357
|
+
IPyLeafletMixin.JUPYTER_PROXY = True # defaults to `/proxy/`
|
|
358
|
+
|
|
359
|
+
# Proxy in a cloud JupyterHub environment
|
|
360
|
+
IPyLeafletMixin.JUPYTER_PROXY = '/jupyter/user/username/proxy/'
|
|
361
|
+
# See if ``JUPYTERHUB_SERVICE_PREFIX`` is in the environment
|
|
362
|
+
# variables to improve this
|
|
363
|
+
|
|
364
|
+
"""
|
|
365
|
+
|
|
366
|
+
JUPYTER_HOST = '127.0.0.1'
|
|
367
|
+
JUPYTER_PROXY = os.environ.get('LARGE_IMAGE_JUPYTER_PROXY', False)
|
|
368
|
+
|
|
369
|
+
def __init__(self, *args, **kwargs):
|
|
370
|
+
self._jupyter_server_manager = None
|
|
371
|
+
self._map = Map()
|
|
372
|
+
if ipyleaflet:
|
|
373
|
+
self.to_map = self._map.to_map
|
|
374
|
+
self.from_map = self._map.from_map
|
|
375
|
+
|
|
376
|
+
def as_leaflet_layer(self, **kwargs):
|
|
377
|
+
# NOTE: `as_leaflet_layer` is supported by ipyleaflet.Map.add
|
|
378
|
+
|
|
379
|
+
if self._jupyter_server_manager is None:
|
|
380
|
+
# Must relaunch to ensure style updates work
|
|
381
|
+
self._jupyter_server_manager = launch_tile_server(self)
|
|
382
|
+
else:
|
|
383
|
+
# Must update the source on the manager in case the previous reference is bad
|
|
384
|
+
self._jupyter_server_manager.tile_source = self
|
|
385
|
+
|
|
386
|
+
port = self._jupyter_server_manager.port
|
|
387
|
+
|
|
388
|
+
if self.JUPYTER_PROXY:
|
|
389
|
+
if isinstance(self.JUPYTER_PROXY, str):
|
|
390
|
+
base_url = f'{self.JUPYTER_PROXY.rstrip("/")}/{port}'
|
|
391
|
+
else:
|
|
392
|
+
base_url = f'/proxy/{port}'
|
|
393
|
+
else:
|
|
394
|
+
base_url = f'http://{self.JUPYTER_HOST}:{port}'
|
|
395
|
+
|
|
396
|
+
# Use repr in URL params to prevent caching across sources/styles
|
|
397
|
+
endpoint = f'tile?z={{z}}&x={{x}}&y={{y}}&encoding=png&repr={self.__repr__()}'
|
|
398
|
+
return self._map.make_layer(self.metadata, f'{base_url}/{endpoint}')
|
|
399
|
+
|
|
400
|
+
# Only make _ipython_display_ available if ipyleaflet is installed
|
|
401
|
+
if ipyleaflet:
|
|
402
|
+
|
|
403
|
+
def _ipython_display_(self):
|
|
404
|
+
from IPython.display import display
|
|
405
|
+
|
|
406
|
+
t = self.as_leaflet_layer()
|
|
407
|
+
|
|
408
|
+
return display(self._map.make_map(self.metadata, t, self.getCenter(srs='EPSG:4326')))
|
|
409
|
+
|
|
410
|
+
@property
|
|
411
|
+
def iplmap(self):
|
|
412
|
+
"""
|
|
413
|
+
If using ipyleaflets, get access to the map object.
|
|
414
|
+
"""
|
|
415
|
+
return self._map.map
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
cachetools
|
|
2
|
+
palettable
|
|
3
|
+
Pillow
|
|
4
|
+
numpy
|
|
5
|
+
|
|
6
|
+
[:python_version < "3.8"]
|
|
7
|
+
importlib-metadata<5
|
|
8
|
+
|
|
9
|
+
[all]
|
|
10
|
+
large-image-source-bioformats>=1.23.5.dev2
|
|
11
|
+
matplotlib
|
|
12
|
+
large-image-source-ometiff>=1.23.5.dev2
|
|
13
|
+
large-image-source-gdal>=1.23.5.dev2
|
|
14
|
+
large-image-source-vips>=1.23.5.dev2
|
|
15
|
+
large-image-source-mapnik>=1.23.5.dev2
|
|
16
|
+
large-image-source-multi>=1.23.5.dev2
|
|
17
|
+
large-image-source-openslide>=1.23.5.dev2
|
|
18
|
+
large-image-source-pil[all]>=1.23.5.dev2
|
|
19
|
+
large-image-source-test>=1.23.5.dev2
|
|
20
|
+
large-image-source-tiff>=1.23.5.dev2
|
|
21
|
+
pyvips
|
|
22
|
+
large-image-converter>=1.23.5.dev2
|
|
23
|
+
large-image-source-deepzoom>=1.23.5.dev2
|
|
24
|
+
large-image-source-dummy>=1.23.5.dev2
|
|
25
|
+
psutil>=4.2.0
|
|
26
|
+
large-image-source-openjpeg>=1.23.5.dev2
|
|
27
|
+
large-image-source-pil>=1.23.5.dev2
|
|
28
|
+
|
|
29
|
+
[all:platform_system != "Windows"]
|
|
30
|
+
pylibmc>=1.5.1
|
|
31
|
+
|
|
32
|
+
[all:python_version < "3.7"]
|
|
33
|
+
simplejpeg<1.6.6
|
|
34
|
+
|
|
35
|
+
[all:python_version >= "3.7"]
|
|
36
|
+
simplejpeg
|
|
37
|
+
large-image-source-nd2>=1.23.5.dev2
|
|
38
|
+
large-image-source-tifffile>=1.23.5.dev2
|
|
39
|
+
|
|
40
|
+
[all:python_version >= "3.8"]
|
|
41
|
+
large-image-source-dicom>=1.23.5.dev2
|
|
42
|
+
large-image-source-rasterio>=1.23.5.dev2
|
|
43
|
+
large-image-source-rasterio[all]>=1.23.5.dev2
|
|
44
|
+
|
|
45
|
+
[bioformats]
|
|
46
|
+
large-image-source-bioformats>=1.23.5.dev2
|
|
47
|
+
|
|
48
|
+
[colormaps]
|
|
49
|
+
matplotlib
|
|
50
|
+
|
|
51
|
+
[converter]
|
|
52
|
+
large-image-converter>=1.23.5.dev2
|
|
53
|
+
|
|
54
|
+
[deepzoom]
|
|
55
|
+
large-image-source-deepzoom>=1.23.5.dev2
|
|
56
|
+
|
|
57
|
+
[dicom]
|
|
58
|
+
|
|
59
|
+
[dicom:python_version >= "3.8"]
|
|
60
|
+
large-image-source-dicom>=1.23.5.dev2
|
|
61
|
+
|
|
62
|
+
[dummy]
|
|
63
|
+
large-image-source-dummy>=1.23.5.dev2
|
|
64
|
+
|
|
65
|
+
[gdal]
|
|
66
|
+
large-image-source-gdal>=1.23.5.dev2
|
|
67
|
+
|
|
68
|
+
[mapnik]
|
|
69
|
+
large-image-source-mapnik>=1.23.5.dev2
|
|
70
|
+
|
|
71
|
+
[memcached]
|
|
72
|
+
|
|
73
|
+
[memcached:platform_system != "Windows"]
|
|
74
|
+
pylibmc>=1.5.1
|
|
75
|
+
|
|
76
|
+
[multi]
|
|
77
|
+
large-image-source-multi>=1.23.5.dev2
|
|
78
|
+
|
|
79
|
+
[nd2]
|
|
80
|
+
|
|
81
|
+
[nd2:python_version >= "3.7"]
|
|
82
|
+
large-image-source-nd2>=1.23.5.dev2
|
|
83
|
+
|
|
84
|
+
[ometiff]
|
|
85
|
+
large-image-source-ometiff>=1.23.5.dev2
|
|
86
|
+
|
|
87
|
+
[openjpeg]
|
|
88
|
+
large-image-source-openjpeg>=1.23.5.dev2
|
|
89
|
+
|
|
90
|
+
[openslide]
|
|
91
|
+
large-image-source-openslide>=1.23.5.dev2
|
|
92
|
+
|
|
93
|
+
[performance]
|
|
94
|
+
psutil>=4.2.0
|
|
95
|
+
|
|
96
|
+
[performance:python_version < "3.7"]
|
|
97
|
+
simplejpeg<1.6.6
|
|
98
|
+
|
|
99
|
+
[performance:python_version >= "3.7"]
|
|
100
|
+
simplejpeg
|
|
101
|
+
|
|
102
|
+
[pil]
|
|
103
|
+
large-image-source-pil>=1.23.5.dev2
|
|
104
|
+
|
|
105
|
+
[rasterio]
|
|
106
|
+
|
|
107
|
+
[rasterio:python_version >= "3.8"]
|
|
108
|
+
large-image-source-rasterio>=1.23.5.dev2
|
|
109
|
+
|
|
110
|
+
[sources]
|
|
111
|
+
large-image-source-tiff>=1.23.5.dev2
|
|
112
|
+
large-image-source-bioformats>=1.23.5.dev2
|
|
113
|
+
large-image-source-ometiff>=1.23.5.dev2
|
|
114
|
+
large-image-source-deepzoom>=1.23.5.dev2
|
|
115
|
+
large-image-source-gdal>=1.23.5.dev2
|
|
116
|
+
large-image-source-vips>=1.23.5.dev2
|
|
117
|
+
large-image-source-mapnik>=1.23.5.dev2
|
|
118
|
+
large-image-source-dummy>=1.23.5.dev2
|
|
119
|
+
large-image-source-multi>=1.23.5.dev2
|
|
120
|
+
large-image-source-openjpeg>=1.23.5.dev2
|
|
121
|
+
large-image-source-openslide>=1.23.5.dev2
|
|
122
|
+
large-image-source-pil>=1.23.5.dev2
|
|
123
|
+
large-image-source-test>=1.23.5.dev2
|
|
124
|
+
|
|
125
|
+
[sources:python_version >= "3.7"]
|
|
126
|
+
large-image-source-tifffile>=1.23.5.dev2
|
|
127
|
+
large-image-source-nd2>=1.23.5.dev2
|
|
128
|
+
|
|
129
|
+
[sources:python_version >= "3.8"]
|
|
130
|
+
large-image-source-dicom>=1.23.5.dev2
|
|
131
|
+
large-image-source-rasterio>=1.23.5.dev2
|
|
132
|
+
|
|
133
|
+
[test]
|
|
134
|
+
large-image-source-test>=1.23.5.dev2
|
|
135
|
+
|
|
136
|
+
[tiff]
|
|
137
|
+
large-image-source-tiff>=1.23.5.dev2
|
|
138
|
+
|
|
139
|
+
[tifffile]
|
|
140
|
+
|
|
141
|
+
[tifffile:python_version >= "3.7"]
|
|
142
|
+
large-image-source-tifffile>=1.23.5.dev2
|
|
143
|
+
|
|
144
|
+
[tiledoutput]
|
|
145
|
+
pyvips
|
|
146
|
+
|
|
147
|
+
[vips]
|
|
148
|
+
large-image-source-vips>=1.23.5.dev2
|
|
@@ -1,248 +0,0 @@
|
|
|
1
|
-
"""A vanilla REST interface to a ``TileSource``.
|
|
2
|
-
|
|
3
|
-
This is intended for use in JupyterLab and not intended to be used as a full
|
|
4
|
-
fledged REST API. Only two endpoints are exposed with minimal options:
|
|
5
|
-
|
|
6
|
-
* `/metadata`
|
|
7
|
-
* `/tile?z={z}&x={x}&y={y}&encoding=png`
|
|
8
|
-
|
|
9
|
-
We use Tornado because it is Jupyter's web server and will not require Jupyter
|
|
10
|
-
users to install any additional dependencies. Also, Tornado doesn't require us
|
|
11
|
-
to manage a separate thread for the web server.
|
|
12
|
-
|
|
13
|
-
Please note that this webserver will not work with Classic Notebook and will
|
|
14
|
-
likely lead to crashes. This is only for use in JupyterLab.
|
|
15
|
-
|
|
16
|
-
"""
|
|
17
|
-
import json
|
|
18
|
-
import os
|
|
19
|
-
import weakref
|
|
20
|
-
|
|
21
|
-
from large_image.exceptions import TileSourceXYZRangeError
|
|
22
|
-
|
|
23
|
-
try:
|
|
24
|
-
import ipyleaflet
|
|
25
|
-
except ImportError: # pragma: no cover
|
|
26
|
-
ipyleaflet = None
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def launch_tile_server(tile_source, port=0):
|
|
30
|
-
import tornado.httpserver
|
|
31
|
-
import tornado.netutil
|
|
32
|
-
import tornado.web
|
|
33
|
-
|
|
34
|
-
class RequestManager:
|
|
35
|
-
def __init__(self, tile_source):
|
|
36
|
-
self._tile_source_ = weakref.ref(tile_source)
|
|
37
|
-
self._ports = ()
|
|
38
|
-
|
|
39
|
-
@property
|
|
40
|
-
def tile_source(self):
|
|
41
|
-
return self._tile_source_()
|
|
42
|
-
|
|
43
|
-
@tile_source.setter
|
|
44
|
-
def tile_source(self, source):
|
|
45
|
-
self._tile_source_ = weakref.ref(source)
|
|
46
|
-
|
|
47
|
-
@property
|
|
48
|
-
def ports(self):
|
|
49
|
-
return self._ports
|
|
50
|
-
|
|
51
|
-
@property
|
|
52
|
-
def port(self):
|
|
53
|
-
return self.ports[0]
|
|
54
|
-
|
|
55
|
-
manager = RequestManager(tile_source)
|
|
56
|
-
# NOTE: set `ports` manually after launching server
|
|
57
|
-
|
|
58
|
-
class TileSourceMetadataHandler(tornado.web.RequestHandler):
|
|
59
|
-
"""REST endpoint to get image metadata."""
|
|
60
|
-
|
|
61
|
-
def get(self):
|
|
62
|
-
self.write(json.dumps(manager.tile_source.getMetadata()))
|
|
63
|
-
self.set_header('Content-Type', 'application/json')
|
|
64
|
-
|
|
65
|
-
class TileSourceTileHandler(tornado.web.RequestHandler):
|
|
66
|
-
"""REST endpoint to serve tiles from image in slippy maps standard."""
|
|
67
|
-
|
|
68
|
-
def get(self):
|
|
69
|
-
x = int(self.get_argument('x'))
|
|
70
|
-
y = int(self.get_argument('y'))
|
|
71
|
-
z = int(self.get_argument('z'))
|
|
72
|
-
encoding = self.get_argument('encoding', 'PNG')
|
|
73
|
-
try:
|
|
74
|
-
tile_binary = manager.tile_source.getTile(x, y, z, encoding=encoding)
|
|
75
|
-
except TileSourceXYZRangeError as e:
|
|
76
|
-
self.clear()
|
|
77
|
-
self.set_status(404)
|
|
78
|
-
self.finish(f'<html><body>{e}</body></html>')
|
|
79
|
-
else:
|
|
80
|
-
self.write(tile_binary)
|
|
81
|
-
self.set_header('Content-Type', 'image/png')
|
|
82
|
-
|
|
83
|
-
app = tornado.web.Application([
|
|
84
|
-
(r'/metadata', TileSourceMetadataHandler),
|
|
85
|
-
(r'/tile', TileSourceTileHandler),
|
|
86
|
-
])
|
|
87
|
-
sockets = tornado.netutil.bind_sockets(port, '')
|
|
88
|
-
server = tornado.httpserver.HTTPServer(app)
|
|
89
|
-
server.add_sockets(sockets)
|
|
90
|
-
|
|
91
|
-
manager._ports = tuple(s.getsockname()[1] for s in sockets)
|
|
92
|
-
return manager
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
class IPyLeafletMixin:
|
|
96
|
-
"""Mixin class to support interactive visualization in JupyterLab.
|
|
97
|
-
|
|
98
|
-
This class implements ``_ipython_display_`` with ``ipyleaflet``
|
|
99
|
-
to display an interactive image visualizer for the tile source
|
|
100
|
-
in JupyterLab.
|
|
101
|
-
|
|
102
|
-
Install `ipyleaflet <https://github.com/jupyter-widgets/ipyleaflet>`_
|
|
103
|
-
to interactively visualize tile sources in JupyterLab.
|
|
104
|
-
|
|
105
|
-
For remote JupyterHub environments, you may need to configure
|
|
106
|
-
the class variables ``JUPYTER_HOST`` or ``JUPYTER_PROXY``.
|
|
107
|
-
|
|
108
|
-
If ``JUPYTER_PROXY`` is set, it overrides ``JUPYTER_HOST``.
|
|
109
|
-
|
|
110
|
-
Use ``JUPYTER_HOST`` to set the host name of the machine such
|
|
111
|
-
that the tile URL can be accessed at
|
|
112
|
-
``'http://{JUPYTER_HOST}:{port}'``.
|
|
113
|
-
|
|
114
|
-
Use ``JUPYTER_PROXY`` to leverage ``jupyter-server-proxy`` to
|
|
115
|
-
proxy the tile serving port through Jupyter's authenticated web
|
|
116
|
-
interface. This is useful in Docker and cloud JupyterHub
|
|
117
|
-
environments. You can set the environment variable
|
|
118
|
-
``LARGE_IMAGE_JUPYTER_PROXY`` to control the default value of
|
|
119
|
-
``JUPYTER_PROXY``. If ``JUPYTER_PROXY`` is set to ``True``, the
|
|
120
|
-
default will be ``'/proxy/`` which will work for most Docker
|
|
121
|
-
Jupyter configurations. If in a cloud JupyterHub environment,
|
|
122
|
-
this will get a bit more nuanced as the
|
|
123
|
-
``JUPYTERHUB_SERVICE_PREFIX`` may need to prefix the
|
|
124
|
-
``'/proxy/'``.
|
|
125
|
-
|
|
126
|
-
To programmatically set these values:
|
|
127
|
-
|
|
128
|
-
.. code::
|
|
129
|
-
|
|
130
|
-
from large_image.tilesource.jupyter import IPyLeafletMixin
|
|
131
|
-
|
|
132
|
-
# Only set one of these values
|
|
133
|
-
|
|
134
|
-
# Use a custom domain (avoids port proxying)
|
|
135
|
-
IPyLeafletMixin.JUPYTER_HOST = 'mydomain'
|
|
136
|
-
|
|
137
|
-
# Proxy in a standard JupyterLab environment
|
|
138
|
-
IPyLeafletMixin.JUPYTER_PROXY = True # defaults to `/proxy/`
|
|
139
|
-
|
|
140
|
-
# Proxy in a cloud JupyterHub environment
|
|
141
|
-
IPyLeafletMixin.JUPYTER_PROXY = '/jupyter/user/username/proxy/'
|
|
142
|
-
# See if ``JUPYTERHUB_SERVICE_PREFIX`` is in the environment
|
|
143
|
-
# variables to improve this
|
|
144
|
-
|
|
145
|
-
"""
|
|
146
|
-
|
|
147
|
-
JUPYTER_HOST = '127.0.0.1'
|
|
148
|
-
JUPYTER_PROXY = os.environ.get('LARGE_IMAGE_JUPYTER_PROXY', False)
|
|
149
|
-
|
|
150
|
-
def __init__(self, *args, **kwargs):
|
|
151
|
-
self._jupyter_server_manager = None
|
|
152
|
-
|
|
153
|
-
def as_leaflet_layer(self, **kwargs):
|
|
154
|
-
# NOTE: `as_leaflet_layer` is supported by ipyleaflet.Map.add
|
|
155
|
-
from ipyleaflet import TileLayer
|
|
156
|
-
|
|
157
|
-
if self._jupyter_server_manager is None:
|
|
158
|
-
# Must relaunch to ensure style updates work
|
|
159
|
-
self._jupyter_server_manager = launch_tile_server(self)
|
|
160
|
-
else:
|
|
161
|
-
# Must update the source on the manager in case the previous reference is bad
|
|
162
|
-
self._jupyter_server_manager.tile_source = self
|
|
163
|
-
|
|
164
|
-
port = self._jupyter_server_manager.port
|
|
165
|
-
|
|
166
|
-
metadata = self.getMetadata()
|
|
167
|
-
|
|
168
|
-
if self.JUPYTER_PROXY:
|
|
169
|
-
if isinstance(self.JUPYTER_PROXY, str):
|
|
170
|
-
base_url = f'{self.JUPYTER_PROXY.rstrip("/")}/{port}'
|
|
171
|
-
else:
|
|
172
|
-
base_url = f'/proxy/{port}'
|
|
173
|
-
else:
|
|
174
|
-
base_url = f'http://{self.JUPYTER_HOST}:{port}'
|
|
175
|
-
|
|
176
|
-
# Use repr in URL params to prevent caching across sources/styles
|
|
177
|
-
endpoint = f'tile?z={{z}}&x={{x}}&y={{y}}&encoding=png&repr={self.__repr__()}'
|
|
178
|
-
|
|
179
|
-
if not (self.geospatial and self.projection) and 'bounds' not in kwargs:
|
|
180
|
-
kwargs = kwargs.copy()
|
|
181
|
-
kwargs['bounds'] = [[0, 0], [metadata['sizeY'], metadata['sizeX']]]
|
|
182
|
-
layer = TileLayer(
|
|
183
|
-
url=f'{base_url}/{endpoint}',
|
|
184
|
-
# attribution='Tiles served with large-image',
|
|
185
|
-
min_zoom=0,
|
|
186
|
-
max_native_zoom=metadata['levels'],
|
|
187
|
-
max_zoom=20,
|
|
188
|
-
tile_size=metadata['tileWidth'],
|
|
189
|
-
**kwargs,
|
|
190
|
-
)
|
|
191
|
-
return layer
|
|
192
|
-
|
|
193
|
-
# Only make _ipython_display_ available if ipyleaflet is installed
|
|
194
|
-
if ipyleaflet:
|
|
195
|
-
|
|
196
|
-
def _ipython_display_(self):
|
|
197
|
-
from ipyleaflet import Map, basemaps, projections
|
|
198
|
-
from IPython.display import display
|
|
199
|
-
|
|
200
|
-
metadata = self.getMetadata()
|
|
201
|
-
|
|
202
|
-
t = self.as_leaflet_layer()
|
|
203
|
-
|
|
204
|
-
try:
|
|
205
|
-
default_zoom = metadata['levels'] - metadata['sourceLevels']
|
|
206
|
-
except KeyError:
|
|
207
|
-
default_zoom = 0
|
|
208
|
-
|
|
209
|
-
geospatial = self.geospatial and self.projection
|
|
210
|
-
|
|
211
|
-
if geospatial:
|
|
212
|
-
# TODO: better handle other projections
|
|
213
|
-
crs = projections.EPSG3857
|
|
214
|
-
else:
|
|
215
|
-
crs = dict(
|
|
216
|
-
name='PixelSpace',
|
|
217
|
-
custom=True,
|
|
218
|
-
# Why does this need to be 256?
|
|
219
|
-
resolutions=[2 ** (metadata['levels'] - 1 - l) for l in range(20)],
|
|
220
|
-
|
|
221
|
-
# This works but has x and y reversed
|
|
222
|
-
proj4def='+proj=longlat +axis=esu',
|
|
223
|
-
bounds=[[0, 0], [metadata['sizeY'], metadata['sizeX']]],
|
|
224
|
-
# Why is origin X, Y but bounds Y, X?
|
|
225
|
-
origin=[0, metadata['sizeY']],
|
|
226
|
-
|
|
227
|
-
# This almost works to fix the x, y reversal, but
|
|
228
|
-
# - bounds are weird and other issues occur
|
|
229
|
-
# proj4def='+proj=longlat +axis=seu',
|
|
230
|
-
# bounds=[[-metadata['sizeX'],-metadata['sizeY']],
|
|
231
|
-
# [metadata['sizeX'],metadata['sizeY']]],
|
|
232
|
-
# origin=[0,0],
|
|
233
|
-
)
|
|
234
|
-
|
|
235
|
-
m = Map(
|
|
236
|
-
crs=crs,
|
|
237
|
-
basemap=basemaps.OpenStreetMap.Mapnik if geospatial else t,
|
|
238
|
-
center=self.getCenter(srs='EPSG:4326'),
|
|
239
|
-
zoom=default_zoom,
|
|
240
|
-
max_zoom=metadata['levels'] + 1,
|
|
241
|
-
min_zoom=0,
|
|
242
|
-
scroll_wheel_zoom=True,
|
|
243
|
-
dragging=True,
|
|
244
|
-
# attribution_control=False,
|
|
245
|
-
)
|
|
246
|
-
if geospatial:
|
|
247
|
-
m.add_layer(t)
|
|
248
|
-
return display(m)
|
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
cachetools
|
|
2
|
-
palettable
|
|
3
|
-
Pillow
|
|
4
|
-
numpy
|
|
5
|
-
|
|
6
|
-
[:python_version < "3.8"]
|
|
7
|
-
importlib-metadata<5
|
|
8
|
-
|
|
9
|
-
[all]
|
|
10
|
-
large-image-source-mapnik>=1.23.4.dev24
|
|
11
|
-
large-image-source-ometiff>=1.23.4.dev24
|
|
12
|
-
large-image-source-openjpeg>=1.23.4.dev24
|
|
13
|
-
large-image-source-vips>=1.23.4.dev24
|
|
14
|
-
large-image-source-gdal>=1.23.4.dev24
|
|
15
|
-
pyvips
|
|
16
|
-
matplotlib
|
|
17
|
-
large-image-source-pil>=1.23.4.dev24
|
|
18
|
-
large-image-source-bioformats>=1.23.4.dev24
|
|
19
|
-
large-image-source-pil[all]>=1.23.4.dev24
|
|
20
|
-
large-image-source-openslide>=1.23.4.dev24
|
|
21
|
-
large-image-source-multi>=1.23.4.dev24
|
|
22
|
-
large-image-source-test>=1.23.4.dev24
|
|
23
|
-
large-image-source-tiff>=1.23.4.dev24
|
|
24
|
-
psutil>=4.2.0
|
|
25
|
-
large-image-source-dummy>=1.23.4.dev24
|
|
26
|
-
large-image-source-deepzoom>=1.23.4.dev24
|
|
27
|
-
large-image-converter>=1.23.4.dev24
|
|
28
|
-
|
|
29
|
-
[all:platform_system != "Windows"]
|
|
30
|
-
pylibmc>=1.5.1
|
|
31
|
-
|
|
32
|
-
[all:python_version < "3.7"]
|
|
33
|
-
simplejpeg<1.6.6
|
|
34
|
-
|
|
35
|
-
[all:python_version >= "3.7"]
|
|
36
|
-
simplejpeg
|
|
37
|
-
large-image-source-nd2>=1.23.4.dev24
|
|
38
|
-
large-image-source-tifffile>=1.23.4.dev24
|
|
39
|
-
|
|
40
|
-
[all:python_version >= "3.8"]
|
|
41
|
-
large-image-source-rasterio[all]>=1.23.4.dev24
|
|
42
|
-
large-image-source-rasterio>=1.23.4.dev24
|
|
43
|
-
large-image-source-dicom>=1.23.4.dev24
|
|
44
|
-
|
|
45
|
-
[bioformats]
|
|
46
|
-
large-image-source-bioformats>=1.23.4.dev24
|
|
47
|
-
|
|
48
|
-
[colormaps]
|
|
49
|
-
matplotlib
|
|
50
|
-
|
|
51
|
-
[converter]
|
|
52
|
-
large-image-converter>=1.23.4.dev24
|
|
53
|
-
|
|
54
|
-
[deepzoom]
|
|
55
|
-
large-image-source-deepzoom>=1.23.4.dev24
|
|
56
|
-
|
|
57
|
-
[dicom]
|
|
58
|
-
|
|
59
|
-
[dicom:python_version >= "3.8"]
|
|
60
|
-
large-image-source-dicom>=1.23.4.dev24
|
|
61
|
-
|
|
62
|
-
[dummy]
|
|
63
|
-
large-image-source-dummy>=1.23.4.dev24
|
|
64
|
-
|
|
65
|
-
[gdal]
|
|
66
|
-
large-image-source-gdal>=1.23.4.dev24
|
|
67
|
-
|
|
68
|
-
[mapnik]
|
|
69
|
-
large-image-source-mapnik>=1.23.4.dev24
|
|
70
|
-
|
|
71
|
-
[memcached]
|
|
72
|
-
|
|
73
|
-
[memcached:platform_system != "Windows"]
|
|
74
|
-
pylibmc>=1.5.1
|
|
75
|
-
|
|
76
|
-
[multi]
|
|
77
|
-
large-image-source-multi>=1.23.4.dev24
|
|
78
|
-
|
|
79
|
-
[nd2]
|
|
80
|
-
|
|
81
|
-
[nd2:python_version >= "3.7"]
|
|
82
|
-
large-image-source-nd2>=1.23.4.dev24
|
|
83
|
-
|
|
84
|
-
[ometiff]
|
|
85
|
-
large-image-source-ometiff>=1.23.4.dev24
|
|
86
|
-
|
|
87
|
-
[openjpeg]
|
|
88
|
-
large-image-source-openjpeg>=1.23.4.dev24
|
|
89
|
-
|
|
90
|
-
[openslide]
|
|
91
|
-
large-image-source-openslide>=1.23.4.dev24
|
|
92
|
-
|
|
93
|
-
[performance]
|
|
94
|
-
psutil>=4.2.0
|
|
95
|
-
|
|
96
|
-
[performance:python_version < "3.7"]
|
|
97
|
-
simplejpeg<1.6.6
|
|
98
|
-
|
|
99
|
-
[performance:python_version >= "3.7"]
|
|
100
|
-
simplejpeg
|
|
101
|
-
|
|
102
|
-
[pil]
|
|
103
|
-
large-image-source-pil>=1.23.4.dev24
|
|
104
|
-
|
|
105
|
-
[rasterio]
|
|
106
|
-
|
|
107
|
-
[rasterio:python_version >= "3.8"]
|
|
108
|
-
large-image-source-rasterio>=1.23.4.dev24
|
|
109
|
-
|
|
110
|
-
[sources]
|
|
111
|
-
large-image-source-openslide>=1.23.4.dev24
|
|
112
|
-
large-image-source-mapnik>=1.23.4.dev24
|
|
113
|
-
large-image-source-multi>=1.23.4.dev24
|
|
114
|
-
large-image-source-ometiff>=1.23.4.dev24
|
|
115
|
-
large-image-source-openjpeg>=1.23.4.dev24
|
|
116
|
-
large-image-source-test>=1.23.4.dev24
|
|
117
|
-
large-image-source-gdal>=1.23.4.dev24
|
|
118
|
-
large-image-source-tiff>=1.23.4.dev24
|
|
119
|
-
large-image-source-vips>=1.23.4.dev24
|
|
120
|
-
large-image-source-dummy>=1.23.4.dev24
|
|
121
|
-
large-image-source-pil>=1.23.4.dev24
|
|
122
|
-
large-image-source-deepzoom>=1.23.4.dev24
|
|
123
|
-
large-image-source-bioformats>=1.23.4.dev24
|
|
124
|
-
|
|
125
|
-
[sources:python_version >= "3.7"]
|
|
126
|
-
large-image-source-nd2>=1.23.4.dev24
|
|
127
|
-
large-image-source-tifffile>=1.23.4.dev24
|
|
128
|
-
|
|
129
|
-
[sources:python_version >= "3.8"]
|
|
130
|
-
large-image-source-rasterio>=1.23.4.dev24
|
|
131
|
-
large-image-source-dicom>=1.23.4.dev24
|
|
132
|
-
|
|
133
|
-
[test]
|
|
134
|
-
large-image-source-test>=1.23.4.dev24
|
|
135
|
-
|
|
136
|
-
[tiff]
|
|
137
|
-
large-image-source-tiff>=1.23.4.dev24
|
|
138
|
-
|
|
139
|
-
[tifffile]
|
|
140
|
-
|
|
141
|
-
[tifffile:python_version >= "3.7"]
|
|
142
|
-
large-image-source-tifffile>=1.23.4.dev24
|
|
143
|
-
|
|
144
|
-
[tiledoutput]
|
|
145
|
-
pyvips
|
|
146
|
-
|
|
147
|
-
[vips]
|
|
148
|
-
large-image-source-vips>=1.23.4.dev24
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/docs/girder_annotation_config_options.rst
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/large_image.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|