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.
Files changed (17) hide show
  1. {large-image-source-tiff-1.27.5.dev65 → large_image_source_tiff-1.30.7.dev10}/PKG-INFO +10 -2
  2. {large-image-source-tiff-1.27.5.dev65 → large_image_source_tiff-1.30.7.dev10}/README.rst +20 -40
  3. {large-image-source-tiff-1.27.5.dev65 → large_image_source_tiff-1.30.7.dev10}/large_image_source_tiff/__init__.py +82 -31
  4. {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
  5. {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
  6. large_image_source_tiff-1.30.7.dev10/large_image_source_tiff.egg-info/requires.txt +9 -0
  7. {large-image-source-tiff-1.27.5.dev65 → large_image_source_tiff-1.30.7.dev10}/setup.py +6 -2
  8. large-image-source-tiff-1.27.5.dev65/large_image_source_tiff.egg-info/requires.txt +0 -6
  9. {large-image-source-tiff-1.27.5.dev65 → large_image_source_tiff-1.30.7.dev10}/LICENSE +0 -0
  10. {large-image-source-tiff-1.27.5.dev65 → large_image_source_tiff-1.30.7.dev10}/large_image_source_tiff/exceptions.py +0 -0
  11. {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
  12. {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
  13. {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
  14. {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
  15. {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
  16. {large-image-source-tiff-1.27.5.dev65 → large_image_source_tiff-1.30.7.dev10}/pyproject.toml +0 -0
  17. {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.27.5.dev65
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
- Provides-Extra: girder
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
- Install all tile sources and all Girder plugins on linux::
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. Some of the source modules are available on conda-forge. You can install the following::
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 images.
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 some multi-frame OMETiff files.
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
- - As a Girder plugin:
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
- - ``girder-large-image``: Large Image as a Girder_ 3.x plugin.
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-annotation``: Annotations for large images as a Girder_ 3.x plugin.
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://github.com/girder/girder
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, TileSourceFileNotFoundError
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 (not (td.imageWidth % td.tileWidth) and not (td.imageHeight % td.tileHeight)):
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
- tileWidth = baseifd['tags'][tifftools.Tag.TileWidth.value]['data'][0]
256
- tileHeight = baseifd['tags'][tifftools.Tag.TileLength.value]['data'][0]
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
- curframe = -1
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
- level = self._levelFromIfd(ifd, info['ifds'][0])
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
- level = self._levelFromIfd(subifds[0], info['ifds'][0])
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._tiffFile.read_image()
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
- else:
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, applyStyle=allowStyle, **kwargs)
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, applyStyle=False, **kwargs)
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 files
746
- # with seemingly bad associated images, we may need to read them with a
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
- self._loadMetadata()
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
- elif tileByteCountsLibtiffType == \
450
+ if tileByteCountsLibtiffType == \
446
451
  libtiff_ctypes.TIFFDataType.TIFF_SHORT:
447
452
  return ctypes.c_uint16
448
- else:
449
- raise IOTiffError(
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
- elif bytesRead < rawTileSize:
531
+ if bytesRead < rawTileSize:
528
532
  msg = 'Buffer underflow when reading tile'
529
533
  raise IOTiffError(msg)
530
- elif bytesRead > rawTileSize:
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['samplesperpixel']),
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 {33003, 33005, 34712} and (
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, and
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(self._getJpegFrame(tileNum, True))
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.27.5.dev65
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
- Provides-Extra: girder
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
 
@@ -0,0 +1,9 @@
1
+ large-image>=1.30.7.dev10
2
+ pylibtiff
3
+ tifftools>=1.2.0
4
+
5
+ [all]
6
+ pylibjpeg-openjpeg
7
+
8
+ [girder]
9
+ girder-large-image>=1.30.7.dev10
@@ -19,8 +19,7 @@ def prerelease_local_scheme(version):
19
19
 
20
20
  if os.getenv('CIRCLE_BRANCH') in ('master', ):
21
21
  return ''
22
- else:
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',
@@ -1,6 +0,0 @@
1
- large-image>=1.27.5.dev65
2
- pylibtiff
3
- tifftools>=1.2.0
4
-
5
- [girder]
6
- girder-large-image>=1.27.5.dev65