large-image-source-tifffile 1.27.5.dev6__tar.gz → 1.30.7.dev12__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (15) hide show
  1. {large-image-source-tifffile-1.27.5.dev6 → large_image_source_tifffile-1.30.7.dev12}/PKG-INFO +9 -2
  2. {large-image-source-tifffile-1.27.5.dev6 → large_image_source_tifffile-1.30.7.dev12}/README.rst +20 -40
  3. {large-image-source-tifffile-1.27.5.dev6 → large_image_source_tifffile-1.30.7.dev12}/large_image_source_tifffile/__init__.py +188 -29
  4. {large-image-source-tifffile-1.27.5.dev6 → large_image_source_tifffile-1.30.7.dev12}/large_image_source_tifffile.egg-info/PKG-INFO +9 -2
  5. large_image_source_tifffile-1.30.7.dev12/large_image_source_tifffile.egg-info/requires.txt +7 -0
  6. {large-image-source-tifffile-1.27.5.dev6 → large_image_source_tifffile-1.30.7.dev12}/setup.py +3 -2
  7. large-image-source-tifffile-1.27.5.dev6/large_image_source_tifffile.egg-info/requires.txt +0 -7
  8. {large-image-source-tifffile-1.27.5.dev6 → large_image_source_tifffile-1.30.7.dev12}/LICENSE +0 -0
  9. {large-image-source-tifffile-1.27.5.dev6 → large_image_source_tifffile-1.30.7.dev12}/large_image_source_tifffile/girder_source.py +0 -0
  10. {large-image-source-tifffile-1.27.5.dev6 → large_image_source_tifffile-1.30.7.dev12}/large_image_source_tifffile.egg-info/SOURCES.txt +0 -0
  11. {large-image-source-tifffile-1.27.5.dev6 → large_image_source_tifffile-1.30.7.dev12}/large_image_source_tifffile.egg-info/dependency_links.txt +0 -0
  12. {large-image-source-tifffile-1.27.5.dev6 → large_image_source_tifffile-1.30.7.dev12}/large_image_source_tifffile.egg-info/entry_points.txt +0 -0
  13. {large-image-source-tifffile-1.27.5.dev6 → large_image_source_tifffile-1.30.7.dev12}/large_image_source_tifffile.egg-info/top_level.txt +0 -0
  14. {large-image-source-tifffile-1.27.5.dev6 → large_image_source_tifffile-1.30.7.dev12}/pyproject.toml +0 -0
  15. {large-image-source-tifffile-1.27.5.dev6 → large_image_source_tifffile-1.30.7.dev12}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: large-image-source-tifffile
3
- Version: 1.27.5.dev6
3
+ Version: 1.30.7.dev12
4
4
  Summary: A tifffile tilesource for large_image.
5
5
  Home-page: https://github.com/girder/large_image
6
6
  Author: Kitware, Inc.
@@ -15,9 +15,16 @@ 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.dev12
23
+ Requires-Dist: dask[array]
24
+ Requires-Dist: tifffile[all]
25
+ Requires-Dist: zarr
26
+ Provides-Extra: girder
27
+ Requires-Dist: girder-large-image>=1.30.7.dev12; extra == "girder"
21
28
 
22
29
  A tifffile tilesource for large_image.
23
30
 
@@ -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/
@@ -5,9 +5,9 @@ import os
5
5
  import threading
6
6
  from importlib.metadata import PackageNotFoundError
7
7
  from importlib.metadata import version as _importlib_version
8
+ from pathlib import Path
8
9
 
9
10
  import numpy as np
10
- import zarr
11
11
 
12
12
  import large_image
13
13
  from large_image.cache_util import LruCacheMetaclass, methodcache
@@ -16,6 +16,7 @@ from large_image.exceptions import TileSourceError, TileSourceFileNotFoundError
16
16
  from large_image.tilesource import FileTileSource
17
17
 
18
18
  tifffile = None
19
+ zarr = None
19
20
 
20
21
  try:
21
22
  __version__ = _importlib_version(__name__)
@@ -37,6 +38,7 @@ def _lazyImport():
37
38
  module initialization because it is slow.
38
39
  """
39
40
  global tifffile
41
+ global zarr
40
42
 
41
43
  if tifffile is None:
42
44
  try:
@@ -55,6 +57,8 @@ def _lazyImport():
55
57
  logging.getLogger('tifffile.tifffile').addHandler(checkForMissingDataHandler())
56
58
  logging.getLogger('tifffile').setLevel(logging.WARNING)
57
59
  logging.getLogger('tifffile').addHandler(checkForMissingDataHandler())
60
+ if zarr is None:
61
+ import zarr
58
62
 
59
63
 
60
64
  def et_findall(tag, text):
@@ -80,6 +84,7 @@ class TifffileFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
80
84
  'scn': SourcePriority.PREFERRED,
81
85
  'tif': SourcePriority.LOW,
82
86
  'tiff': SourcePriority.LOW,
87
+ 'ome': SourcePriority.HIGHER,
83
88
  }
84
89
  mimeTypes = {
85
90
  None: SourcePriority.FALLBACK,
@@ -109,6 +114,7 @@ class TifffileFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
109
114
  self._largeImagePath = str(self._getLargeImagePath())
110
115
 
111
116
  _lazyImport()
117
+ self.addKnownExtensions()
112
118
  try:
113
119
  self._tf = tifffile.TiffFile(self._largeImagePath)
114
120
  except Exception:
@@ -116,6 +122,7 @@ class TifffileFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
116
122
  raise TileSourceFileNotFoundError(self._largeImagePath) from None
117
123
  msg = 'File cannot be opened via tifffile.'
118
124
  raise TileSourceError(msg)
125
+ self._checkForOmeBinaryonly()
119
126
  maxseries, maxsamples = self._biggestSeries()
120
127
  self.tileWidth = self.tileHeight = self._tileSize
121
128
  s = self._tf.series[maxseries]
@@ -123,6 +130,9 @@ class TifffileFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
123
130
  if len(s.levels) == 1:
124
131
  self.tileWidth = self.tileHeight = self._singleTileSize
125
132
  page = s.pages[0]
133
+ if not hasattr(page, 'tags'):
134
+ msg = 'File will not be opened via tifffile.'
135
+ raise TileSourceError(msg)
126
136
  if ('TileWidth' in page.tags and
127
137
  self._minTileSize <= page.tags['TileWidth'].value <= self._maxTileSize):
128
138
  self.tileWidth = page.tags['TileWidth'].value
@@ -133,6 +143,10 @@ class TifffileFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
133
143
  self._iccprofiles = [page.tags['InterColorProfile'].value]
134
144
  self.sizeX = s.shape[s.axes.index('X')]
135
145
  self.sizeY = s.shape[s.axes.index('Y')]
146
+ while (self.tileWidth // 2 >= self.sizeX and self.tileHeight // 2 >= self.sizeY and
147
+ min(self.tileWidth, self.tileHeight) // 2 >= self._minTileSize):
148
+ self.tileWidth //= 2
149
+ self.tileHeight //= 2
136
150
  self._mm_x = self._mm_y = None
137
151
  try:
138
152
  unit = {2: 25.4, 3: 10}[page.tags['ResolutionUnit'].value.real]
@@ -166,6 +180,35 @@ class TifffileFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
166
180
  msg = 'File cannot be opened via tifffile: axes and shape do not match access pattern.'
167
181
  raise TileSourceError(msg)
168
182
 
183
+ def _checkForOmeBinaryonly(self):
184
+ from xml.etree import ElementTree as etree
185
+
186
+ omexml = getattr(self._tf, 'ome_metadata', None)
187
+ if not omexml:
188
+ return
189
+ try:
190
+ root = etree.fromstring(omexml)
191
+ except Exception:
192
+ return
193
+ metadatafile = None
194
+ for element in root:
195
+ if element.tag.endswith('BinaryOnly'):
196
+ metadatafile = element.attrib.get('MetadataFile', '')
197
+ if not metadatafile:
198
+ return
199
+ path = Path(self._largeImagePath).parent / metadatafile
200
+ if not path.is_file():
201
+ return
202
+ try:
203
+ newxml = path.open('r').read()
204
+ except Exception:
205
+ return
206
+ try:
207
+ root = etree.fromstring(newxml)
208
+ except Exception:
209
+ return
210
+ self._tf._omexml = newxml
211
+
169
212
  def _biggestSeries(self):
170
213
  """
171
214
  Find the series with the most pixels. Use all series that have the
@@ -179,7 +222,7 @@ class TifffileFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
179
222
  ex = 'no maximum series'
180
223
  try:
181
224
  for idx, s in enumerate(self._tf.series):
182
- samples = np.prod(s.shape)
225
+ samples = math.prod(s.shape)
183
226
  if samples > maxsamples and 'X' in s.axes and 'Y' in s.axes:
184
227
  maxseries = idx
185
228
  maxsamples = samples
@@ -228,7 +271,7 @@ class TifffileFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
228
271
  'sizeX': s.shape[s.axes.index('X')], 'sizeY': s.shape[s.axes.index('Y')]})
229
272
  self.sizeX = max(self.sizeX, s.shape[s.axes.index('X')])
230
273
  self.sizeY = max(self.sizeY, s.shape[s.axes.index('Y')])
231
- self._framecount = len(self._series) * np.prod(tuple(
274
+ self._framecount = len(self._series) * math.prod(tuple(
232
275
  1 if base.axes[sidx] in 'YXS' else v for sidx, v in enumerate(base.shape)))
233
276
  self._basis = {}
234
277
  basis = 1
@@ -255,14 +298,17 @@ class TifffileFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
255
298
  for p in self._tf.pages:
256
299
  if (p not in pagesInSeries and getattr(p, 'keyframe', None) is not None and
257
300
  p.hash not in hashes and not len(set(p.axes) - set('YXS'))):
258
- id = 'image_%s' % p.index
259
- entry = {'page': p.index}
260
- entry['width'] = p.shape[p.axes.index('X')]
261
- entry['height'] = p.shape[p.axes.index('Y')]
262
- if (id not in self._associatedImages and
263
- max(entry['width'], entry['height']) <= self._maxAssociatedImageSize and
264
- max(entry['width'], entry['height']) >= self._minAssociatedImageSize):
265
- self._associatedImages[id] = entry
301
+ try:
302
+ id = 'image_%s' % p.index
303
+ entry = {'page': p.index}
304
+ entry['width'] = p.shape[p.axes.index('X')]
305
+ entry['height'] = p.shape[p.axes.index('Y')]
306
+ if (id not in self._associatedImages and
307
+ max(entry['width'], entry['height']) <= self._maxAssociatedImageSize and
308
+ max(entry['width'], entry['height']) >= self._minAssociatedImageSize):
309
+ self._associatedImages[id] = entry
310
+ except Exception:
311
+ pass
266
312
  for sidx, s in enumerate(self._tf.series):
267
313
  if sidx not in self._series and not len(set(s.axes) - set('YXS')):
268
314
  id = 'series_%d' % sidx
@@ -285,6 +331,79 @@ class TifffileFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
285
331
  except Exception:
286
332
  pass
287
333
 
334
+ def _handle_indica(self):
335
+ import xml.etree.ElementTree
336
+
337
+ import large_image.tilesource.utilities
338
+
339
+ try:
340
+ root = xml.etree.ElementTree.fromstring(self._tf.pages[0].description)
341
+ self._xml = large_image.tilesource.utilities.etreeToDict(root)
342
+ self._channels = [c['name'] for c in
343
+ self._xml['indica']['image']['channels']['channel']]
344
+ if len(self._basis) == 1 and 'I' in self._basis:
345
+ self._basis['C'] = self._basis.pop('I')
346
+ self._associatedImages.clear()
347
+ except Exception:
348
+ pass
349
+
350
+ def _handle_ome(self):
351
+ """
352
+ For OME Tiff, if we didn't parse the mangification elsewhere, try to
353
+ parse it here.
354
+ """
355
+ import xml.etree.ElementTree
356
+
357
+ import large_image.tilesource.utilities
358
+
359
+ _omeUnitsToMeters = {
360
+ 'Ym': 1e24,
361
+ 'Zm': 1e21,
362
+ 'Em': 1e18,
363
+ 'Pm': 1e15,
364
+ 'Tm': 1e12,
365
+ 'Gm': 1e9,
366
+ 'Mm': 1e6,
367
+ 'km': 1e3,
368
+ 'hm': 1e2,
369
+ 'dam': 1e1,
370
+ 'm': 1,
371
+ 'dm': 1e-1,
372
+ 'cm': 1e-2,
373
+ 'mm': 1e-3,
374
+ '\u00b5m': 1e-6,
375
+ 'nm': 1e-9,
376
+ 'pm': 1e-12,
377
+ 'fm': 1e-15,
378
+ 'am': 1e-18,
379
+ 'zm': 1e-21,
380
+ 'ym': 1e-24,
381
+ '\u00c5': 1e-10,
382
+ }
383
+
384
+ try:
385
+ root = xml.etree.ElementTree.fromstring(self._tf.pages[0].description)
386
+ self._xml = large_image.tilesource.utilities.etreeToDict(root)
387
+ except Exception:
388
+ return
389
+ try:
390
+ try:
391
+ base = self._xml['OME']['Image'][0]['Pixels']
392
+ except Exception:
393
+ base = self._xml['OME']['Image']['Pixels']
394
+ if self._mm_x is None and 'PhysicalSizeX' in base:
395
+ self._mm_x = (
396
+ float(base['PhysicalSizeX']) * 1e3 *
397
+ _omeUnitsToMeters[base.get('PhysicalSizeXUnit', '\u00b5m')])
398
+ if self._mm_y is None and 'PhysicalSizeY' in base:
399
+ self._mm_y = (
400
+ float(base['PhysicalSizeY']) * 1e3 *
401
+ _omeUnitsToMeters[base.get('PhysicalSizeYUnit', '\u00b5m')])
402
+ self._mm_x = self._mm_x or self._mm_y
403
+ self._mm_y = self._mm_y or self._mm_x
404
+ except Exception:
405
+ pass
406
+
288
407
  def _handle_scn(self): # noqa
289
408
  """
290
409
  For SCN files, parse the xml and possibly adjust how associated images
@@ -350,6 +469,20 @@ class TifffileFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
350
469
  except Exception:
351
470
  pass
352
471
 
472
+ def _handle_internal_ndpi(self, intmeta):
473
+ try:
474
+ ndpi = intmeta.pop('65449')
475
+ intmeta['ndpi'] = {}
476
+ for line in ndpi.replace('\r', '\n').split('\n'):
477
+ if '=' in line:
478
+ key, value = line.split('=', 1)
479
+ key = key.strip()
480
+ value = value.strip()
481
+ if key and key not in intmeta['ndpi'] and value:
482
+ intmeta['ndpi'][key] = value
483
+ except Exception:
484
+ pass
485
+
353
486
  def getNativeMagnification(self):
354
487
  """
355
488
  Get the magnification at a particular level.
@@ -419,6 +552,10 @@ class TifffileFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
419
552
  json.dumps(result[key][subkey])
420
553
  except Exception:
421
554
  del result[key][subkey]
555
+ for key in dir(self._tf):
556
+ if (key.startswith('is_') and hasattr(self, '_handle_internal_' + key[3:]) and
557
+ getattr(self._tf, key)):
558
+ getattr(self, '_handle_internal_' + key[3:])(result)
422
559
  if hasattr(self, '_xml') and 'xml' not in result:
423
560
  result.pop('ImageDescription', None)
424
561
  result['xml'] = self._xml
@@ -473,19 +610,12 @@ class TifffileFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
473
610
  sidx = frame // self._basis['P'][0]
474
611
  else:
475
612
  sidx = 0
476
- series = self._tf.series[self._series[sidx]]
477
613
  nonempty = [None] * self.levels
478
614
  nonempty[self.levels - 1] = True
615
+ series = self._tf.series[self._series[sidx]]
616
+ za, hasgbs = self._getZarrArray(series, sidx)
479
617
  xidx = series.axes.index('X')
480
618
  yidx = series.axes.index('Y')
481
- with self._zarrlock:
482
- if sidx not in self._zarrcache:
483
- if len(self._zarrcache) > 10:
484
- self._zarrcache = {}
485
- za = zarr.open(series.aszarr(), mode='r')
486
- hasgbs = hasattr(za[0], 'get_basic_selection')
487
- self._zarrcache[sidx] = (za, hasgbs)
488
- za, hasgbs = self._zarrcache[sidx]
489
619
  for ll in range(1, len(series.levels)):
490
620
  scale = round(math.log(max(za[0].shape[xidx] / za[ll].shape[xidx],
491
621
  za[0].shape[yidx] / za[ll].shape[yidx])) / math.log(2))
@@ -496,6 +626,30 @@ class TifffileFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
496
626
  self._nonempty_levels_list[frame] = nonempty
497
627
  return nonempty
498
628
 
629
+ def getPreferredLevel(self, level):
630
+ """
631
+ Given a desired level (0 is minimum resolution, self.levels - 1 is max
632
+ resolution), return the level that contains actual data that is no
633
+ lower resolution.
634
+
635
+ :param level: desired level
636
+ :returns level: a level with actual data that is no lower resolution.
637
+ """
638
+ return max(0, min(level, self.levels - 1))
639
+
640
+ def _getZarrArray(self, series, sidx):
641
+ with self._zarrlock:
642
+ if sidx not in self._zarrcache:
643
+ if len(self._zarrcache) > 10:
644
+ self._zarrcache = {}
645
+ za = zarr.open(series.aszarr(), mode='r')
646
+ hasgbs = hasattr(za[0], 'get_basic_selection')
647
+ if not hasgbs and math.prod(series.shape) < 256 * 1024 ** 2:
648
+ za = series.asarray()
649
+ self._zarrcache[sidx] = (za, hasgbs)
650
+ za, hasgbs = self._zarrcache[sidx]
651
+ return za, hasgbs
652
+
499
653
  @methodcache()
500
654
  def getTile(self, x, y, z, pilImageAllowed=False, numpyAllowed=False, **kwargs):
501
655
  frame = self._getFrame(**kwargs)
@@ -506,14 +660,7 @@ class TifffileFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
506
660
  else:
507
661
  sidx = 0
508
662
  series = self._tf.series[self._series[sidx]]
509
- with self._zarrlock:
510
- if sidx not in self._zarrcache:
511
- if len(self._zarrcache) > 10:
512
- self._zarrcache = {}
513
- za = zarr.open(series.aszarr(), mode='r')
514
- hasgbs = hasattr(za[0], 'get_basic_selection')
515
- self._zarrcache[sidx] = (za, hasgbs)
516
- za, hasgbs = self._zarrcache[sidx]
663
+ za, hasgbs = self._getZarrArray(series, sidx)
517
664
  xidx = series.axes.index('X')
518
665
  yidx = series.axes.index('Y')
519
666
  if hasgbs:
@@ -533,7 +680,7 @@ class TifffileFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
533
680
  else:
534
681
  bza = za
535
682
  if step > 2 ** self._maxSkippedLevels:
536
- tile = self._getTileFromEmptyLevel(x, y, z, **kwargs)
683
+ tile, _format = self._getTileFromEmptyLevel(x, y, z, **kwargs)
537
684
  tile = large_image.tilesource.base._imageToNumpy(tile)[0]
538
685
  else:
539
686
  sel = []
@@ -549,6 +696,8 @@ class TifffileFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
549
696
  sel.append(slice(series.shape[aidx]))
550
697
  baxis += 'S'
551
698
  else:
699
+ if axis not in self._basis and axis == 'I':
700
+ axis = 'C'
552
701
  sel.append((frame // self._basis[axis][0]) % self._basis[axis][2])
553
702
  tile = bza[tuple(sel)]
554
703
  # rotate
@@ -558,6 +707,16 @@ class TifffileFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
558
707
  return self._outputTile(tile, TILE_FORMAT_NUMPY, x, y, z,
559
708
  pilImageAllowed, numpyAllowed, **kwargs)
560
709
 
710
+ @classmethod
711
+ def addKnownExtensions(cls):
712
+ if not hasattr(cls, '_addedExtensions'):
713
+ _lazyImport()
714
+ cls._addedExtensions = True
715
+ cls.extensions = cls.extensions.copy()
716
+ for ext in tifffile.TIFF.FILE_EXTENSIONS:
717
+ if ext not in cls.extensions:
718
+ cls.extensions[ext] = SourcePriority.IMPLICIT
719
+
561
720
 
562
721
  def open(*args, **kwargs):
563
722
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: large-image-source-tifffile
3
- Version: 1.27.5.dev6
3
+ Version: 1.30.7.dev12
4
4
  Summary: A tifffile tilesource for large_image.
5
5
  Home-page: https://github.com/girder/large_image
6
6
  Author: Kitware, Inc.
@@ -15,9 +15,16 @@ 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.dev12
23
+ Requires-Dist: dask[array]
24
+ Requires-Dist: tifffile[all]
25
+ Requires-Dist: zarr
26
+ Provides-Extra: girder
27
+ Requires-Dist: girder-large-image>=1.30.7.dev12; extra == "girder"
21
28
 
22
29
  A tifffile tilesource for large_image.
23
30
 
@@ -0,0 +1,7 @@
1
+ large-image>=1.30.7.dev12
2
+ dask[array]
3
+ tifffile[all]
4
+ zarr
5
+
6
+ [girder]
7
+ girder-large-image>=1.30.7.dev12
@@ -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}',
@@ -1,7 +0,0 @@
1
- large-image>=1.27.5.dev6
2
- dask[array]
3
- tifffile[all]
4
- zarr
5
-
6
- [girder]
7
- girder-large-image>=1.27.5.dev6