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.
- large_image_source_tifffile/__init__.py +161 -12
- {large_image_source_tifffile-1.28.3.dev2.dist-info → large_image_source_tifffile-1.30.7.dev14.dist-info}/METADATA +18 -4
- large_image_source_tifffile-1.30.7.dev14.dist-info/RECORD +8 -0
- {large_image_source_tifffile-1.28.3.dev2.dist-info → large_image_source_tifffile-1.30.7.dev14.dist-info}/WHEEL +1 -1
- large_image_source_tifffile-1.28.3.dev2.dist-info/RECORD +0 -8
- {large_image_source_tifffile-1.28.3.dev2.dist-info → large_image_source_tifffile-1.30.7.dev14.dist-info}/LICENSE +0 -0
- {large_image_source_tifffile-1.28.3.dev2.dist-info → large_image_source_tifffile-1.30.7.dev14.dist-info}/entry_points.txt +0 -0
- {large_image_source_tifffile-1.28.3.dev2.dist-info → large_image_source_tifffile-1.30.7.dev14.dist-info}/top_level.txt +0 -0
@@ -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 =
|
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) *
|
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
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
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
|
+
Metadata-Version: 2.2
|
2
2
|
Name: large-image-source-tifffile
|
3
|
-
Version: 1.
|
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
|
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
|
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,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,,
|
File without changes
|
File without changes
|