large-image-source-tiff 1.27.5.dev6__py3-none-any.whl → 1.30.7.dev10__py3-none-any.whl
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_tiff/__init__.py +83 -31
- large_image_source_tiff/tiff_reader.py +55 -17
- {large_image_source_tiff-1.27.5.dev6.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.dev6.dist-info → large_image_source_tiff-1.30.7.dev10.dist-info}/WHEEL +1 -1
- large_image_source_tiff-1.27.5.dev6.dist-info/RECORD +0 -10
- {large_image_source_tiff-1.27.5.dev6.dist-info → large_image_source_tiff-1.30.7.dev10.dist-info}/LICENSE +0 -0
- {large_image_source_tiff-1.27.5.dev6.dist-info → large_image_source_tiff-1.30.7.dev10.dist-info}/entry_points.txt +0 -0
- {large_image_source_tiff-1.27.5.dev6.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.dev6.dist-info/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
|
6
|
-
large_image_source_tiff-1.27.5.dev6.dist-info/METADATA,sha256=R2M0aEGAX6nMGr7yciHUYN2OJNqzK4r_-Uh_kylwrb4,1031
|
7
|
-
large_image_source_tiff-1.27.5.dev6.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
8
|
-
large_image_source_tiff-1.27.5.dev6.dist-info/entry_points.txt,sha256=iZ43sIcj98SND7nDUC-_4qroBL6apyXN4iSbPXZ8LE4,166
|
9
|
-
large_image_source_tiff-1.27.5.dev6.dist-info/top_level.txt,sha256=QRx_D2oeiOOz_5FlBOAoDPF-E4Q-aFmerUWlaeP14B8,24
|
10
|
-
large_image_source_tiff-1.27.5.dev6.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|