large-image-source-tiff 1.27.5.dev4__py3-none-any.whl → 1.30.7.dev10__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- large_image_source_tiff/__init__.py +83 -31
- large_image_source_tiff/tiff_reader.py +55 -17
- {large_image_source_tiff-1.27.5.dev4.dist-info → large_image_source_tiff-1.30.7.dev10.dist-info}/METADATA +21 -5
- large_image_source_tiff-1.30.7.dev10.dist-info/RECORD +10 -0
- {large_image_source_tiff-1.27.5.dev4.dist-info → large_image_source_tiff-1.30.7.dev10.dist-info}/WHEEL +1 -1
- large_image_source_tiff-1.27.5.dev4.dist-info/RECORD +0 -10
- {large_image_source_tiff-1.27.5.dev4.dist-info → large_image_source_tiff-1.30.7.dev10.dist-info}/LICENSE +0 -0
- {large_image_source_tiff-1.27.5.dev4.dist-info → large_image_source_tiff-1.30.7.dev10.dist-info}/entry_points.txt +0 -0
- {large_image_source_tiff-1.27.5.dev4.dist-info → large_image_source_tiff-1.30.7.dev10.dist-info}/top_level.txt +0 -0
@@ -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
|
@@ -63,6 +65,7 @@ class TiffFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
63
65
|
'ptif': SourcePriority.PREFERRED,
|
64
66
|
'ptiff': SourcePriority.PREFERRED,
|
65
67
|
'qptiff': SourcePriority.PREFERRED,
|
68
|
+
'svs': SourcePriority.MEDIUM,
|
66
69
|
}
|
67
70
|
mimeTypes = {
|
68
71
|
None: SourcePriority.FALLBACK,
|
@@ -72,6 +75,7 @@ class TiffFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
72
75
|
}
|
73
76
|
|
74
77
|
_maxAssociatedImageSize = 8192
|
78
|
+
_maxUntiledImage = 4096
|
75
79
|
|
76
80
|
def __init__(self, path, **kwargs): # noqa
|
77
81
|
"""
|
@@ -84,18 +88,20 @@ class TiffFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
84
88
|
|
85
89
|
self._largeImagePath = str(self._getLargeImagePath())
|
86
90
|
|
91
|
+
lastException = None
|
87
92
|
try:
|
88
93
|
self._initWithTiffTools()
|
89
94
|
return
|
95
|
+
except TileSourceMalformedError:
|
96
|
+
raise
|
90
97
|
except Exception as exc:
|
91
98
|
self.logger.debug('Cannot read with tifftools route; %r', exc)
|
99
|
+
lastException = exc
|
92
100
|
|
93
101
|
alldir = []
|
94
102
|
try:
|
95
103
|
if hasattr(self, '_info'):
|
96
104
|
alldir = self._scanDirectories()
|
97
|
-
else:
|
98
|
-
lastException = 'Could not parse file with tifftools'
|
99
105
|
except IOOpenTiffError:
|
100
106
|
msg = 'File cannot be opened via tiff source.'
|
101
107
|
raise TileSourceError(msg)
|
@@ -135,7 +141,7 @@ class TiffFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
135
141
|
continue
|
136
142
|
# If a layer is a multiple of the tile size, the number of tiles
|
137
143
|
# should be a power of two rounded up from the primary.
|
138
|
-
if
|
144
|
+
if not (td.imageWidth % td.tileWidth) and not (td.imageHeight % td.tileHeight):
|
139
145
|
htw = highest.imageWidth // td.tileWidth
|
140
146
|
hth = highest.imageHeight // td.tileHeight
|
141
147
|
ttw = td.imageWidth // td.tileWidth
|
@@ -156,7 +162,7 @@ class TiffFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
156
162
|
tifftools.constants.SampleFormat[sampleformat or 1].name,
|
157
163
|
bitspersample,
|
158
164
|
))
|
159
|
-
self._bandCount = highest._tiffInfo.get('samplesperpixel')
|
165
|
+
self._bandCount = highest._tiffInfo.get('samplesperpixel', 1)
|
160
166
|
# Sort the directories so that the highest resolution is the last one;
|
161
167
|
# if a level is missing, put a None value in its place.
|
162
168
|
self._tiffDirectories = [directories.get(key) for key in
|
@@ -251,8 +257,13 @@ class TiffFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
251
257
|
"""
|
252
258
|
sizeX = ifd['tags'][tifftools.Tag.ImageWidth.value]['data'][0]
|
253
259
|
sizeY = ifd['tags'][tifftools.Tag.ImageLength.value]['data'][0]
|
254
|
-
|
255
|
-
|
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
|
+
|
256
267
|
for tag in {
|
257
268
|
tifftools.Tag.SamplesPerPixel.value,
|
258
269
|
tifftools.Tag.BitsPerSample.value,
|
@@ -297,7 +308,7 @@ class TiffFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
297
308
|
directories are the same size and format; all non-tiled directories are
|
298
309
|
treated as associated images.
|
299
310
|
"""
|
300
|
-
dir0 = self.getTiffDir(0)
|
311
|
+
dir0 = self.getTiffDir(0, mustBeTiled=None)
|
301
312
|
self.tileWidth = dir0.tileWidth
|
302
313
|
self.tileHeight = dir0.tileHeight
|
303
314
|
self.sizeX = dir0.imageWidth
|
@@ -311,21 +322,26 @@ class TiffFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
311
322
|
tifftools.constants.SampleFormat[sampleformat or 1].name,
|
312
323
|
bitspersample,
|
313
324
|
))
|
314
|
-
self._bandCount = dir0._tiffInfo.get('samplesperpixel')
|
325
|
+
self._bandCount = dir0._tiffInfo.get('samplesperpixel', 1)
|
315
326
|
info = _cached_read_tiff(self._largeImagePath)
|
316
327
|
self._info = info
|
317
328
|
frames = []
|
318
329
|
associated = [] # for now, a list of directories
|
319
|
-
|
330
|
+
used_subifd = False
|
320
331
|
for idx, ifd in enumerate(info['ifds']):
|
321
332
|
# if not tiles, add to associated images
|
322
333
|
if tifftools.Tag.tileWidth.value not in ifd['tags']:
|
323
|
-
associated.append(idx)
|
334
|
+
associated.append((idx, False))
|
324
335
|
continue
|
325
|
-
|
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
|
326
343
|
# if the same resolution as the main image, add a frame
|
327
344
|
if level == self.levels - 1:
|
328
|
-
curframe += 1
|
329
345
|
frames.append({'dirs': [None] * self.levels})
|
330
346
|
frames[-1]['dirs'][-1] = (idx, 0)
|
331
347
|
try:
|
@@ -358,15 +374,52 @@ class TiffFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
358
374
|
if len(subifds) != 1:
|
359
375
|
msg = 'When stored in subifds, each subifd should be a single ifd.'
|
360
376
|
raise TileSourceError(msg)
|
361
|
-
|
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
|
362
385
|
if level < self.levels - 1 and frames[-1]['dirs'][level] is None:
|
363
386
|
frames[-1]['dirs'][level] = (idx, subidx + 1)
|
387
|
+
used_subifd = True
|
364
388
|
else:
|
365
389
|
msg = 'Tile layers are in a surprising order'
|
366
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'])
|
367
420
|
self._associatedImages = {}
|
368
|
-
for dirNum in associated:
|
369
|
-
self._addAssociatedImage(dirNum)
|
421
|
+
for dirNum, isTiled in associated:
|
422
|
+
self._addAssociatedImage(dirNum, isTiled)
|
370
423
|
self._frames = frames
|
371
424
|
self._tiffDirectories = [
|
372
425
|
self.getTiffDir(
|
@@ -448,7 +501,7 @@ class TiffFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
448
501
|
frame.setdefault('frame', {})
|
449
502
|
frame['frame']['IndexC'] = idx
|
450
503
|
|
451
|
-
def _addAssociatedImage(self, directoryNum, mustBeTiled=False, topImage=None):
|
504
|
+
def _addAssociatedImage(self, directoryNum, mustBeTiled=False, topImage=None, imageId=None):
|
452
505
|
"""
|
453
506
|
Check if the specified TIFF directory contains an image with a sensible
|
454
507
|
image description that can be used as an ID. If so, and if the image
|
@@ -459,6 +512,7 @@ class TiffFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
459
512
|
untiled images.
|
460
513
|
:param topImage: if specified, add image-embedded metadata to this
|
461
514
|
image.
|
515
|
+
:param imageId: if specified, use this as the image name.
|
462
516
|
"""
|
463
517
|
try:
|
464
518
|
associated = self.getTiffDir(directoryNum, mustBeTiled)
|
@@ -472,6 +526,8 @@ class TiffFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
472
526
|
id = 'dir%d' % directoryNum
|
473
527
|
if not len(self._associatedImages):
|
474
528
|
id = 'macro'
|
529
|
+
if imageId:
|
530
|
+
id = imageId
|
475
531
|
if not id and not mustBeTiled:
|
476
532
|
id = {1: 'label', 9: 'macro'}.get(associated._tiffInfo.get('subfiletype'))
|
477
533
|
if not isinstance(id, str):
|
@@ -483,7 +539,7 @@ class TiffFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
483
539
|
associated._pixelInfo['width'] <= self._maxAssociatedImageSize and
|
484
540
|
associated._pixelInfo['height'] <= self._maxAssociatedImageSize and
|
485
541
|
id not in self._associatedImages):
|
486
|
-
image = associated.
|
542
|
+
image = associated.read_image()
|
487
543
|
# Optrascan scanners store xml image descriptions in a "tiled
|
488
544
|
# image". Check if this is the case, and, if so, parse such
|
489
545
|
# data
|
@@ -630,20 +686,16 @@ class TiffFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
630
686
|
else:
|
631
687
|
dir = self._tiffDirectories[z]
|
632
688
|
try:
|
633
|
-
allowStyle = True
|
634
689
|
if dir is None:
|
635
690
|
try:
|
636
691
|
if not kwargs.get('inSparseFallback'):
|
637
|
-
tile = self._getTileFromEmptyLevel(x, y, z, **kwargs)
|
692
|
+
tile, format = self._getTileFromEmptyLevel(x, y, z, **kwargs)
|
638
693
|
else:
|
639
694
|
raise IOTiffError('Missing z level %d' % z)
|
640
695
|
except Exception:
|
641
696
|
if sparseFallback:
|
642
697
|
raise IOTiffError('Missing z level %d' % z)
|
643
|
-
|
644
|
-
raise
|
645
|
-
allowStyle = False
|
646
|
-
format = TILE_FORMAT_PIL
|
698
|
+
raise
|
647
699
|
else:
|
648
700
|
tile = dir.getTile(x, y, asarray=numpyAllowed == 'always')
|
649
701
|
format = 'JPEG'
|
@@ -652,7 +704,7 @@ class TiffFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
652
704
|
if isinstance(tile, np.ndarray):
|
653
705
|
format = TILE_FORMAT_NUMPY
|
654
706
|
return self._outputTile(tile, format, x, y, z, pilImageAllowed,
|
655
|
-
numpyAllowed,
|
707
|
+
numpyAllowed, **kwargs)
|
656
708
|
except InvalidOperationTiffError as e:
|
657
709
|
raise TileSourceError(e.args[0])
|
658
710
|
except IOTiffError as e:
|
@@ -701,7 +753,7 @@ class TiffFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
701
753
|
else:
|
702
754
|
image = PIL.Image.new('RGBA', (self.tileWidth, self.tileHeight))
|
703
755
|
return self._outputTile(image, TILE_FORMAT_PIL, x, y, z, pilImageAllowed,
|
704
|
-
numpyAllowed,
|
756
|
+
numpyAllowed, **kwargs)
|
705
757
|
raise TileSourceError('Internal I/O failure: %s' % exception.args[0])
|
706
758
|
|
707
759
|
def _nonemptyLevelsList(self, frame=0):
|
@@ -726,7 +778,7 @@ class TiffFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
726
778
|
"""
|
727
779
|
imageList = set(self._associatedImages)
|
728
780
|
for td in self._tiffDirectories:
|
729
|
-
if td is not None:
|
781
|
+
if td is not None and td is not False:
|
730
782
|
imageList |= set(td._embeddedImages)
|
731
783
|
return sorted(imageList)
|
732
784
|
|
@@ -741,11 +793,11 @@ class TiffFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
741
793
|
# _associatedImages. There are some sample files where libtiff's
|
742
794
|
# read_image fails to read the _associatedImage properly because of
|
743
795
|
# separated jpeg information. For the samples we currently have,
|
744
|
-
# preferring the _embeddedImages is sufficient, but if find other
|
745
|
-
# with seemingly bad associated images, we may need to read them
|
746
|
-
# 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.
|
747
799
|
for td in self._tiffDirectories:
|
748
|
-
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:
|
749
801
|
return PIL.Image.open(io.BytesIO(base64.b64decode(td._embeddedImages[imageKey])))
|
750
802
|
if imageKey in self._associatedImages:
|
751
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
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.2
|
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,13 +15,29 @@ 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
|
20
|
+
Description-Content-Type: text/x-rst
|
19
21
|
License-File: LICENSE
|
20
|
-
Requires-Dist: large-image
|
22
|
+
Requires-Dist: large-image>=1.30.7.dev10
|
21
23
|
Requires-Dist: pylibtiff
|
22
|
-
Requires-Dist: tifftools
|
24
|
+
Requires-Dist: tifftools>=1.2.0
|
25
|
+
Provides-Extra: all
|
26
|
+
Requires-Dist: pylibjpeg-openjpeg; extra == "all"
|
23
27
|
Provides-Extra: girder
|
24
|
-
Requires-Dist: girder-large-image
|
28
|
+
Requires-Dist: girder-large-image>=1.30.7.dev10; extra == "girder"
|
29
|
+
Dynamic: author
|
30
|
+
Dynamic: author-email
|
31
|
+
Dynamic: classifier
|
32
|
+
Dynamic: description
|
33
|
+
Dynamic: description-content-type
|
34
|
+
Dynamic: home-page
|
35
|
+
Dynamic: keywords
|
36
|
+
Dynamic: license
|
37
|
+
Dynamic: provides-extra
|
38
|
+
Dynamic: requires-dist
|
39
|
+
Dynamic: requires-python
|
40
|
+
Dynamic: summary
|
25
41
|
|
26
42
|
A TIFF tilesource for large_image.
|
27
43
|
|
@@ -0,0 +1,10 @@
|
|
1
|
+
large_image_source_tiff/__init__.py,sha256=o-NkH3SI7XLmEdejSREFMw39kfTMu2LiV3GGHsmNrPQ,36588
|
2
|
+
large_image_source_tiff/exceptions.py,sha256=NgdwloaDCtbtUMe2BU2lXEU8IwQSYtaokIwGIFypCps,617
|
3
|
+
large_image_source_tiff/girder_source.py,sha256=Dp2e3O4VTANYXZI_eybgzs5BcyuMcw2-MAzCUJ8zzPg,1031
|
4
|
+
large_image_source_tiff/tiff_reader.py,sha256=gkAhuB4-IRzNLlNr958HKE9JZpRJwVo4zRGMd4rsD2o,40338
|
5
|
+
large_image_source_tiff-1.30.7.dev10.dist-info/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
|
6
|
+
large_image_source_tiff-1.30.7.dev10.dist-info/METADATA,sha256=O5AbEZVfjpc5zu9f10kzVnxMz86Jqq-E18IFCQ_lvkQ,1444
|
7
|
+
large_image_source_tiff-1.30.7.dev10.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
8
|
+
large_image_source_tiff-1.30.7.dev10.dist-info/entry_points.txt,sha256=iZ43sIcj98SND7nDUC-_4qroBL6apyXN4iSbPXZ8LE4,166
|
9
|
+
large_image_source_tiff-1.30.7.dev10.dist-info/top_level.txt,sha256=QRx_D2oeiOOz_5FlBOAoDPF-E4Q-aFmerUWlaeP14B8,24
|
10
|
+
large_image_source_tiff-1.30.7.dev10.dist-info/RECORD,,
|
@@ -1,10 +0,0 @@
|
|
1
|
-
large_image_source_tiff/__init__.py,sha256=0XCpYfa6BDT4m22xtLYO6qlxJ7vCFrWopWQQQo37FGQ,33986
|
2
|
-
large_image_source_tiff/exceptions.py,sha256=NgdwloaDCtbtUMe2BU2lXEU8IwQSYtaokIwGIFypCps,617
|
3
|
-
large_image_source_tiff/girder_source.py,sha256=Dp2e3O4VTANYXZI_eybgzs5BcyuMcw2-MAzCUJ8zzPg,1031
|
4
|
-
large_image_source_tiff/tiff_reader.py,sha256=xZGz9P97LHfL5kmxVCL1ek0ry259LNp39yGls6tnpnM,38634
|
5
|
-
large_image_source_tiff-1.27.5.dev4.dist-info/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
|
6
|
-
large_image_source_tiff-1.27.5.dev4.dist-info/METADATA,sha256=U6sw1488a3V479AF1sU3eE4a4inaMwhX57NtsUJwG6c,1031
|
7
|
-
large_image_source_tiff-1.27.5.dev4.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
8
|
-
large_image_source_tiff-1.27.5.dev4.dist-info/entry_points.txt,sha256=iZ43sIcj98SND7nDUC-_4qroBL6apyXN4iSbPXZ8LE4,166
|
9
|
-
large_image_source_tiff-1.27.5.dev4.dist-info/top_level.txt,sha256=QRx_D2oeiOOz_5FlBOAoDPF-E4Q-aFmerUWlaeP14B8,24
|
10
|
-
large_image_source_tiff-1.27.5.dev4.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|