large-image-source-multi 1.27.5.dev6__tar.gz → 1.30.7.dev12__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-source-multi-1.27.5.dev6/large_image_source_multi.egg-info → large_image_source_multi-1.30.7.dev12}/PKG-INFO +10 -3
- {large-image-source-multi-1.27.5.dev6 → large_image_source_multi-1.30.7.dev12}/README.rst +20 -40
- {large-image-source-multi-1.27.5.dev6 → large_image_source_multi-1.30.7.dev12}/large_image_source_multi/__init__.py +150 -46
- {large-image-source-multi-1.27.5.dev6 → large_image_source_multi-1.30.7.dev12/large_image_source_multi.egg-info}/PKG-INFO +10 -3
- large_image_source_multi-1.30.7.dev12/large_image_source_multi.egg-info/requires.txt +9 -0
- {large-image-source-multi-1.27.5.dev6 → large_image_source_multi-1.30.7.dev12}/setup.py +4 -3
- large-image-source-multi-1.27.5.dev6/large_image_source_multi.egg-info/requires.txt +0 -9
- {large-image-source-multi-1.27.5.dev6 → large_image_source_multi-1.30.7.dev12}/LICENSE +0 -0
- {large-image-source-multi-1.27.5.dev6 → large_image_source_multi-1.30.7.dev12}/docs/specification.rst +0 -0
- {large-image-source-multi-1.27.5.dev6 → large_image_source_multi-1.30.7.dev12}/large_image_source_multi/girder_source.py +0 -0
- {large-image-source-multi-1.27.5.dev6 → large_image_source_multi-1.30.7.dev12}/large_image_source_multi.egg-info/SOURCES.txt +0 -0
- {large-image-source-multi-1.27.5.dev6 → large_image_source_multi-1.30.7.dev12}/large_image_source_multi.egg-info/dependency_links.txt +0 -0
- {large-image-source-multi-1.27.5.dev6 → large_image_source_multi-1.30.7.dev12}/large_image_source_multi.egg-info/entry_points.txt +0 -0
- {large-image-source-multi-1.27.5.dev6 → large_image_source_multi-1.30.7.dev12}/large_image_source_multi.egg-info/top_level.txt +0 -0
- {large-image-source-multi-1.27.5.dev6 → large_image_source_multi-1.30.7.dev12}/pyproject.toml +0 -0
- {large-image-source-multi-1.27.5.dev6 → large_image_source_multi-1.30.7.dev12}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: large-image-source-multi
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.30.7.dev12
|
|
4
4
|
Summary: A tilesource for large_image to composite other tile sources
|
|
5
5
|
Home-page: https://github.com/girder/large_image
|
|
6
6
|
Author: Kitware, Inc.
|
|
@@ -15,10 +15,17 @@ Classifier: Programming Language :: Python :: 3.9
|
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.10
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.11
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
-
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Requires-Python: >=3.8
|
|
20
|
+
Description-Content-Type: text/x-rst
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: jsonschema
|
|
23
|
+
Requires-Dist: large-image>=1.30.7.dev12
|
|
24
|
+
Requires-Dist: pyyaml
|
|
19
25
|
Provides-Extra: all
|
|
26
|
+
Requires-Dist: scikit-image; extra == "all"
|
|
20
27
|
Provides-Extra: girder
|
|
21
|
-
|
|
28
|
+
Requires-Dist: girder-large-image>=1.30.7.dev12; extra == "girder"
|
|
22
29
|
|
|
23
30
|
A tilesource for large_image to composite other tile sources
|
|
24
31
|
|
|
@@ -15,7 +15,7 @@ Large Image
|
|
|
15
15
|
:target: https://codecov.io/github/girder/large_image?branch=master
|
|
16
16
|
:alt: codecov.io
|
|
17
17
|
|
|
18
|
-
.. |doi-badge| image:: https://img.shields.io/badge/DOI-10.5281%2Fzenodo.4723355-blue
|
|
18
|
+
.. |doi-badge| image:: https://img.shields.io/badge/DOI-10.5281%2Fzenodo.4723355-blue.svg
|
|
19
19
|
:target: https://zenodo.org/badge/latestdoi/45569214
|
|
20
20
|
|
|
21
21
|
.. |pypi-badge| image:: https://img.shields.io/pypi/v/large-image.svg?logo=python&logoColor=white
|
|
@@ -39,7 +39,7 @@ Highlights
|
|
|
39
39
|
Installation
|
|
40
40
|
------------
|
|
41
41
|
|
|
42
|
-
In addition to installing the ``large-image`` package, you'll need at least one tile source (a ``large-image-source-xxx`` package). You can install everything from the main project with one of these commands:
|
|
42
|
+
In addition to installing the base ``large-image`` package, you'll need at least one tile source which corresponds to your target file format(s) (a ``large-image-source-xxx`` package). You can install everything from the main project with one of these commands:
|
|
43
43
|
|
|
44
44
|
Pip
|
|
45
45
|
~~~
|
|
@@ -52,7 +52,7 @@ Install all tile sources on linux::
|
|
|
52
52
|
|
|
53
53
|
pip install large-image[all] --find-links https://girder.github.io/large_image_wheels
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
When using large-image with an instance of `Girder`_, install all tile sources and all Girder plugins on linux::
|
|
56
56
|
|
|
57
57
|
pip install large-image[all] girder-large-image-annotation[tasks] --find-links https://girder.github.io/large_image_wheels
|
|
58
58
|
|
|
@@ -60,8 +60,9 @@ Install all tile sources and all Girder plugins on linux::
|
|
|
60
60
|
Conda
|
|
61
61
|
~~~~~
|
|
62
62
|
|
|
63
|
-
Conda makes dependency management a bit easier if not on Linux.
|
|
63
|
+
Conda makes dependency management a bit easier if not on Linux. The base module, converter module, and two of the source modules are available on conda-forge. You can install the following::
|
|
64
64
|
|
|
65
|
+
conda install -c conda-forge large-image
|
|
65
66
|
conda install -c conda-forge large-image-source-gdal
|
|
66
67
|
conda install -c conda-forge large-image-source-tiff
|
|
67
68
|
conda install -c conda-forge large-image-converter
|
|
@@ -118,7 +119,7 @@ Large Image consists of several Python modules designed to work together. These
|
|
|
118
119
|
|
|
119
120
|
- ``large-image-source-deepzoom``: A tile source for reading Deepzoom tiles.
|
|
120
121
|
|
|
121
|
-
- ``large-image-source-dicom``: A tile source for reading DICOM WSI
|
|
122
|
+
- ``large-image-source-dicom``: A tile source for reading DICOM Whole Slide Images (WSI).
|
|
122
123
|
|
|
123
124
|
- ``large-image-source-gdal``: A tile source for reading geotiff files via GDAL. This handles source data with more complex transforms than the mapnik tile source.
|
|
124
125
|
|
|
@@ -128,62 +129,41 @@ Large Image consists of several Python modules designed to work together. These
|
|
|
128
129
|
|
|
129
130
|
- ``large-image-source-nd2``: A tile source for reading nd2 (NIS Element) images.
|
|
130
131
|
|
|
131
|
-
- ``large-image-source-ometiff``: A tile source using the tiff library that can handle
|
|
132
|
+
- ``large-image-source-ometiff``: A tile source using the tiff library that can handle most multi-frame OMETiff files that are compliant with the specification.
|
|
132
133
|
|
|
133
134
|
- ``large-image-source-openjpeg``: A tile source using the Glymur library to read jp2 (JPEG 2000) files.
|
|
134
135
|
|
|
135
136
|
- ``large-image-source-openslide``: A tile source using the OpenSlide library. This works with svs, ndpi, Mirax, tiff, vms, and other file formats.
|
|
136
137
|
|
|
137
|
-
- ``large-image-source-pil``: A tile source for small images via the Python Imaging Library (Pillow).
|
|
138
|
+
- ``large-image-source-pil``: A tile source for small images via the Python Imaging Library (Pillow). By default, the maximum size is 4096, but the maximum size can be configured.
|
|
138
139
|
|
|
139
140
|
- ``large-image-source-tiff``: A tile source for reading pyramidal tiff files in common compression formats.
|
|
140
141
|
|
|
141
142
|
- ``large-image-source-tifffile``: A tile source using the tifffile library that can handle a wide variety of tiff-like files.
|
|
142
143
|
|
|
143
|
-
- ``large-image-source-vips``: A tile source for reading any files handled by libvips. This also can be used for writing tiled images from numpy arrays.
|
|
144
|
+
- ``large-image-source-vips``: A tile source for reading any files handled by libvips. This also can be used for writing tiled images from numpy arrays (up to 4 dimensions).
|
|
144
145
|
|
|
145
|
-
- ``large-image-source-zarr``: A tile source using the zarr library that can handle OME-Zarr (OME-NGFF) files as well as some other zarr files.
|
|
146
|
+
- ``large-image-source-zarr``: A tile source using the zarr library that can handle OME-Zarr (OME-NGFF) files as well as some other zarr files. This can also be used for writing N-dimensional tiled images from numpy arrays. Written images can be saved as any supported format.
|
|
146
147
|
|
|
147
148
|
- ``large-image-source-test``: A tile source that generates test tiles, including a simple fractal pattern. Useful for testing extreme zoom levels.
|
|
148
149
|
|
|
149
|
-
- ``large-image-source-dummy``: A tile source that does nothing.
|
|
150
|
+
- ``large-image-source-dummy``: A tile source that does nothing. This is an absolutely minimal implementation of a tile source used for testing. If you want to create a custom tile source, start with this implementation.
|
|
150
151
|
|
|
151
|
-
Most tile sources can be used with girder-large-image. You can specific an extras_require of ``girder`` to include ``girder-large-image`` with the source.
|
|
152
152
|
|
|
153
|
-
|
|
153
|
+
As a `Girder`_ plugin, ``large-image`` adds end points to access all of the image formats it can read both to get metadata and to act as a tile server.
|
|
154
|
+
In the Girder UI, ``large-image`` shows images on item pages, and can show thumbnails in item lists when browsing folders.
|
|
155
|
+
There is also cache management to balance memory use and speed of response in Girder when ``large-image`` is used as a tile server.
|
|
154
156
|
|
|
155
|
-
|
|
156
|
-
You can specify extras_require of ``tasks`` to install a Girder Worker task that can convert otherwise unreadable images to pyramidal tiff files.
|
|
157
|
+
Most tile sources can be used with Girder Large Image. You can specify an extras_require of ``girder`` to install the following packages:
|
|
157
158
|
|
|
158
|
-
- ``girder-large-image
|
|
159
|
+
- ``girder-large-image``: Large Image as a Girder 3.x plugin.
|
|
160
|
+
You can install ``large-image[tasks]`` to install a Girder Worker task that can convert otherwise unreadable images to pyramidal tiff files.
|
|
161
|
+
|
|
162
|
+
- ``girder-large-image-annotation``: Adds models to the Girder database for supporting annotating large images. These annotations can be rendered on images. Annotations can include polygons, points, image overlays, and other types. Each annotation can have a label and metadata.
|
|
159
163
|
|
|
160
164
|
- ``large-image-tasks``: A utility for running the converter via Girder Worker.
|
|
161
165
|
You can specify an extras_require of ``girder`` to include modules needed to work with the Girder remote worker or ``worker`` to include modules needed on the remote side of the Girder remote worker. If neither is specified, some conversion tasks can be run using Girder local jobs.
|
|
162
166
|
|
|
163
167
|
|
|
164
|
-
Developer Installation
|
|
165
|
-
----------------------
|
|
166
|
-
|
|
167
|
-
To install all packages from source, clone the repository::
|
|
168
|
-
|
|
169
|
-
git clone https://github.com/girder/large_image.git
|
|
170
|
-
cd large_image
|
|
171
|
-
|
|
172
|
-
Install all packages and dependencies::
|
|
173
|
-
|
|
174
|
-
pip install -e . -r requirements-dev.txt
|
|
175
|
-
|
|
176
|
-
If you aren't developing with Girder 3, you can skip installing those components. Use ``requirements-dev-core.txt`` instead of ``requirements-dev.txt``::
|
|
177
|
-
|
|
178
|
-
pip install -e . -r requirements-dev-core.txt
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
Tile source prerequisites
|
|
182
|
-
=========================
|
|
183
|
-
|
|
184
|
-
Many tile sources have complex prerequisites. These can be installed directly using your system's package manager or from some prebuilt Python wheels for Linux. The prebuilt wheels are not official packages, but they can be used by instructing pip to use them by preference::
|
|
185
|
-
|
|
186
|
-
pip install -e . -r requirements-dev.txt --find-links https://girder.github.io/large_image_wheels
|
|
187
|
-
|
|
188
168
|
|
|
189
|
-
.. _Girder: https://
|
|
169
|
+
.. _Girder: https://girder.readthedocs.io/en/latest/
|
|
@@ -10,7 +10,6 @@ from importlib.metadata import PackageNotFoundError
|
|
|
10
10
|
from importlib.metadata import version as _importlib_version
|
|
11
11
|
from pathlib import Path
|
|
12
12
|
|
|
13
|
-
import jsonschema
|
|
14
13
|
import numpy as np
|
|
15
14
|
import yaml
|
|
16
15
|
|
|
@@ -27,6 +26,26 @@ except PackageNotFoundError:
|
|
|
27
26
|
# package is not installed
|
|
28
27
|
pass
|
|
29
28
|
|
|
29
|
+
jsonschema = None
|
|
30
|
+
_validator = None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _lazyImport():
|
|
34
|
+
"""
|
|
35
|
+
Import the jsonschema module. This is done when needed rather than in the
|
|
36
|
+
module initialization because it is slow.
|
|
37
|
+
"""
|
|
38
|
+
global jsonschema, _validator
|
|
39
|
+
|
|
40
|
+
if jsonschema is None:
|
|
41
|
+
try:
|
|
42
|
+
import jsonschema
|
|
43
|
+
|
|
44
|
+
_validator = jsonschema.Draft6Validator(MultiSourceSchema)
|
|
45
|
+
except ImportError:
|
|
46
|
+
msg = 'jsonschema module not found.'
|
|
47
|
+
raise TileSourceError(msg)
|
|
48
|
+
|
|
30
49
|
|
|
31
50
|
SourceEntrySchema = {
|
|
32
51
|
'type': 'object',
|
|
@@ -269,10 +288,24 @@ SourceEntrySchema = {
|
|
|
269
288
|
'type': 'array',
|
|
270
289
|
'items': {'type': 'integer'},
|
|
271
290
|
},
|
|
272
|
-
'
|
|
273
|
-
'params': {
|
|
291
|
+
'sampleScale': {
|
|
274
292
|
'description':
|
|
275
|
-
'
|
|
293
|
+
'Each pixel sample values is divided by this scale after any '
|
|
294
|
+
'sampleOffset has been applied',
|
|
295
|
+
'type': 'number',
|
|
296
|
+
},
|
|
297
|
+
'sampleOffset': {
|
|
298
|
+
'description':
|
|
299
|
+
'This is added to each pixel sample value before any '
|
|
300
|
+
'sampleScale is applied',
|
|
301
|
+
'type': 'number',
|
|
302
|
+
},
|
|
303
|
+
'style': {
|
|
304
|
+
'description': 'A style specification to pass to the base tile source',
|
|
305
|
+
'type': 'object',
|
|
306
|
+
},
|
|
307
|
+
'params': {
|
|
308
|
+
'description': 'Additional parameters to pass to the base tile source',
|
|
276
309
|
'type': 'object',
|
|
277
310
|
},
|
|
278
311
|
},
|
|
@@ -333,6 +366,10 @@ MultiSourceSchema = {
|
|
|
333
366
|
'layout and size, assume all sources are so similar',
|
|
334
367
|
'type': 'boolean',
|
|
335
368
|
},
|
|
369
|
+
'dtype': {
|
|
370
|
+
'description': 'If present, a numpy dtype name to use for the data.',
|
|
371
|
+
'type': 'string',
|
|
372
|
+
},
|
|
336
373
|
'singleBand': {
|
|
337
374
|
'description':
|
|
338
375
|
'If true, output only the first band of compositied results',
|
|
@@ -376,6 +413,7 @@ class MultiFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
|
376
413
|
None: SourcePriority.FALLBACK,
|
|
377
414
|
'application/json': SourcePriority.PREFERRED,
|
|
378
415
|
'application/yaml': SourcePriority.PREFERRED,
|
|
416
|
+
'text/yaml': SourcePriority.PREFERRED,
|
|
379
417
|
}
|
|
380
418
|
|
|
381
419
|
_minTileSize = 64
|
|
@@ -383,9 +421,7 @@ class MultiFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
|
383
421
|
_defaultTileSize = 256
|
|
384
422
|
_maxOpenHandles = 6
|
|
385
423
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
def __init__(self, path, **kwargs):
|
|
424
|
+
def __init__(self, path, **kwargs): # noqa
|
|
389
425
|
"""
|
|
390
426
|
Initialize the tile class. See the base class for other available
|
|
391
427
|
parameters.
|
|
@@ -394,11 +430,22 @@ class MultiFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
|
394
430
|
"""
|
|
395
431
|
super().__init__(path, **kwargs)
|
|
396
432
|
|
|
433
|
+
_lazyImport()
|
|
434
|
+
self._validator = _validator
|
|
397
435
|
self._largeImagePath = self._getLargeImagePath()
|
|
398
436
|
self._lastOpenSourceLock = threading.RLock()
|
|
399
437
|
# 'c' must be first as channels are special because they can have names
|
|
400
438
|
self._axesList = ['c', 'z', 't', 'xy']
|
|
401
|
-
if
|
|
439
|
+
if isinstance(path, dict) and 'sources' in path:
|
|
440
|
+
self._info = path.copy()
|
|
441
|
+
self._basePath = '.'
|
|
442
|
+
self._largeImagePath = '.'
|
|
443
|
+
try:
|
|
444
|
+
self._validator.validate(self._info)
|
|
445
|
+
except jsonschema.ValidationError:
|
|
446
|
+
msg = 'File cannot be validated via multi-source reader.'
|
|
447
|
+
raise TileSourceError(msg)
|
|
448
|
+
elif not os.path.isfile(self._largeImagePath):
|
|
402
449
|
try:
|
|
403
450
|
possibleYaml = self._largeImagePath.split('multi://', 1)[-1]
|
|
404
451
|
self._info = yaml.safe_load(possibleYaml)
|
|
@@ -410,7 +457,8 @@ class MultiFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
|
410
457
|
try:
|
|
411
458
|
with builtins.open(self._largeImagePath) as fptr:
|
|
412
459
|
start = fptr.read(1024).strip()
|
|
413
|
-
if start[:1] not in ('{', '#', '-') and
|
|
460
|
+
if (start[:1] not in ('{', '#', '-') and
|
|
461
|
+
(start[:1] < 'a' or start[:1] > 'z')) or 'FeatureCollection' in start:
|
|
414
462
|
msg = 'File cannot be opened via multi-source reader.'
|
|
415
463
|
raise TileSourceError(msg)
|
|
416
464
|
fptr.seek(0)
|
|
@@ -661,9 +709,15 @@ class MultiFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
|
661
709
|
aKey = tuple(self._axisKey(source, frame.get(f'Index{axis.upper()}') or 0, axis)
|
|
662
710
|
for axis in self._axesList)
|
|
663
711
|
channel = channels[cIdx] if cIdx < len(channels) else None
|
|
712
|
+
# We add the channel name to our channel list if the individual
|
|
713
|
+
# source lists the channel name or a set of channels OR the source
|
|
714
|
+
# appends channels to an existing set.
|
|
664
715
|
if channel and channel not in self._channels and (
|
|
665
|
-
'channel' in source or 'channels' in source
|
|
716
|
+
'channel' in source or 'channels' in source or
|
|
717
|
+
len(self._channels) == aKey[0]):
|
|
666
718
|
self._channels.append(channel)
|
|
719
|
+
# Adjust the channel number if the source named the channel; do not
|
|
720
|
+
# do so in other cases
|
|
667
721
|
if (channel and channel in self._channels and
|
|
668
722
|
'c' not in source and 'cValues' not in source):
|
|
669
723
|
aKey = tuple([self._channels.index(channel)] + list(aKey[1:]))
|
|
@@ -759,7 +813,11 @@ class MultiFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
|
759
813
|
self.tileWidth = self.tileWidth or ts.tileWidth
|
|
760
814
|
self.tileHeight = self.tileHeight or ts.tileHeight
|
|
761
815
|
if not hasattr(self, '_firstdtype'):
|
|
762
|
-
self._firstdtype =
|
|
816
|
+
self._firstdtype = (
|
|
817
|
+
ts.dtype if not self._info.get('dtype') else
|
|
818
|
+
np.dtype(self._info['dtype']))
|
|
819
|
+
if self._info.get('dtype'):
|
|
820
|
+
self._dtype = np.dtype(self._info['dtype'])
|
|
763
821
|
if not numChecked:
|
|
764
822
|
tsMag = ts.getNativeMagnification()
|
|
765
823
|
for key in self._nativeMagnification:
|
|
@@ -773,7 +831,7 @@ class MultiFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
|
773
831
|
self._bands = {}
|
|
774
832
|
self._bands.update(tsMeta['bands'])
|
|
775
833
|
lastSource = source
|
|
776
|
-
self.
|
|
834
|
+
self._bandCount = 1 if self._info.get('singleBand') else (
|
|
777
835
|
len(self._bands) if hasattr(self, '_bands') else (bandCount or None))
|
|
778
836
|
bbox = self._sourceBoundingBox(source, tsMeta['sizeX'], tsMeta['sizeY'])
|
|
779
837
|
computedWidth = max(computedWidth, int(math.ceil(bbox['right'])))
|
|
@@ -828,7 +886,8 @@ class MultiFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
|
828
886
|
with self._lastOpenSourceLock:
|
|
829
887
|
if (hasattr(self, '_lastOpenSource') and
|
|
830
888
|
self._lastOpenSource['source'] == source and
|
|
831
|
-
self._lastOpenSource['params'] == params
|
|
889
|
+
(self._lastOpenSource['params'] == params or (
|
|
890
|
+
params == {} and self._lastOpenSource['params'] is None))):
|
|
832
891
|
return self._lastOpenSource['ts']
|
|
833
892
|
if not len(large_image.tilesource.AvailableTileSources):
|
|
834
893
|
large_image.tilesource.loadTileSources()
|
|
@@ -841,6 +900,22 @@ class MultiFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
|
841
900
|
if params is None:
|
|
842
901
|
params = source.get('params', {})
|
|
843
902
|
ts = openFunc(source['path'], **params)
|
|
903
|
+
if (self._dtype and np.dtype(ts.dtype).kind == 'f' and
|
|
904
|
+
(self._dtype == 'check' or np.dtype(self._dtype).kind != 'f') and
|
|
905
|
+
'sampleScale' not in source and 'sampleOffset' not in source):
|
|
906
|
+
minval = maxval = 0
|
|
907
|
+
for f in range(ts.frames):
|
|
908
|
+
ftile = ts.getTile(x=0, y=0, z=0, frame=f, numpyAllowed='always')
|
|
909
|
+
minval = min(minval, np.amin(ftile))
|
|
910
|
+
maxval = max(maxval, np.amax(ftile))
|
|
911
|
+
if minval >= 0 and maxval <= 1:
|
|
912
|
+
source['sampleScale'] = None
|
|
913
|
+
elif minval >= 0:
|
|
914
|
+
source['sampleScale'] = 2 ** math.ceil(math.log2(maxval))
|
|
915
|
+
else:
|
|
916
|
+
source['sampleScale'] = 2 ** math.ceil(math.log2(max(-minval, maxval)) + 1)
|
|
917
|
+
source['sampleOffset'] = source['sampleScale'] / 2
|
|
918
|
+
source['sourceName'] = ts.name
|
|
844
919
|
with self._lastOpenSourceLock:
|
|
845
920
|
self._lastOpenSource = {
|
|
846
921
|
'source': source,
|
|
@@ -860,7 +935,7 @@ class MultiFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
|
860
935
|
None if the associated image doesn't exist.
|
|
861
936
|
"""
|
|
862
937
|
if imageKey not in self._associatedImages:
|
|
863
|
-
return
|
|
938
|
+
return None
|
|
864
939
|
source = self._sources[self._associatedImages[imageKey]['sourcenum']]
|
|
865
940
|
ts = self._openSource(source)
|
|
866
941
|
return ts.getAssociatedImage(self._associatedImages[imageKey]['key'], *args, **kwargs)
|
|
@@ -894,7 +969,7 @@ class MultiFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
|
894
969
|
"""
|
|
895
970
|
Return additional known metadata about the tile source. Data returned
|
|
896
971
|
from this method is not guaranteed to be in any particular format or
|
|
897
|
-
have specific values.
|
|
972
|
+
have specific values. Also, only the first 100 sources are used.
|
|
898
973
|
|
|
899
974
|
:returns: a dictionary of data or None.
|
|
900
975
|
"""
|
|
@@ -903,7 +978,7 @@ class MultiFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
|
903
978
|
'sources': copy.deepcopy(self._sources),
|
|
904
979
|
'sourceFiles': [],
|
|
905
980
|
}
|
|
906
|
-
for path in self._sourcePaths.values():
|
|
981
|
+
for path in list(self._sourcePaths.values())[:100]:
|
|
907
982
|
source = self._sources[min(path['sourcenum'])]
|
|
908
983
|
ts = self._openSource(source)
|
|
909
984
|
result['sourceFiles'].append({
|
|
@@ -937,38 +1012,39 @@ class MultiFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
|
937
1012
|
(tile.shape[0] + y - base.shape[0], base.shape[1], base.shape[2]),
|
|
938
1013
|
dtype=base.dtype)
|
|
939
1014
|
if base.shape[2] in {2, 4}:
|
|
940
|
-
vfill[:, :, -1] = fullAlphaValue(
|
|
1015
|
+
vfill[:, :, -1] = fullAlphaValue(base.dtype)
|
|
941
1016
|
base = np.vstack((base, vfill))
|
|
942
1017
|
if base.shape[1] < tile.shape[1] + x:
|
|
943
1018
|
hfill = np.zeros(
|
|
944
1019
|
(base.shape[0], tile.shape[1] + x - base.shape[1], base.shape[2]),
|
|
945
1020
|
dtype=base.dtype)
|
|
946
1021
|
if base.shape[2] in {2, 4}:
|
|
947
|
-
hfill[:, :, -1] = fullAlphaValue(
|
|
1022
|
+
hfill[:, :, -1] = fullAlphaValue(base.dtype)
|
|
948
1023
|
base = np.hstack((base, hfill))
|
|
949
1024
|
if base.flags.writeable is False:
|
|
950
1025
|
base = base.copy()
|
|
951
1026
|
if base.shape[2] in {2, 4}:
|
|
952
1027
|
baseA = base[y:y + tile.shape[0], x:x + tile.shape[1], -1].astype(
|
|
953
|
-
float) / fullAlphaValue(
|
|
954
|
-
tileA = tile[:, :, -1].astype(float) / fullAlphaValue(
|
|
1028
|
+
float) / fullAlphaValue(base.dtype)
|
|
1029
|
+
tileA = tile[:, :, -1].astype(float) / fullAlphaValue(tile.dtype)
|
|
955
1030
|
outA = tileA + baseA * (1 - tileA)
|
|
956
1031
|
base[y:y + tile.shape[0], x:x + tile.shape[1], :-1] = (
|
|
957
|
-
np.where(tileA[..., np.newaxis], tile[:, :, :-1], 0) +
|
|
1032
|
+
np.where(tileA[..., np.newaxis], tile[:, :, :-1] * tileA[..., np.newaxis], 0) +
|
|
958
1033
|
base[y:y + tile.shape[0], x:x + tile.shape[1], :-1] * baseA[..., np.newaxis] *
|
|
959
1034
|
(1 - tileA[..., np.newaxis])
|
|
960
1035
|
) / np.where(outA[..., np.newaxis], outA[..., np.newaxis], 1)
|
|
961
|
-
base[y:y + tile.shape[0], x:x + tile.shape[1], -1] = outA * fullAlphaValue(
|
|
1036
|
+
base[y:y + tile.shape[0], x:x + tile.shape[1], -1] = outA * fullAlphaValue(base.dtype)
|
|
962
1037
|
else:
|
|
963
1038
|
base[y:y + tile.shape[0], x:x + tile.shape[1], :] = tile
|
|
964
1039
|
return base
|
|
965
1040
|
|
|
966
|
-
def _getTransformedTile(self, ts, transform, corners, scale, frame,
|
|
1041
|
+
def _getTransformedTile(self, ts, transform, corners, scale, frame,
|
|
1042
|
+
crop=None, firstMerge=False):
|
|
967
1043
|
"""
|
|
968
1044
|
Determine where the target tile's corners are located on the source.
|
|
969
1045
|
Fetch that so that we have at least sqrt(2) more resolution, then use
|
|
970
|
-
scikit-image warp to transform it. scikit-image does a better
|
|
971
|
-
scipy.ndimage.affine_transform.
|
|
1046
|
+
scikit-image warp to transform it. scikit-image does a better and
|
|
1047
|
+
faster job than scipy.ndimage.affine_transform.
|
|
972
1048
|
|
|
973
1049
|
:param ts: the source of the image to transform.
|
|
974
1050
|
:param transform: a 3x3 affine 2d matrix for transforming the source
|
|
@@ -980,6 +1056,9 @@ class MultiFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
|
980
1056
|
:param crop: an optional dictionary to crop the source image in full
|
|
981
1057
|
resolution, untransformed coordinates. This may contain left, top,
|
|
982
1058
|
right, and bottom values in pixels.
|
|
1059
|
+
:param firstMerge: if False and using an alpha channel, transform
|
|
1060
|
+
with nearest neighbor rather than a higher order function to
|
|
1061
|
+
avoid transparency effects.
|
|
983
1062
|
:returns: a numpy array tile or None, x, y coordinates within the
|
|
984
1063
|
target tile for the placement of the numpy tile array.
|
|
985
1064
|
"""
|
|
@@ -1010,7 +1089,7 @@ class MultiFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
|
1010
1089
|
# we only need every 1/srcscale pixel.
|
|
1011
1090
|
srcscale = int(2 ** math.log2(max(1, srcscale)))
|
|
1012
1091
|
# Pad to reduce edge effects at tile boundaries
|
|
1013
|
-
border = int(math.ceil(
|
|
1092
|
+
border = int(math.ceil(4 * srcscale))
|
|
1014
1093
|
region = {
|
|
1015
1094
|
'left': int(max(0, minx - border) // srcscale) * srcscale,
|
|
1016
1095
|
'top': int(max(0, miny - border) // srcscale) * srcscale,
|
|
@@ -1057,28 +1136,38 @@ class MultiFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
|
1057
1136
|
x, y = int(math.floor(x)), int(math.floor(y))
|
|
1058
1137
|
# Recompute where the source corners will land
|
|
1059
1138
|
destcorners = (np.dot(transform, regioncorners.T).T).tolist()
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1139
|
+
destShape = [
|
|
1140
|
+
max(max(math.ceil(c[1]) for c in destcorners), srcImage.shape[0]),
|
|
1141
|
+
max(max(math.ceil(c[0]) for c in destcorners), srcImage.shape[1]),
|
|
1142
|
+
]
|
|
1143
|
+
if max(0, -x) or max(0, -y):
|
|
1144
|
+
transform[0][2] -= max(0, -x)
|
|
1145
|
+
transform[1][2] -= max(0, -y)
|
|
1146
|
+
destShape[0] -= max(0, -y)
|
|
1147
|
+
destShape[1] -= max(0, -x)
|
|
1148
|
+
x += max(0, -x)
|
|
1149
|
+
y += max(0, -y)
|
|
1150
|
+
destShape = [min(destShape[0], outh - y), min(destShape[1], outw - x)]
|
|
1151
|
+
if destShape[0] <= 0 or destShape[1] <= 0:
|
|
1152
|
+
return None, None, None
|
|
1153
|
+
# Add an alpha band if needed. This has to be done before the
|
|
1154
|
+
# transform if it isn't the first tile, since the unused transformed
|
|
1155
|
+
# areas need to have a zero alpha value
|
|
1063
1156
|
if srcImage.shape[2] in {1, 3}:
|
|
1064
1157
|
_, srcImage = _makeSameChannelDepth(np.zeros((1, 1, srcImage.shape[2] + 1)), srcImage)
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1158
|
+
useNearest = srcImage.shape[2] in {2, 4} and not firstMerge
|
|
1159
|
+
# skimage.transform.warp is faster and has less artifacts than
|
|
1160
|
+
# scipy.ndimage.affine_transform. It is faster than using cupy's
|
|
1161
|
+
# version of scipy's affine_transform when the source and destination
|
|
1162
|
+
# images are converted from numpy to cupy and back in this method.
|
|
1070
1163
|
destImage = skimage.transform.warp(
|
|
1164
|
+
# Although using np.float32 could reduce memory use, it doesn't
|
|
1165
|
+
# provide any speed improvement
|
|
1071
1166
|
srcImage.astype(float),
|
|
1072
|
-
skimage.transform.AffineTransform(np.linalg.inv(
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
x += max(0, -x)
|
|
1077
|
-
y += max(0, -y)
|
|
1078
|
-
if x >= outw or y >= outh:
|
|
1079
|
-
return None, None, None
|
|
1080
|
-
destImage = destImage[:min(destImage.shape[0], outh - y),
|
|
1081
|
-
:min(destImage.shape[1], outw - x), :]
|
|
1167
|
+
skimage.transform.AffineTransform(np.linalg.inv(transform)),
|
|
1168
|
+
order=0 if useNearest else 3,
|
|
1169
|
+
output_shape=(destShape[0], destShape[1], srcImage.shape[2]),
|
|
1170
|
+
).astype(srcImage.dtype)
|
|
1082
1171
|
return destImage, x, y
|
|
1083
1172
|
|
|
1084
1173
|
def _addSourceToTile(self, tile, sourceEntry, corners, scale):
|
|
@@ -1098,12 +1187,12 @@ class MultiFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
|
1098
1187
|
:returns: a numpy array of the tile.
|
|
1099
1188
|
"""
|
|
1100
1189
|
source = self._sources[sourceEntry['sourcenum']]
|
|
1101
|
-
ts = self._openSource(source, sourceEntry['kwargs'])
|
|
1102
1190
|
# If tile is outside of bounding box, skip it
|
|
1103
1191
|
bbox = source['bbox']
|
|
1104
1192
|
if (corners[2][0] <= bbox['left'] or corners[0][0] >= bbox['right'] or
|
|
1105
1193
|
corners[2][1] <= bbox['top'] or corners[0][1] >= bbox['bottom']):
|
|
1106
1194
|
return tile
|
|
1195
|
+
ts = self._openSource(source, sourceEntry['kwargs'])
|
|
1107
1196
|
transform = bbox.get('transform')
|
|
1108
1197
|
x = y = 0
|
|
1109
1198
|
# If there is no transform or the diagonals are positive and there is
|
|
@@ -1153,8 +1242,23 @@ class MultiFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
|
1153
1242
|
else:
|
|
1154
1243
|
sourceTile, x, y = self._getTransformedTile(
|
|
1155
1244
|
ts, transform, corners, scale, sourceEntry.get('frame', 0),
|
|
1156
|
-
source.get('position', {}).get('crop')
|
|
1245
|
+
source.get('position', {}).get('crop'),
|
|
1246
|
+
firstMerge=tile is None)
|
|
1157
1247
|
if sourceTile is not None and all(dim > 0 for dim in sourceTile.shape):
|
|
1248
|
+
targetDtype = np.dtype(self._info.get('dtype', ts.dtype))
|
|
1249
|
+
changeDtype = sourceTile.dtype != targetDtype
|
|
1250
|
+
if source.get('sampleScale') or source.get('sampleOffset'):
|
|
1251
|
+
sourceTile = sourceTile.astype(float)
|
|
1252
|
+
if source.get('sampleOffset'):
|
|
1253
|
+
sourceTile[:, :, :-1] += source['sampleOffset']
|
|
1254
|
+
if source.get('sampleScale') and source.get('sampleScale') != 1:
|
|
1255
|
+
sourceTile[:, :, :-1] /= source['sampleScale']
|
|
1256
|
+
if sourceTile.dtype != targetDtype:
|
|
1257
|
+
if changeDtype:
|
|
1258
|
+
sourceTile = (
|
|
1259
|
+
sourceTile.astype(float) * fullAlphaValue(targetDtype) /
|
|
1260
|
+
fullAlphaValue(sourceTile))
|
|
1261
|
+
sourceTile = sourceTile.astype(targetDtype)
|
|
1158
1262
|
tile = self._mergeTiles(tile, sourceTile, x, y)
|
|
1159
1263
|
return tile
|
|
1160
1264
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: large-image-source-multi
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.30.7.dev12
|
|
4
4
|
Summary: A tilesource for large_image to composite other tile sources
|
|
5
5
|
Home-page: https://github.com/girder/large_image
|
|
6
6
|
Author: Kitware, Inc.
|
|
@@ -15,10 +15,17 @@ Classifier: Programming Language :: Python :: 3.9
|
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.10
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.11
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
-
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Requires-Python: >=3.8
|
|
20
|
+
Description-Content-Type: text/x-rst
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: jsonschema
|
|
23
|
+
Requires-Dist: large-image>=1.30.7.dev12
|
|
24
|
+
Requires-Dist: pyyaml
|
|
19
25
|
Provides-Extra: all
|
|
26
|
+
Requires-Dist: scikit-image; extra == "all"
|
|
20
27
|
Provides-Extra: girder
|
|
21
|
-
|
|
28
|
+
Requires-Dist: girder-large-image>=1.30.7.dev12; extra == "girder"
|
|
22
29
|
|
|
23
30
|
A tilesource for large_image to composite other tile sources
|
|
24
31
|
|
|
@@ -19,8 +19,7 @@ def prerelease_local_scheme(version):
|
|
|
19
19
|
|
|
20
20
|
if os.getenv('CIRCLE_BRANCH') in ('master', ):
|
|
21
21
|
return ''
|
|
22
|
-
|
|
23
|
-
return get_local_node_and_date(version)
|
|
22
|
+
return get_local_node_and_date(version)
|
|
24
23
|
|
|
25
24
|
|
|
26
25
|
try:
|
|
@@ -37,6 +36,7 @@ setup(
|
|
|
37
36
|
'fallback_version': '0.0.0'},
|
|
38
37
|
description=description,
|
|
39
38
|
long_description=long_description,
|
|
39
|
+
long_description_content_type='text/x-rst',
|
|
40
40
|
license='Apache Software License 2.0',
|
|
41
41
|
author='Kitware, Inc.',
|
|
42
42
|
author_email='kitware@kitware.com',
|
|
@@ -49,6 +49,7 @@ setup(
|
|
|
49
49
|
'Programming Language :: Python :: 3.10',
|
|
50
50
|
'Programming Language :: Python :: 3.11',
|
|
51
51
|
'Programming Language :: Python :: 3.12',
|
|
52
|
+
'Programming Language :: Python :: 3.13',
|
|
52
53
|
],
|
|
53
54
|
install_requires=[
|
|
54
55
|
'jsonschema',
|
|
@@ -64,7 +65,7 @@ setup(
|
|
|
64
65
|
keywords='large_image, tile source',
|
|
65
66
|
packages=find_packages(exclude=['test', 'test.*']),
|
|
66
67
|
url='https://github.com/girder/large_image',
|
|
67
|
-
python_requires='>=3.
|
|
68
|
+
python_requires='>=3.8',
|
|
68
69
|
entry_points={
|
|
69
70
|
'large_image.source': [
|
|
70
71
|
'multi = large_image_source_multi:MultiFileTileSource',
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{large-image-source-multi-1.27.5.dev6 → large_image_source_multi-1.30.7.dev12}/pyproject.toml
RENAMED
|
File without changes
|
|
File without changes
|