large-image-source-tifffile 1.28.3.dev2__py3-none-any.whl → 1.30.7.dev14__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.
@@ -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,
@@ -117,6 +122,7 @@ class TifffileFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
117
122
  raise TileSourceFileNotFoundError(self._largeImagePath) from None
118
123
  msg = 'File cannot be opened via tifffile.'
119
124
  raise TileSourceError(msg)
125
+ self._checkForOmeBinaryonly()
120
126
  maxseries, maxsamples = self._biggestSeries()
121
127
  self.tileWidth = self.tileHeight = self._tileSize
122
128
  s = self._tf.series[maxseries]
@@ -124,6 +130,9 @@ class TifffileFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
124
130
  if len(s.levels) == 1:
125
131
  self.tileWidth = self.tileHeight = self._singleTileSize
126
132
  page = s.pages[0]
133
+ if not hasattr(page, 'tags'):
134
+ msg = 'File will not be opened via tifffile.'
135
+ raise TileSourceError(msg)
127
136
  if ('TileWidth' in page.tags and
128
137
  self._minTileSize <= page.tags['TileWidth'].value <= self._maxTileSize):
129
138
  self.tileWidth = page.tags['TileWidth'].value
@@ -134,6 +143,10 @@ class TifffileFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
134
143
  self._iccprofiles = [page.tags['InterColorProfile'].value]
135
144
  self.sizeX = s.shape[s.axes.index('X')]
136
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
137
150
  self._mm_x = self._mm_y = None
138
151
  try:
139
152
  unit = {2: 25.4, 3: 10}[page.tags['ResolutionUnit'].value.real]
@@ -167,6 +180,35 @@ class TifffileFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
167
180
  msg = 'File cannot be opened via tifffile: axes and shape do not match access pattern.'
168
181
  raise TileSourceError(msg)
169
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
+
170
212
  def _biggestSeries(self):
171
213
  """
172
214
  Find the series with the most pixels. Use all series that have the
@@ -180,7 +222,7 @@ class TifffileFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
180
222
  ex = 'no maximum series'
181
223
  try:
182
224
  for idx, s in enumerate(self._tf.series):
183
- samples = np.prod(s.shape)
225
+ samples = math.prod(s.shape)
184
226
  if samples > maxsamples and 'X' in s.axes and 'Y' in s.axes:
185
227
  maxseries = idx
186
228
  maxsamples = samples
@@ -229,7 +271,7 @@ class TifffileFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
229
271
  'sizeX': s.shape[s.axes.index('X')], 'sizeY': s.shape[s.axes.index('Y')]})
230
272
  self.sizeX = max(self.sizeX, s.shape[s.axes.index('X')])
231
273
  self.sizeY = max(self.sizeY, s.shape[s.axes.index('Y')])
232
- self._framecount = len(self._series) * np.prod(tuple(
274
+ self._framecount = len(self._series) * math.prod(tuple(
233
275
  1 if base.axes[sidx] in 'YXS' else v for sidx, v in enumerate(base.shape)))
234
276
  self._basis = {}
235
277
  basis = 1
@@ -256,14 +298,17 @@ class TifffileFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
256
298
  for p in self._tf.pages:
257
299
  if (p not in pagesInSeries and getattr(p, 'keyframe', None) is not None and
258
300
  p.hash not in hashes and not len(set(p.axes) - set('YXS'))):
259
- id = 'image_%s' % p.index
260
- entry = {'page': p.index}
261
- entry['width'] = p.shape[p.axes.index('X')]
262
- entry['height'] = p.shape[p.axes.index('Y')]
263
- if (id not in self._associatedImages and
264
- max(entry['width'], entry['height']) <= self._maxAssociatedImageSize and
265
- max(entry['width'], entry['height']) >= self._minAssociatedImageSize):
266
- 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
267
312
  for sidx, s in enumerate(self._tf.series):
268
313
  if sidx not in self._series and not len(set(s.axes) - set('YXS')):
269
314
  id = 'series_%d' % sidx
@@ -286,6 +331,79 @@ class TifffileFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
286
331
  except Exception:
287
332
  pass
288
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
+
289
407
  def _handle_scn(self): # noqa
290
408
  """
291
409
  For SCN files, parse the xml and possibly adjust how associated images
@@ -351,6 +469,20 @@ class TifffileFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
351
469
  except Exception:
352
470
  pass
353
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
+
354
486
  def getNativeMagnification(self):
355
487
  """
356
488
  Get the magnification at a particular level.
@@ -420,6 +552,10 @@ class TifffileFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
420
552
  json.dumps(result[key][subkey])
421
553
  except Exception:
422
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)
423
559
  if hasattr(self, '_xml') and 'xml' not in result:
424
560
  result.pop('ImageDescription', None)
425
561
  result['xml'] = self._xml
@@ -490,6 +626,17 @@ class TifffileFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
490
626
  self._nonempty_levels_list[frame] = nonempty
491
627
  return nonempty
492
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
+
493
640
  def _getZarrArray(self, series, sidx):
494
641
  with self._zarrlock:
495
642
  if sidx not in self._zarrcache:
@@ -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
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: large-image-source-tifffile
3
- Version: 1.28.3.dev2
3
+ Version: 1.30.7.dev14
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,14 +15,28 @@ 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 >=1.28.3.dev2
22
+ Requires-Dist: large-image>=1.30.7.dev14
21
23
  Requires-Dist: dask[array]
22
24
  Requires-Dist: tifffile[all]
23
25
  Requires-Dist: zarr
24
26
  Provides-Extra: girder
25
- Requires-Dist: girder-large-image >=1.28.3.dev2 ; extra == 'girder'
27
+ Requires-Dist: girder-large-image>=1.30.7.dev14; extra == "girder"
28
+ Dynamic: author
29
+ Dynamic: author-email
30
+ Dynamic: classifier
31
+ Dynamic: description
32
+ Dynamic: description-content-type
33
+ Dynamic: home-page
34
+ Dynamic: keywords
35
+ Dynamic: license
36
+ Dynamic: provides-extra
37
+ Dynamic: requires-dist
38
+ Dynamic: requires-python
39
+ Dynamic: summary
26
40
 
27
41
  A tifffile tilesource for large_image.
28
42
 
@@ -0,0 +1,8 @@
1
+ large_image_source_tifffile/__init__.py,sha256=WsyPJKR2Iij1V7maAfv4-m1xEhEUHLlRsO3qOK9IIWc,30244
2
+ large_image_source_tifffile/girder_source.py,sha256=8YsxpSK_UzbAcpjn6fIMlgKye2FchkB8_HTSQV0FpRA,1064
3
+ large_image_source_tifffile-1.30.7.dev14.dist-info/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
4
+ large_image_source_tifffile-1.30.7.dev14.dist-info/METADATA,sha256=HZbBC3gdWWv_ZegeoKX2YzbnTJyRl8Bhw-uPd2Ud5iY,1405
5
+ large_image_source_tifffile-1.30.7.dev14.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
6
+ large_image_source_tifffile-1.30.7.dev14.dist-info/entry_points.txt,sha256=aILCN5f7-Zj8-WRXcABxCQvqJv9VLTvqRhZ_7HEyokc,190
7
+ large_image_source_tifffile-1.30.7.dev14.dist-info/top_level.txt,sha256=feOAsix2Rzy6mjy2Ua-N0-L6tlKsgvLdRhO6Hd8_ZFs,28
8
+ large_image_source_tifffile-1.30.7.dev14.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.43.0)
2
+ Generator: setuptools (75.8.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,8 +0,0 @@
1
- large_image_source_tifffile/__init__.py,sha256=7hREWGbhyYOcz94l_vxMAOTUSJKtJY37TEIZdkvamNw,25001
2
- large_image_source_tifffile/girder_source.py,sha256=8YsxpSK_UzbAcpjn6fIMlgKye2FchkB8_HTSQV0FpRA,1064
3
- large_image_source_tifffile-1.28.3.dev2.dist-info/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
4
- large_image_source_tifffile-1.28.3.dev2.dist-info/METADATA,sha256=2XRfComEY1H1fO7m6luWIfzukgRCnPmPJw6RVkiDpZw,1061
5
- large_image_source_tifffile-1.28.3.dev2.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
6
- large_image_source_tifffile-1.28.3.dev2.dist-info/entry_points.txt,sha256=aILCN5f7-Zj8-WRXcABxCQvqJv9VLTvqRhZ_7HEyokc,190
7
- large_image_source_tifffile-1.28.3.dev2.dist-info/top_level.txt,sha256=feOAsix2Rzy6mjy2Ua-N0-L6tlKsgvLdRhO6Hd8_ZFs,28
8
- large_image_source_tifffile-1.28.3.dev2.dist-info/RECORD,,