large-image-source-tifffile 1.28.3.dev2__py3-none-any.whl → 1.30.7.dev12__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -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.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,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.dev12
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.dev12; 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.dev12.dist-info/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
4
+ large_image_source_tifffile-1.30.7.dev12.dist-info/METADATA,sha256=DwwJpYFWMtn8Tp7_mYPoplHzoqmMpoZ43B9xK7nZuZc,1405
5
+ large_image_source_tifffile-1.30.7.dev12.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
6
+ large_image_source_tifffile-1.30.7.dev12.dist-info/entry_points.txt,sha256=aILCN5f7-Zj8-WRXcABxCQvqJv9VLTvqRhZ_7HEyokc,190
7
+ large_image_source_tifffile-1.30.7.dev12.dist-info/top_level.txt,sha256=feOAsix2Rzy6mjy2Ua-N0-L6tlKsgvLdRhO6Hd8_ZFs,28
8
+ large_image_source_tifffile-1.30.7.dev12.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,,