large-image-source-tiff 1.27.5.dev65__tar.gz → 1.30.7.dev10__tar.gz
Sign up to get free protection for your applications and to get access to all the features.
- {large-image-source-tiff-1.27.5.dev65 → large_image_source_tiff-1.30.7.dev10}/PKG-INFO +10 -2
- {large-image-source-tiff-1.27.5.dev65 → large_image_source_tiff-1.30.7.dev10}/README.rst +20 -40
- {large-image-source-tiff-1.27.5.dev65 → large_image_source_tiff-1.30.7.dev10}/large_image_source_tiff/__init__.py +82 -31
- {large-image-source-tiff-1.27.5.dev65 → large_image_source_tiff-1.30.7.dev10}/large_image_source_tiff/tiff_reader.py +55 -17
- {large-image-source-tiff-1.27.5.dev65 → large_image_source_tiff-1.30.7.dev10}/large_image_source_tiff.egg-info/PKG-INFO +10 -2
- large_image_source_tiff-1.30.7.dev10/large_image_source_tiff.egg-info/requires.txt +9 -0
- {large-image-source-tiff-1.27.5.dev65 → large_image_source_tiff-1.30.7.dev10}/setup.py +6 -2
- large-image-source-tiff-1.27.5.dev65/large_image_source_tiff.egg-info/requires.txt +0 -6
- {large-image-source-tiff-1.27.5.dev65 → large_image_source_tiff-1.30.7.dev10}/LICENSE +0 -0
- {large-image-source-tiff-1.27.5.dev65 → large_image_source_tiff-1.30.7.dev10}/large_image_source_tiff/exceptions.py +0 -0
- {large-image-source-tiff-1.27.5.dev65 → large_image_source_tiff-1.30.7.dev10}/large_image_source_tiff/girder_source.py +0 -0
- {large-image-source-tiff-1.27.5.dev65 → large_image_source_tiff-1.30.7.dev10}/large_image_source_tiff.egg-info/SOURCES.txt +0 -0
- {large-image-source-tiff-1.27.5.dev65 → large_image_source_tiff-1.30.7.dev10}/large_image_source_tiff.egg-info/dependency_links.txt +0 -0
- {large-image-source-tiff-1.27.5.dev65 → large_image_source_tiff-1.30.7.dev10}/large_image_source_tiff.egg-info/entry_points.txt +0 -0
- {large-image-source-tiff-1.27.5.dev65 → large_image_source_tiff-1.30.7.dev10}/large_image_source_tiff.egg-info/top_level.txt +0 -0
- {large-image-source-tiff-1.27.5.dev65 → large_image_source_tiff-1.30.7.dev10}/pyproject.toml +0 -0
- {large-image-source-tiff-1.27.5.dev65 → large_image_source_tiff-1.30.7.dev10}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: large-image-source-tiff
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.30.7.dev10
|
4
4
|
Summary: A TIFF tilesource for large_image.
|
5
5
|
Home-page: https://github.com/girder/large_image
|
6
6
|
Author: Kitware, Inc.
|
@@ -15,9 +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
|
+
Classifier: Programming Language :: Python :: 3.13
|
18
19
|
Requires-Python: >=3.8
|
19
|
-
|
20
|
+
Description-Content-Type: text/x-rst
|
20
21
|
License-File: LICENSE
|
22
|
+
Requires-Dist: large-image>=1.30.7.dev10
|
23
|
+
Requires-Dist: pylibtiff
|
24
|
+
Requires-Dist: tifftools>=1.2.0
|
25
|
+
Provides-Extra: all
|
26
|
+
Requires-Dist: pylibjpeg-openjpeg; extra == "all"
|
27
|
+
Provides-Extra: girder
|
28
|
+
Requires-Dist: girder-large-image>=1.30.7.dev10; extra == "girder"
|
21
29
|
|
22
30
|
A TIFF tilesource for large_image.
|
23
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/
|
@@ -30,7 +30,9 @@ import tifftools
|
|
30
30
|
|
31
31
|
from large_image.cache_util import LruCacheMetaclass, methodcache
|
32
32
|
from large_image.constants import TILE_FORMAT_NUMPY, TILE_FORMAT_PIL, SourcePriority
|
33
|
-
from large_image.exceptions import TileSourceError,
|
33
|
+
from large_image.exceptions import (TileSourceError,
|
34
|
+
TileSourceFileNotFoundError,
|
35
|
+
TileSourceMalformedError)
|
34
36
|
from large_image.tilesource import FileTileSource, nearPowerOfTwo
|
35
37
|
|
36
38
|
from . import tiff_reader
|
@@ -73,6 +75,7 @@ class TiffFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
73
75
|
}
|
74
76
|
|
75
77
|
_maxAssociatedImageSize = 8192
|
78
|
+
_maxUntiledImage = 4096
|
76
79
|
|
77
80
|
def __init__(self, path, **kwargs): # noqa
|
78
81
|
"""
|
@@ -85,18 +88,20 @@ class TiffFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
85
88
|
|
86
89
|
self._largeImagePath = str(self._getLargeImagePath())
|
87
90
|
|
91
|
+
lastException = None
|
88
92
|
try:
|
89
93
|
self._initWithTiffTools()
|
90
94
|
return
|
95
|
+
except TileSourceMalformedError:
|
96
|
+
raise
|
91
97
|
except Exception as exc:
|
92
98
|
self.logger.debug('Cannot read with tifftools route; %r', exc)
|
99
|
+
lastException = exc
|
93
100
|
|
94
101
|
alldir = []
|
95
102
|
try:
|
96
103
|
if hasattr(self, '_info'):
|
97
104
|
alldir = self._scanDirectories()
|
98
|
-
else:
|
99
|
-
lastException = 'Could not parse file with tifftools'
|
100
105
|
except IOOpenTiffError:
|
101
106
|
msg = 'File cannot be opened via tiff source.'
|
102
107
|
raise TileSourceError(msg)
|
@@ -136,7 +141,7 @@ class TiffFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
136
141
|
continue
|
137
142
|
# If a layer is a multiple of the tile size, the number of tiles
|
138
143
|
# should be a power of two rounded up from the primary.
|
139
|
-
if
|
144
|
+
if not (td.imageWidth % td.tileWidth) and not (td.imageHeight % td.tileHeight):
|
140
145
|
htw = highest.imageWidth // td.tileWidth
|
141
146
|
hth = highest.imageHeight // td.tileHeight
|
142
147
|
ttw = td.imageWidth // td.tileWidth
|
@@ -157,7 +162,7 @@ class TiffFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
157
162
|
tifftools.constants.SampleFormat[sampleformat or 1].name,
|
158
163
|
bitspersample,
|
159
164
|
))
|
160
|
-
self._bandCount = highest._tiffInfo.get('samplesperpixel')
|
165
|
+
self._bandCount = highest._tiffInfo.get('samplesperpixel', 1)
|
161
166
|
# Sort the directories so that the highest resolution is the last one;
|
162
167
|
# if a level is missing, put a None value in its place.
|
163
168
|
self._tiffDirectories = [directories.get(key) for key in
|
@@ -252,8 +257,13 @@ class TiffFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
252
257
|
"""
|
253
258
|
sizeX = ifd['tags'][tifftools.Tag.ImageWidth.value]['data'][0]
|
254
259
|
sizeY = ifd['tags'][tifftools.Tag.ImageLength.value]['data'][0]
|
255
|
-
|
256
|
-
|
260
|
+
if tifftools.Tag.TileWidth.value in baseifd['tags']:
|
261
|
+
tileWidth = baseifd['tags'][tifftools.Tag.TileWidth.value]['data'][0]
|
262
|
+
tileHeight = baseifd['tags'][tifftools.Tag.TileLength.value]['data'][0]
|
263
|
+
else:
|
264
|
+
tileWidth = sizeX
|
265
|
+
tileHeight = baseifd['tags'][tifftools.Tag.RowsPerStrip.value]['data'][0]
|
266
|
+
|
257
267
|
for tag in {
|
258
268
|
tifftools.Tag.SamplesPerPixel.value,
|
259
269
|
tifftools.Tag.BitsPerSample.value,
|
@@ -298,7 +308,7 @@ class TiffFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
298
308
|
directories are the same size and format; all non-tiled directories are
|
299
309
|
treated as associated images.
|
300
310
|
"""
|
301
|
-
dir0 = self.getTiffDir(0)
|
311
|
+
dir0 = self.getTiffDir(0, mustBeTiled=None)
|
302
312
|
self.tileWidth = dir0.tileWidth
|
303
313
|
self.tileHeight = dir0.tileHeight
|
304
314
|
self.sizeX = dir0.imageWidth
|
@@ -312,21 +322,26 @@ class TiffFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
312
322
|
tifftools.constants.SampleFormat[sampleformat or 1].name,
|
313
323
|
bitspersample,
|
314
324
|
))
|
315
|
-
self._bandCount = dir0._tiffInfo.get('samplesperpixel')
|
325
|
+
self._bandCount = dir0._tiffInfo.get('samplesperpixel', 1)
|
316
326
|
info = _cached_read_tiff(self._largeImagePath)
|
317
327
|
self._info = info
|
318
328
|
frames = []
|
319
329
|
associated = [] # for now, a list of directories
|
320
|
-
|
330
|
+
used_subifd = False
|
321
331
|
for idx, ifd in enumerate(info['ifds']):
|
322
332
|
# if not tiles, add to associated images
|
323
333
|
if tifftools.Tag.tileWidth.value not in ifd['tags']:
|
324
|
-
associated.append(idx)
|
334
|
+
associated.append((idx, False))
|
325
335
|
continue
|
326
|
-
|
336
|
+
try:
|
337
|
+
level = self._levelFromIfd(ifd, info['ifds'][0])
|
338
|
+
except TileSourceError:
|
339
|
+
if idx and used_subifd:
|
340
|
+
associated.append((idx, True))
|
341
|
+
continue
|
342
|
+
raise
|
327
343
|
# if the same resolution as the main image, add a frame
|
328
344
|
if level == self.levels - 1:
|
329
|
-
curframe += 1
|
330
345
|
frames.append({'dirs': [None] * self.levels})
|
331
346
|
frames[-1]['dirs'][-1] = (idx, 0)
|
332
347
|
try:
|
@@ -359,15 +374,52 @@ class TiffFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
359
374
|
if len(subifds) != 1:
|
360
375
|
msg = 'When stored in subifds, each subifd should be a single ifd.'
|
361
376
|
raise TileSourceError(msg)
|
362
|
-
|
377
|
+
if (tifftools.Tag.StripOffsets.value not in subifds[0]['tags'] and
|
378
|
+
tifftools.Tag.TileOffsets.value not in subifds[0]['tags']):
|
379
|
+
msg = 'Subifd has no strip or tile offsets.'
|
380
|
+
raise TileSourceMalformedError(msg)
|
381
|
+
try:
|
382
|
+
level = self._levelFromIfd(subifds[0], info['ifds'][0])
|
383
|
+
except Exception:
|
384
|
+
break
|
363
385
|
if level < self.levels - 1 and frames[-1]['dirs'][level] is None:
|
364
386
|
frames[-1]['dirs'][level] = (idx, subidx + 1)
|
387
|
+
used_subifd = True
|
365
388
|
else:
|
366
389
|
msg = 'Tile layers are in a surprising order'
|
367
390
|
raise TileSourceError(msg)
|
391
|
+
# If we have a single untiled ifd that is "small", use it
|
392
|
+
if tifftools.Tag.tileWidth.value not in info['ifds'][0]['tags']:
|
393
|
+
if (
|
394
|
+
self.sizeX > self._maxUntiledImage or self.sizeY > self._maxUntiledImage or
|
395
|
+
(len(info['ifds']) != 1 or tifftools.Tag.SubIfd.value in ifd['tags']) or
|
396
|
+
(tifftools.Tag.ImageDescription.value in ifd['tags'] and
|
397
|
+
'ImageJ' in ifd['tags'][tifftools.Tag.ImageDescription.value]['data'])
|
398
|
+
):
|
399
|
+
msg = 'A tiled TIFF is required.'
|
400
|
+
raise ValidationTiffError(msg)
|
401
|
+
associated = []
|
402
|
+
level = self._levelFromIfd(ifd, info['ifds'][0])
|
403
|
+
frames.append({'dirs': [None] * self.levels})
|
404
|
+
frames[-1]['dirs'][-1] = (idx, 0)
|
405
|
+
try:
|
406
|
+
frameMetadata = json.loads(
|
407
|
+
ifd['tags'][tifftools.Tag.ImageDescription.value]['data'])
|
408
|
+
for key in {'channels', 'frame'}:
|
409
|
+
if key in frameMetadata:
|
410
|
+
frames[-1][key] = frameMetadata[key]
|
411
|
+
except Exception:
|
412
|
+
pass
|
413
|
+
if tifftools.Tag.ICCProfile.value in ifd['tags']:
|
414
|
+
if not hasattr(self, '_iccprofiles'):
|
415
|
+
self._iccprofiles = []
|
416
|
+
while len(self._iccprofiles) < len(frames) - 1:
|
417
|
+
self._iccprofiles.append(None)
|
418
|
+
self._iccprofiles.append(ifd['tags'][
|
419
|
+
tifftools.Tag.ICCProfile.value]['data'])
|
368
420
|
self._associatedImages = {}
|
369
|
-
for dirNum in associated:
|
370
|
-
self._addAssociatedImage(dirNum)
|
421
|
+
for dirNum, isTiled in associated:
|
422
|
+
self._addAssociatedImage(dirNum, isTiled)
|
371
423
|
self._frames = frames
|
372
424
|
self._tiffDirectories = [
|
373
425
|
self.getTiffDir(
|
@@ -449,7 +501,7 @@ class TiffFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
449
501
|
frame.setdefault('frame', {})
|
450
502
|
frame['frame']['IndexC'] = idx
|
451
503
|
|
452
|
-
def _addAssociatedImage(self, directoryNum, mustBeTiled=False, topImage=None):
|
504
|
+
def _addAssociatedImage(self, directoryNum, mustBeTiled=False, topImage=None, imageId=None):
|
453
505
|
"""
|
454
506
|
Check if the specified TIFF directory contains an image with a sensible
|
455
507
|
image description that can be used as an ID. If so, and if the image
|
@@ -460,6 +512,7 @@ class TiffFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
460
512
|
untiled images.
|
461
513
|
:param topImage: if specified, add image-embedded metadata to this
|
462
514
|
image.
|
515
|
+
:param imageId: if specified, use this as the image name.
|
463
516
|
"""
|
464
517
|
try:
|
465
518
|
associated = self.getTiffDir(directoryNum, mustBeTiled)
|
@@ -473,6 +526,8 @@ class TiffFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
473
526
|
id = 'dir%d' % directoryNum
|
474
527
|
if not len(self._associatedImages):
|
475
528
|
id = 'macro'
|
529
|
+
if imageId:
|
530
|
+
id = imageId
|
476
531
|
if not id and not mustBeTiled:
|
477
532
|
id = {1: 'label', 9: 'macro'}.get(associated._tiffInfo.get('subfiletype'))
|
478
533
|
if not isinstance(id, str):
|
@@ -484,7 +539,7 @@ class TiffFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
484
539
|
associated._pixelInfo['width'] <= self._maxAssociatedImageSize and
|
485
540
|
associated._pixelInfo['height'] <= self._maxAssociatedImageSize and
|
486
541
|
id not in self._associatedImages):
|
487
|
-
image = associated.
|
542
|
+
image = associated.read_image()
|
488
543
|
# Optrascan scanners store xml image descriptions in a "tiled
|
489
544
|
# image". Check if this is the case, and, if so, parse such
|
490
545
|
# data
|
@@ -631,20 +686,16 @@ class TiffFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
631
686
|
else:
|
632
687
|
dir = self._tiffDirectories[z]
|
633
688
|
try:
|
634
|
-
allowStyle = True
|
635
689
|
if dir is None:
|
636
690
|
try:
|
637
691
|
if not kwargs.get('inSparseFallback'):
|
638
|
-
tile = self._getTileFromEmptyLevel(x, y, z, **kwargs)
|
692
|
+
tile, format = self._getTileFromEmptyLevel(x, y, z, **kwargs)
|
639
693
|
else:
|
640
694
|
raise IOTiffError('Missing z level %d' % z)
|
641
695
|
except Exception:
|
642
696
|
if sparseFallback:
|
643
697
|
raise IOTiffError('Missing z level %d' % z)
|
644
|
-
|
645
|
-
raise
|
646
|
-
allowStyle = False
|
647
|
-
format = TILE_FORMAT_PIL
|
698
|
+
raise
|
648
699
|
else:
|
649
700
|
tile = dir.getTile(x, y, asarray=numpyAllowed == 'always')
|
650
701
|
format = 'JPEG'
|
@@ -653,7 +704,7 @@ class TiffFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
653
704
|
if isinstance(tile, np.ndarray):
|
654
705
|
format = TILE_FORMAT_NUMPY
|
655
706
|
return self._outputTile(tile, format, x, y, z, pilImageAllowed,
|
656
|
-
numpyAllowed,
|
707
|
+
numpyAllowed, **kwargs)
|
657
708
|
except InvalidOperationTiffError as e:
|
658
709
|
raise TileSourceError(e.args[0])
|
659
710
|
except IOTiffError as e:
|
@@ -702,7 +753,7 @@ class TiffFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
702
753
|
else:
|
703
754
|
image = PIL.Image.new('RGBA', (self.tileWidth, self.tileHeight))
|
704
755
|
return self._outputTile(image, TILE_FORMAT_PIL, x, y, z, pilImageAllowed,
|
705
|
-
numpyAllowed,
|
756
|
+
numpyAllowed, **kwargs)
|
706
757
|
raise TileSourceError('Internal I/O failure: %s' % exception.args[0])
|
707
758
|
|
708
759
|
def _nonemptyLevelsList(self, frame=0):
|
@@ -727,7 +778,7 @@ class TiffFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
727
778
|
"""
|
728
779
|
imageList = set(self._associatedImages)
|
729
780
|
for td in self._tiffDirectories:
|
730
|
-
if td is not None:
|
781
|
+
if td is not None and td is not False:
|
731
782
|
imageList |= set(td._embeddedImages)
|
732
783
|
return sorted(imageList)
|
733
784
|
|
@@ -742,11 +793,11 @@ class TiffFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
742
793
|
# _associatedImages. There are some sample files where libtiff's
|
743
794
|
# read_image fails to read the _associatedImage properly because of
|
744
795
|
# separated jpeg information. For the samples we currently have,
|
745
|
-
# preferring the _embeddedImages is sufficient, but if find other
|
746
|
-
# with seemingly bad associated images, we may need to read them
|
747
|
-
# more complex process than read_image.
|
796
|
+
# preferring the _embeddedImages is sufficient, but if we find other
|
797
|
+
# files with seemingly bad associated images, we may need to read them
|
798
|
+
# with a more complex process than read_image.
|
748
799
|
for td in self._tiffDirectories:
|
749
|
-
if td is not None and imageKey in td._embeddedImages:
|
800
|
+
if td is not None and td is not False and imageKey in td._embeddedImages:
|
750
801
|
return PIL.Image.open(io.BytesIO(base64.b64decode(td._embeddedImages[imageKey])))
|
751
802
|
if imageKey in self._associatedImages:
|
752
803
|
return PIL.Image.fromarray(self._associatedImages[imageKey])
|
@@ -121,7 +121,12 @@ class TiledTiffDirectory:
|
|
121
121
|
self._tileLock = threading.RLock()
|
122
122
|
|
123
123
|
self._open(filePath, directoryNum, subDirectoryNum)
|
124
|
-
|
124
|
+
try:
|
125
|
+
self._loadMetadata()
|
126
|
+
except Exception:
|
127
|
+
self.logger.exception('Could not parse tiff metadata')
|
128
|
+
raise IOOpenTiffError(
|
129
|
+
'Could not open TIFF file: %s' % filePath)
|
125
130
|
self.logger.debug(
|
126
131
|
'TiffDirectory %d:%d Information %r',
|
127
132
|
directoryNum, subDirectoryNum or 0, self._tiffInfo)
|
@@ -207,8 +212,8 @@ class TiledTiffDirectory:
|
|
207
212
|
# the create_image.py script, such as flatten or colourspace. These
|
208
213
|
# should only be done if necessary, which would require the conversion
|
209
214
|
# job to check output and perform subsequent processing as needed.
|
210
|
-
if (not self._tiffInfo.get('samplesperpixel') or
|
211
|
-
self._tiffInfo.get('samplesperpixel') < 1):
|
215
|
+
if (not self._tiffInfo.get('samplesperpixel', 1) or
|
216
|
+
self._tiffInfo.get('samplesperpixel', 1) < 1):
|
212
217
|
msg = 'Only RGB and greyscale TIFF files are supported'
|
213
218
|
raise ValidationTiffError(msg)
|
214
219
|
|
@@ -442,12 +447,11 @@ class TiledTiffDirectory:
|
|
442
447
|
|
443
448
|
if tileByteCountsLibtiffType == libtiff_ctypes.TIFFDataType.TIFF_LONG8:
|
444
449
|
return ctypes.c_uint64
|
445
|
-
|
450
|
+
if tileByteCountsLibtiffType == \
|
446
451
|
libtiff_ctypes.TIFFDataType.TIFF_SHORT:
|
447
452
|
return ctypes.c_uint16
|
448
|
-
|
449
|
-
|
450
|
-
'Invalid type for TIFFTAG_TILEBYTECOUNTS: %s' % tileByteCountsLibtiffType)
|
453
|
+
raise IOTiffError(
|
454
|
+
'Invalid type for TIFFTAG_TILEBYTECOUNTS: %s' % tileByteCountsLibtiffType)
|
451
455
|
|
452
456
|
def _getJpegFrameSize(self, tileNum):
|
453
457
|
"""
|
@@ -524,10 +528,10 @@ class TiledTiffDirectory:
|
|
524
528
|
if bytesRead == -1:
|
525
529
|
msg = 'Failed to read raw tile'
|
526
530
|
raise IOTiffError(msg)
|
527
|
-
|
531
|
+
if bytesRead < rawTileSize:
|
528
532
|
msg = 'Buffer underflow when reading tile'
|
529
533
|
raise IOTiffError(msg)
|
530
|
-
|
534
|
+
if bytesRead > rawTileSize:
|
531
535
|
# It's unlikely that this will ever occur, but incomplete reads will
|
532
536
|
# be checked for by looking for the JPEG end marker
|
533
537
|
msg = 'Buffer overflow when reading tile'
|
@@ -607,7 +611,7 @@ class TiledTiffDirectory:
|
|
607
611
|
self._tiffInfo.get('bitspersample'),
|
608
612
|
self._tiffInfo.get('sampleformat') if self._tiffInfo.get(
|
609
613
|
'sampleformat') is not None else libtiff_ctypes.SAMPLEFORMAT_UINT)
|
610
|
-
image = np.empty((th, tw, self._tiffInfo
|
614
|
+
image = np.empty((th, tw, self._tiffInfo.get('samplesperpixel', 1)),
|
611
615
|
dtype=_ctypesFormattbl[format])
|
612
616
|
imageBuffer = image.ctypes.data_as(ctypes.POINTER(ctypes.c_char))
|
613
617
|
if self._tiffInfo.get('istiled'):
|
@@ -635,7 +639,7 @@ class TiledTiffDirectory:
|
|
635
639
|
raise IOTiffError(
|
636
640
|
'Read an unexpected number of bytes from an encoded tile' if readSize >= 0 else
|
637
641
|
'Failed to read from an encoded tile')
|
638
|
-
if (self._tiffInfo.get('samplesperpixel') == 3 and
|
642
|
+
if (self._tiffInfo.get('samplesperpixel', 1) == 3 and
|
639
643
|
self._tiffInfo.get('photometric') == libtiff_ctypes.PHOTOMETRIC_YCBCR):
|
640
644
|
if self._tiffInfo.get('bitspersample') == 16:
|
641
645
|
image = np.floor_divide(image, 256).astype(np.uint8)
|
@@ -783,11 +787,13 @@ class TiledTiffDirectory:
|
|
783
787
|
|
784
788
|
if (not self._tiffInfo.get('istiled') or
|
785
789
|
self._tiffInfo.get('compression') not in {
|
786
|
-
libtiff_ctypes.COMPRESSION_JPEG, 33003, 33005, 34712} or
|
790
|
+
libtiff_ctypes.COMPRESSION_JPEG, 33003, 33004, 33005, 34712} or
|
787
791
|
self._tiffInfo.get('bitspersample') != 8 or
|
788
792
|
self._tiffInfo.get('sampleformat') not in {
|
789
793
|
None, libtiff_ctypes.SAMPLEFORMAT_UINT} or
|
790
|
-
(asarray and self._tiffInfo.get('compression') not in {
|
794
|
+
(asarray and self._tiffInfo.get('compression') not in {
|
795
|
+
33003, 33004, 33005, 34712,
|
796
|
+
} and (
|
791
797
|
self._tiffInfo.get('compression') != libtiff_ctypes.COMPRESSION_JPEG or
|
792
798
|
self._tiffInfo.get('photometric') != libtiff_ctypes.PHOTOMETRIC_YCBCR))):
|
793
799
|
return self._getUncompressedTile(tileNum)
|
@@ -803,9 +809,18 @@ class TiledTiffDirectory:
|
|
803
809
|
# Write JPEG End Of Image marker
|
804
810
|
imageBuffer.write(b'\xff\xd9')
|
805
811
|
return imageBuffer.getvalue()
|
806
|
-
# Get the whole frame, which is in a JPEG or JPEG 2000 format
|
812
|
+
# Get the whole frame, which is in a JPEG or JPEG 2000 format
|
813
|
+
frame = self._getJpegFrame(tileNum, True)
|
814
|
+
# For JP2K, see if we can convert it faster than PIL
|
815
|
+
if self._tiffInfo.get('compression') in {33003, 33004, 33005, 34712}:
|
816
|
+
try:
|
817
|
+
import openjpeg
|
818
|
+
|
819
|
+
return openjpeg.decode(frame)
|
820
|
+
except Exception:
|
821
|
+
pass
|
807
822
|
# convert it to a PIL image
|
808
|
-
imageBuffer.write(
|
823
|
+
imageBuffer.write(frame)
|
809
824
|
image = PIL.Image.open(imageBuffer)
|
810
825
|
# Converting the image mode ensures that it gets loaded once and is in
|
811
826
|
# a form we expect. If this isn't done, then PIL can load the image
|
@@ -822,7 +837,7 @@ class TiledTiffDirectory:
|
|
822
837
|
self._embeddedImages = {}
|
823
838
|
|
824
839
|
if not meta:
|
825
|
-
return
|
840
|
+
return None
|
826
841
|
if not isinstance(meta, str):
|
827
842
|
meta = meta.decode(errors='ignore')
|
828
843
|
try:
|
@@ -844,7 +859,7 @@ class TiledTiffDirectory:
|
|
844
859
|
meta.split('|MPP = ', 1)[1].split('|')[0].strip()) * 0.001
|
845
860
|
except Exception:
|
846
861
|
pass
|
847
|
-
return
|
862
|
+
return None
|
848
863
|
try:
|
849
864
|
image = xml.find(
|
850
865
|
".//DataObject[@ObjectType='DPScannedImage']")
|
@@ -880,3 +895,26 @@ class TiledTiffDirectory:
|
|
880
895
|
except Exception:
|
881
896
|
pass
|
882
897
|
return True
|
898
|
+
|
899
|
+
def read_image(self):
|
900
|
+
"""
|
901
|
+
Use the underlying _tiffFile to read an image. But, if it is in a jp2k
|
902
|
+
encoding, read the raw data and convert it.
|
903
|
+
"""
|
904
|
+
if self._tiffInfo.get('compression') not in {33003, 33004, 33005, 34712}:
|
905
|
+
return self._tiffFile.read_image()
|
906
|
+
output = None
|
907
|
+
for yidx, y in enumerate(range(0, self.imageHeight, self.tileHeight)):
|
908
|
+
for xidx, x in enumerate(range(0, self.imageWidth, self.tileWidth)):
|
909
|
+
tile = self.getTile(xidx, yidx, asarray=True)
|
910
|
+
if len(tile.shape) == 2:
|
911
|
+
tile = tile[:, :, np.newaxis]
|
912
|
+
if output is None:
|
913
|
+
output = np.zeros(
|
914
|
+
(self.imageHeight, self.imageWidth, tile.shape[2]), dtype=tile.dtype)
|
915
|
+
if tile.shape[0] > self.imageHeight - y:
|
916
|
+
tile = tile[:self.imageHeight - y, :, :]
|
917
|
+
if tile.shape[1] > self.imageWidth - x:
|
918
|
+
tile = tile[:, :self.imageWidth - x, :]
|
919
|
+
output[y:y + tile.shape[0], x:x + tile.shape[1], :] = tile
|
920
|
+
return output
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: large-image-source-tiff
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.30.7.dev10
|
4
4
|
Summary: A TIFF tilesource for large_image.
|
5
5
|
Home-page: https://github.com/girder/large_image
|
6
6
|
Author: Kitware, Inc.
|
@@ -15,9 +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
|
+
Classifier: Programming Language :: Python :: 3.13
|
18
19
|
Requires-Python: >=3.8
|
19
|
-
|
20
|
+
Description-Content-Type: text/x-rst
|
20
21
|
License-File: LICENSE
|
22
|
+
Requires-Dist: large-image>=1.30.7.dev10
|
23
|
+
Requires-Dist: pylibtiff
|
24
|
+
Requires-Dist: tifftools>=1.2.0
|
25
|
+
Provides-Extra: all
|
26
|
+
Requires-Dist: pylibjpeg-openjpeg; extra == "all"
|
27
|
+
Provides-Extra: girder
|
28
|
+
Requires-Dist: girder-large-image>=1.30.7.dev10; extra == "girder"
|
21
29
|
|
22
30
|
A TIFF tilesource for large_image.
|
23
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
|
f'large-image{limit_version}',
|
@@ -56,6 +57,9 @@ setup(
|
|
56
57
|
'tifftools>=1.2.0',
|
57
58
|
],
|
58
59
|
extras_require={
|
60
|
+
'all': [
|
61
|
+
'pylibjpeg-openjpeg',
|
62
|
+
],
|
59
63
|
'girder': f'girder-large-image{limit_version}',
|
60
64
|
},
|
61
65
|
keywords='large_image, tile source',
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{large-image-source-tiff-1.27.5.dev65 → large_image_source_tiff-1.30.7.dev10}/pyproject.toml
RENAMED
File without changes
|
File without changes
|