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.
Files changed (44) hide show
  1. {large-image-1.23.4.dev24/large_image.egg-info → large-image-1.23.5.dev2}/PKG-INFO +1 -1
  2. large-image-1.23.5.dev2/large_image/tilesource/jupyter.py +415 -0
  3. {large-image-1.23.4.dev24 → large-image-1.23.5.dev2/large_image.egg-info}/PKG-INFO +1 -1
  4. large-image-1.23.5.dev2/large_image.egg-info/requires.txt +148 -0
  5. large-image-1.23.4.dev24/large_image/tilesource/jupyter.py +0 -248
  6. large-image-1.23.4.dev24/large_image.egg-info/requires.txt +0 -148
  7. {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/LICENSE +0 -0
  8. {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/NOTICE +0 -0
  9. {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/README.rst +0 -0
  10. {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/docs/annotations.rst +0 -0
  11. {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/docs/conf.py +0 -0
  12. {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/docs/config_options.rst +0 -0
  13. {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/docs/development.rst +0 -0
  14. {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/docs/example_usage.rst +0 -0
  15. {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/docs/girder_annotation_config_options.rst +0 -0
  16. {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/docs/girder_config_options.rst +0 -0
  17. {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/docs/image_conversion.rst +0 -0
  18. {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/docs/index.rst +0 -0
  19. {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/docs/make_docs.sh +0 -0
  20. {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/docs/multi_source_specification.rst +0 -0
  21. {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/docs/static/custom.css +0 -0
  22. {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/docs/tilesource_options.rst +0 -0
  23. {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/docs/upgrade.rst +0 -0
  24. {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/large_image/__init__.py +0 -0
  25. {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/large_image/cache_util/__init__.py +0 -0
  26. {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/large_image/cache_util/base.py +0 -0
  27. {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/large_image/cache_util/cache.py +0 -0
  28. {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/large_image/cache_util/cachefactory.py +0 -0
  29. {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/large_image/cache_util/memcache.py +0 -0
  30. {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/large_image/config.py +0 -0
  31. {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/large_image/constants.py +0 -0
  32. {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/large_image/exceptions.py +0 -0
  33. {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/large_image/tilesource/__init__.py +0 -0
  34. {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/large_image/tilesource/base.py +0 -0
  35. {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/large_image/tilesource/geo.py +0 -0
  36. {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/large_image/tilesource/stylefuncs.py +0 -0
  37. {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/large_image/tilesource/tiledict.py +0 -0
  38. {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/large_image/tilesource/utilities.py +0 -0
  39. {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/large_image.egg-info/SOURCES.txt +0 -0
  40. {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/large_image.egg-info/dependency_links.txt +0 -0
  41. {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/large_image.egg-info/not-zip-safe +0 -0
  42. {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/large_image.egg-info/top_level.txt +0 -0
  43. {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/setup.cfg +0 -0
  44. {large-image-1.23.4.dev24 → large-image-1.23.5.dev2}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: large-image
3
- Version: 1.23.4.dev24
3
+ Version: 1.23.5.dev2
4
4
  Summary: Python modules to work with large, multiresolution images.
5
5
  Home-page: https://github.com/girder/large_image
6
6
  Author: Kitware, Inc.
@@ -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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: large-image
3
- Version: 1.23.4.dev24
3
+ Version: 1.23.5.dev2
4
4
  Summary: Python modules to work with large, multiresolution images.
5
5
  Home-page: https://github.com/girder/large_image
6
6
  Author: Kitware, Inc.
@@ -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