large-image-source-zarr 1.27.5.dev6__tar.gz → 1.28.2.dev12__tar.gz
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-zarr-1.27.5.dev6 → large-image-source-zarr-1.28.2.dev12}/PKG-INFO +8 -2
- {large-image-source-zarr-1.27.5.dev6 → large-image-source-zarr-1.28.2.dev12}/large_image_source_zarr/__init__.py +398 -4
- {large-image-source-zarr-1.27.5.dev6 → large-image-source-zarr-1.28.2.dev12}/large_image_source_zarr.egg-info/PKG-INFO +8 -2
- large-image-source-zarr-1.28.2.dev12/large_image_source_zarr.egg-info/requires.txt +9 -0
- {large-image-source-zarr-1.27.5.dev6 → large-image-source-zarr-1.28.2.dev12}/setup.py +6 -0
- large-image-source-zarr-1.27.5.dev6/large_image_source_zarr.egg-info/requires.txt +0 -5
- {large-image-source-zarr-1.27.5.dev6 → large-image-source-zarr-1.28.2.dev12}/LICENSE +0 -0
- {large-image-source-zarr-1.27.5.dev6 → large-image-source-zarr-1.28.2.dev12}/README.rst +0 -0
- {large-image-source-zarr-1.27.5.dev6 → large-image-source-zarr-1.28.2.dev12}/large_image_source_zarr/girder_source.py +0 -0
- {large-image-source-zarr-1.27.5.dev6 → large-image-source-zarr-1.28.2.dev12}/large_image_source_zarr.egg-info/SOURCES.txt +0 -0
- {large-image-source-zarr-1.27.5.dev6 → large-image-source-zarr-1.28.2.dev12}/large_image_source_zarr.egg-info/dependency_links.txt +0 -0
- {large-image-source-zarr-1.27.5.dev6 → large-image-source-zarr-1.28.2.dev12}/large_image_source_zarr.egg-info/entry_points.txt +0 -0
- {large-image-source-zarr-1.27.5.dev6 → large-image-source-zarr-1.28.2.dev12}/large_image_source_zarr.egg-info/top_level.txt +0 -0
- {large-image-source-zarr-1.27.5.dev6 → large-image-source-zarr-1.28.2.dev12}/pyproject.toml +0 -0
- {large-image-source-zarr-1.27.5.dev6 → large-image-source-zarr-1.28.2.dev12}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: large-image-source-zarr
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.28.2.dev12
|
|
4
4
|
Summary: A OME Zarr tilesource for large_image.
|
|
5
5
|
Home-page: https://github.com/girder/large_image
|
|
6
6
|
Author: Kitware, Inc.
|
|
@@ -16,8 +16,14 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.11
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.12
|
|
18
18
|
Requires-Python: >=3.6
|
|
19
|
-
Provides-Extra: girder
|
|
20
19
|
License-File: LICENSE
|
|
20
|
+
Requires-Dist: large-image>=1.28.2.dev12
|
|
21
|
+
Requires-Dist: zarr
|
|
22
|
+
Requires-Dist: imagecodecs-numcodecs
|
|
23
|
+
Provides-Extra: girder
|
|
24
|
+
Requires-Dist: girder-large-image>=1.28.2.dev12; extra == "girder"
|
|
25
|
+
Provides-Extra: all
|
|
26
|
+
Requires-Dist: large-image-converter; extra == "all"
|
|
21
27
|
|
|
22
28
|
A OME Zarr tilesource for large_image.
|
|
23
29
|
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import math
|
|
2
2
|
import os
|
|
3
|
+
import shutil
|
|
4
|
+
import tempfile
|
|
3
5
|
import threading
|
|
6
|
+
import uuid
|
|
4
7
|
from importlib.metadata import PackageNotFoundError
|
|
5
8
|
from importlib.metadata import version as _importlib_version
|
|
9
|
+
from pathlib import Path
|
|
6
10
|
|
|
7
11
|
import numpy as np
|
|
8
12
|
import packaging.version
|
|
@@ -10,10 +14,11 @@ import zarr
|
|
|
10
14
|
|
|
11
15
|
import large_image
|
|
12
16
|
from large_image.cache_util import LruCacheMetaclass, methodcache
|
|
13
|
-
from large_image.constants import TILE_FORMAT_NUMPY, SourcePriority
|
|
17
|
+
from large_image.constants import NEW_IMAGE_PATH_FLAG, TILE_FORMAT_NUMPY, SourcePriority
|
|
14
18
|
from large_image.exceptions import TileSourceError, TileSourceFileNotFoundError
|
|
15
19
|
from large_image.tilesource import FileTileSource
|
|
16
|
-
from large_image.tilesource.
|
|
20
|
+
from large_image.tilesource.resample import ResampleMethod, downsampleTileHalfRes
|
|
21
|
+
from large_image.tilesource.utilities import _imageToNumpy, nearPowerOfTwo
|
|
17
22
|
|
|
18
23
|
try:
|
|
19
24
|
__version__ = _importlib_version(__name__)
|
|
@@ -34,8 +39,17 @@ class ZarrFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
|
34
39
|
'zarr': SourcePriority.PREFERRED,
|
|
35
40
|
'zgroup': SourcePriority.PREFERRED,
|
|
36
41
|
'zattrs': SourcePriority.PREFERRED,
|
|
42
|
+
'zarray': SourcePriority.PREFERRED,
|
|
37
43
|
'db': SourcePriority.MEDIUM,
|
|
44
|
+
'zip': SourcePriority.LOWER,
|
|
38
45
|
}
|
|
46
|
+
mimeTypes = {
|
|
47
|
+
None: SourcePriority.FALLBACK,
|
|
48
|
+
'application/zip+zarr': SourcePriority.PREFERRED,
|
|
49
|
+
'application/vnd+zarr': SourcePriority.PREFERRED,
|
|
50
|
+
'application/x-zarr': SourcePriority.PREFERRED,
|
|
51
|
+
}
|
|
52
|
+
newPriority = SourcePriority.HIGH
|
|
39
53
|
|
|
40
54
|
_tileSize = 512
|
|
41
55
|
_minTileSize = 128
|
|
@@ -52,6 +66,13 @@ class ZarrFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
|
52
66
|
"""
|
|
53
67
|
super().__init__(path, **kwargs)
|
|
54
68
|
|
|
69
|
+
if str(path).startswith(NEW_IMAGE_PATH_FLAG):
|
|
70
|
+
self._initNew(path, **kwargs)
|
|
71
|
+
else:
|
|
72
|
+
self._initOpen(**kwargs)
|
|
73
|
+
self._tileLock = threading.RLock()
|
|
74
|
+
|
|
75
|
+
def _initOpen(self, **kwargs):
|
|
55
76
|
self._largeImagePath = str(self._getLargeImagePath())
|
|
56
77
|
self._zarr = None
|
|
57
78
|
if not os.path.isfile(self._largeImagePath) and '//:' not in self._largeImagePath:
|
|
@@ -79,7 +100,48 @@ class ZarrFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
|
79
100
|
except Exception:
|
|
80
101
|
msg = 'File cannot be opened -- not an OME NGFF file or understandable zarr file.'
|
|
81
102
|
raise TileSourceError(msg)
|
|
82
|
-
|
|
103
|
+
|
|
104
|
+
def _initNew(self, path, **kwargs):
|
|
105
|
+
"""
|
|
106
|
+
Initialize the tile class for creating a new image.
|
|
107
|
+
"""
|
|
108
|
+
self._tempdir = tempfile.TemporaryDirectory(path)
|
|
109
|
+
self._zarr_store = zarr.DirectoryStore(self._tempdir.name)
|
|
110
|
+
self._zarr = zarr.open(self._zarr_store, mode='w')
|
|
111
|
+
# Make unpickleable
|
|
112
|
+
self._unpickleable = True
|
|
113
|
+
self._largeImagePath = None
|
|
114
|
+
self._dims = {}
|
|
115
|
+
self.sizeX = self.sizeY = self.levels = 0
|
|
116
|
+
self.tileWidth = self.tileHeight = self._tileSize
|
|
117
|
+
self._cacheValue = str(uuid.uuid4())
|
|
118
|
+
self._output = None
|
|
119
|
+
self._editable = True
|
|
120
|
+
self._bandRanges = None
|
|
121
|
+
self._addLock = threading.RLock()
|
|
122
|
+
self._framecount = 0
|
|
123
|
+
self._mm_x = 0
|
|
124
|
+
self._mm_y = 0
|
|
125
|
+
self._levels = []
|
|
126
|
+
|
|
127
|
+
def __del__(self):
|
|
128
|
+
if not hasattr(self, '_derivedSource'):
|
|
129
|
+
try:
|
|
130
|
+
self._zarr.close()
|
|
131
|
+
except Exception:
|
|
132
|
+
pass
|
|
133
|
+
try:
|
|
134
|
+
self._tempdir.cleanup()
|
|
135
|
+
except Exception:
|
|
136
|
+
pass
|
|
137
|
+
|
|
138
|
+
def _checkEditable(self):
|
|
139
|
+
"""
|
|
140
|
+
Raise an exception if this is not an editable image.
|
|
141
|
+
"""
|
|
142
|
+
if not self._editable:
|
|
143
|
+
msg = 'Not an editable image'
|
|
144
|
+
raise TileSourceError(msg)
|
|
83
145
|
|
|
84
146
|
def _getGeneralAxes(self, arr):
|
|
85
147
|
"""
|
|
@@ -283,6 +345,8 @@ class ZarrFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
|
283
345
|
baseArray.shape[self._axes.get('c')] in {1, 3, 4}):
|
|
284
346
|
self._bandCount = baseArray.shape[self._axes['c']]
|
|
285
347
|
self._axes['s'] = self._axes.pop('c')
|
|
348
|
+
elif 's' in self._axes:
|
|
349
|
+
self._bandCount = baseArray.shape[self._axes['s']]
|
|
286
350
|
self._zarrFindLevels()
|
|
287
351
|
self._getScale()
|
|
288
352
|
stride = 1
|
|
@@ -325,6 +389,13 @@ class ZarrFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
|
325
389
|
'mm_y': mm_y,
|
|
326
390
|
}
|
|
327
391
|
|
|
392
|
+
def getState(self):
|
|
393
|
+
# Use the _cacheValue to avoid caching the source and tiles if we are
|
|
394
|
+
# creating something new.
|
|
395
|
+
if not hasattr(self, '_cacheValue'):
|
|
396
|
+
return super().getState()
|
|
397
|
+
return super().getState() + ',%s' % (self._cacheValue, )
|
|
398
|
+
|
|
328
399
|
def getMetadata(self):
|
|
329
400
|
"""
|
|
330
401
|
Return a dictionary of metadata containing levels, sizeX, sizeY,
|
|
@@ -332,6 +403,8 @@ class ZarrFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
|
332
403
|
|
|
333
404
|
:returns: metadata dictionary.
|
|
334
405
|
"""
|
|
406
|
+
if self._levels is None:
|
|
407
|
+
self._validateZarr()
|
|
335
408
|
result = super().getMetadata()
|
|
336
409
|
if self._framecount > 1:
|
|
337
410
|
result['frames'] = frames = []
|
|
@@ -355,8 +428,11 @@ class ZarrFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
|
355
428
|
result = {}
|
|
356
429
|
result['zarr'] = {
|
|
357
430
|
'base': self._zarr.attrs.asdict(),
|
|
358
|
-
'main': self._series[0][0].attrs.asdict(),
|
|
359
431
|
}
|
|
432
|
+
try:
|
|
433
|
+
result['zarr']['main'] = self._series[0][0].attrs.asdict()
|
|
434
|
+
except Exception:
|
|
435
|
+
pass
|
|
360
436
|
return result
|
|
361
437
|
|
|
362
438
|
def getAssociatedImagesList(self):
|
|
@@ -396,6 +472,9 @@ class ZarrFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
|
396
472
|
|
|
397
473
|
@methodcache()
|
|
398
474
|
def getTile(self, x, y, z, pilImageAllowed=False, numpyAllowed=False, **kwargs):
|
|
475
|
+
if self._levels is None:
|
|
476
|
+
self._validateZarr()
|
|
477
|
+
|
|
399
478
|
frame = self._getFrame(**kwargs)
|
|
400
479
|
self._xyzInRange(x, y, z, frame, self._framecount)
|
|
401
480
|
x0, y0, x1, y1, step = self._xyzToCorners(x, y, z)
|
|
@@ -438,6 +517,313 @@ class ZarrFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
|
438
517
|
return self._outputTile(tile, TILE_FORMAT_NUMPY, x, y, z,
|
|
439
518
|
pilImageAllowed, numpyAllowed, **kwargs)
|
|
440
519
|
|
|
520
|
+
def _validateNewTile(self, tile, mask, placement, axes):
|
|
521
|
+
if not isinstance(tile, np.ndarray) or axes is None:
|
|
522
|
+
axes = 'yxs'
|
|
523
|
+
tile, mode = _imageToNumpy(tile)
|
|
524
|
+
elif not isinstance(axes, str) and not isinstance(axes, list):
|
|
525
|
+
err = 'Invalid type for axes. Must be str or list[str].'
|
|
526
|
+
raise ValueError(err)
|
|
527
|
+
axes = [x.lower() for x in axes]
|
|
528
|
+
if axes[-1] != 's':
|
|
529
|
+
axes.append('s')
|
|
530
|
+
if mask is not None and len(axes) - 1 == len(mask.shape):
|
|
531
|
+
mask = mask[:, :, np.newaxis]
|
|
532
|
+
if 'x' not in axes or 'y' not in axes:
|
|
533
|
+
err = 'Invalid value for axes. Must contain "y" and "x".'
|
|
534
|
+
raise ValueError(err)
|
|
535
|
+
for k in placement:
|
|
536
|
+
if k not in axes:
|
|
537
|
+
axes[0:0] = [k]
|
|
538
|
+
while len(tile.shape) < len(axes):
|
|
539
|
+
tile = np.expand_dims(tile, axis=0)
|
|
540
|
+
while mask is not None and len(mask.shape) < len(axes):
|
|
541
|
+
mask = np.expand_dims(mask, axis=0)
|
|
542
|
+
|
|
543
|
+
return tile, mask, placement, axes
|
|
544
|
+
|
|
545
|
+
def addTile(self, tile, x=0, y=0, mask=None, axes=None, **kwargs):
|
|
546
|
+
"""
|
|
547
|
+
Add a numpy or image tile to the image, expanding the image as needed
|
|
548
|
+
to accommodate it. Note that x and y can be negative. If so, the
|
|
549
|
+
output image (and internal memory access of the image) will act as if
|
|
550
|
+
the 0, 0 point is the most negative position. Cropping is applied
|
|
551
|
+
after this offset.
|
|
552
|
+
|
|
553
|
+
:param tile: a numpy array, PIL Image, or a binary string
|
|
554
|
+
with an image. The numpy array can have 2 or 3 dimensions.
|
|
555
|
+
:param x: location in destination for upper-left corner.
|
|
556
|
+
:param y: location in destination for upper-left corner.
|
|
557
|
+
:param mask: a 2-d numpy array (or 3-d if the last dimension is 1).
|
|
558
|
+
If specified, areas where the mask is false will not be altered.
|
|
559
|
+
:param axes: a string or list of strings specifying the names of axes
|
|
560
|
+
in the same order as the tile dimensions.
|
|
561
|
+
:param kwargs: start locations for any additional axes. Note that
|
|
562
|
+
``level`` is a reserved word and not permitted for an axis name.
|
|
563
|
+
"""
|
|
564
|
+
# TODO: improve band bookkeeping
|
|
565
|
+
|
|
566
|
+
self._checkEditable()
|
|
567
|
+
store_path = str(kwargs.pop('level', 0))
|
|
568
|
+
placement = {
|
|
569
|
+
'x': x,
|
|
570
|
+
'y': y,
|
|
571
|
+
**kwargs,
|
|
572
|
+
}
|
|
573
|
+
tile, mask, placement, axes = self._validateNewTile(tile, mask, placement, axes)
|
|
574
|
+
|
|
575
|
+
with self._addLock:
|
|
576
|
+
self._axes = {k: i for i, k in enumerate(axes)}
|
|
577
|
+
new_dims = {
|
|
578
|
+
a: max(
|
|
579
|
+
self._dims.get(store_path, {}).get(a, 0),
|
|
580
|
+
placement.get(a, 0) + tile.shape[i],
|
|
581
|
+
)
|
|
582
|
+
for a, i in self._axes.items()
|
|
583
|
+
}
|
|
584
|
+
self._dims[store_path] = new_dims
|
|
585
|
+
placement_slices = tuple([
|
|
586
|
+
slice(placement.get(a, 0), placement.get(a, 0) + tile.shape[i], 1)
|
|
587
|
+
for i, a in enumerate(axes)
|
|
588
|
+
])
|
|
589
|
+
|
|
590
|
+
current_arrays = dict(self._zarr.arrays())
|
|
591
|
+
if store_path == '0':
|
|
592
|
+
# if writing to base data, invalidate generated levels
|
|
593
|
+
for path in current_arrays:
|
|
594
|
+
if path != store_path:
|
|
595
|
+
self._zarr_store.rmdir(path)
|
|
596
|
+
chunking = None
|
|
597
|
+
if store_path not in current_arrays:
|
|
598
|
+
arr = np.empty(tuple(new_dims.values()), dtype=tile.dtype)
|
|
599
|
+
chunking = tuple([
|
|
600
|
+
self._tileSize if a in ['x', 'y'] else
|
|
601
|
+
new_dims.get('s') if a == 's' else 1
|
|
602
|
+
for a in axes
|
|
603
|
+
])
|
|
604
|
+
else:
|
|
605
|
+
arr = current_arrays[store_path]
|
|
606
|
+
arr.resize(*tuple(new_dims.values()))
|
|
607
|
+
if arr.chunks[-1] != new_dims.get('s'):
|
|
608
|
+
# rechunk if length of samples axis changes
|
|
609
|
+
chunking = tuple([
|
|
610
|
+
self._tileSize if a in ['x', 'y'] else
|
|
611
|
+
new_dims.get('s') if a == 's' else 1
|
|
612
|
+
for a in axes
|
|
613
|
+
])
|
|
614
|
+
|
|
615
|
+
if mask is not None:
|
|
616
|
+
arr[placement_slices] = np.where(mask, tile, arr[placement_slices])
|
|
617
|
+
else:
|
|
618
|
+
arr[placement_slices] = tile
|
|
619
|
+
if chunking:
|
|
620
|
+
zarr.array(
|
|
621
|
+
arr,
|
|
622
|
+
chunks=chunking,
|
|
623
|
+
overwrite=True,
|
|
624
|
+
store=self._zarr_store,
|
|
625
|
+
path=store_path,
|
|
626
|
+
)
|
|
627
|
+
|
|
628
|
+
# If base data changed, update large_image attributes and OME metadata
|
|
629
|
+
if store_path == '0':
|
|
630
|
+
self._zarr.attrs.update({
|
|
631
|
+
'multiscales': [{
|
|
632
|
+
'version': '0.5-dev',
|
|
633
|
+
'axes': [{
|
|
634
|
+
'name': a,
|
|
635
|
+
'type': 'space' if a in ['x', 'y'] else 'other',
|
|
636
|
+
} for a in axes],
|
|
637
|
+
'datasets': [{'path': 0}],
|
|
638
|
+
}],
|
|
639
|
+
'omero': {'version': '0.5-dev'},
|
|
640
|
+
})
|
|
641
|
+
|
|
642
|
+
self._dtype = tile.dtype
|
|
643
|
+
self._bandCount = new_dims.get(axes[-1]) # last axis is assumed to be bands
|
|
644
|
+
self.sizeX = new_dims.get('x')
|
|
645
|
+
self.sizeY = new_dims.get('y')
|
|
646
|
+
self._framecount = np.prod([
|
|
647
|
+
length
|
|
648
|
+
for axis, length in new_dims.items()
|
|
649
|
+
if axis in axes[:-3]
|
|
650
|
+
])
|
|
651
|
+
self._cacheValue = str(uuid.uuid4())
|
|
652
|
+
self._levels = None
|
|
653
|
+
self.levels = int(max(1, math.ceil(math.log(max(
|
|
654
|
+
self.sizeX / self.tileWidth, self.sizeY / self.tileHeight)) / math.log(2)) + 1))
|
|
655
|
+
|
|
656
|
+
@property
|
|
657
|
+
def crop(self):
|
|
658
|
+
"""
|
|
659
|
+
Crop only applies to the output file, not the internal data access.
|
|
660
|
+
|
|
661
|
+
It consists of x, y, w, h in pixels.
|
|
662
|
+
"""
|
|
663
|
+
return getattr(self, '_crop', None)
|
|
664
|
+
|
|
665
|
+
@crop.setter
|
|
666
|
+
def crop(self, value):
|
|
667
|
+
self._checkEditable()
|
|
668
|
+
if value is None:
|
|
669
|
+
self._crop = None
|
|
670
|
+
return
|
|
671
|
+
x, y, w, h = value
|
|
672
|
+
x = int(x)
|
|
673
|
+
y = int(y)
|
|
674
|
+
w = int(w)
|
|
675
|
+
h = int(h)
|
|
676
|
+
if x < 0 or y < 0 or w <= 0 or h <= 0:
|
|
677
|
+
msg = 'Crop must have non-negative x, y and positive w, h'
|
|
678
|
+
raise TileSourceError(msg)
|
|
679
|
+
self._crop = (x, y, w, h)
|
|
680
|
+
|
|
681
|
+
def _generateDownsampledLevels(self, resample_method):
|
|
682
|
+
self._checkEditable()
|
|
683
|
+
current_arrays = dict(self._zarr.arrays())
|
|
684
|
+
if '0' not in current_arrays:
|
|
685
|
+
msg = 'No root data found, cannot generate lower resolution levels.'
|
|
686
|
+
raise TileSourceError(msg)
|
|
687
|
+
if 'x' not in self._axes or 'y' not in self._axes:
|
|
688
|
+
msg = 'Data must have an X axis and Y axis to generate lower resolution levels.'
|
|
689
|
+
raise TileSourceError(msg)
|
|
690
|
+
|
|
691
|
+
metadata = self.getMetadata()
|
|
692
|
+
|
|
693
|
+
if (
|
|
694
|
+
resample_method.value < ResampleMethod.PIL_MAX_ENUM.value and
|
|
695
|
+
resample_method != ResampleMethod.PIL_NEAREST
|
|
696
|
+
):
|
|
697
|
+
tile_overlap = dict(x=4, y=4, edges=True)
|
|
698
|
+
else:
|
|
699
|
+
tile_overlap = dict(x=0, y=0)
|
|
700
|
+
tile_size = dict(
|
|
701
|
+
width=4096 + tile_overlap['x'],
|
|
702
|
+
height=4096 + tile_overlap['y'],
|
|
703
|
+
)
|
|
704
|
+
for level in range(1, self.levels):
|
|
705
|
+
scale_factor = 2 ** level
|
|
706
|
+
iterator_output = dict(
|
|
707
|
+
maxWidth=self.sizeX // scale_factor,
|
|
708
|
+
maxHeight=self.sizeY // scale_factor,
|
|
709
|
+
)
|
|
710
|
+
for frame in metadata.get('frames', [{'Index': 0}]):
|
|
711
|
+
frame_position = {
|
|
712
|
+
k.replace('Index', '').lower(): v
|
|
713
|
+
for k, v in frame.items()
|
|
714
|
+
if k.replace('Index', '').lower() in self._axes
|
|
715
|
+
}
|
|
716
|
+
for tile in self.tileIterator(
|
|
717
|
+
tile_size=tile_size,
|
|
718
|
+
tile_overlap=tile_overlap,
|
|
719
|
+
frame=frame['Index'],
|
|
720
|
+
output=iterator_output,
|
|
721
|
+
resample=False, # TODO: incorporate resampling in core
|
|
722
|
+
):
|
|
723
|
+
new_tile = downsampleTileHalfRes(tile['tile'], resample_method)
|
|
724
|
+
overlap = {k: int(v / 2) for k, v in tile['tile_overlap'].items()}
|
|
725
|
+
new_tile = new_tile[
|
|
726
|
+
slice(overlap['top'], new_tile.shape[0] - overlap['bottom']),
|
|
727
|
+
slice(overlap['left'], new_tile.shape[1] - overlap['right']),
|
|
728
|
+
]
|
|
729
|
+
|
|
730
|
+
x = int(tile['x'] / 2 + overlap['left'])
|
|
731
|
+
y = int(tile['y'] / 2 + overlap['top'])
|
|
732
|
+
|
|
733
|
+
self.addTile(
|
|
734
|
+
new_tile,
|
|
735
|
+
x=x,
|
|
736
|
+
y=y,
|
|
737
|
+
**frame_position,
|
|
738
|
+
axes=list(self._axes.keys()),
|
|
739
|
+
level=level,
|
|
740
|
+
)
|
|
741
|
+
self._validateZarr() # refresh self._levels before continuing
|
|
742
|
+
|
|
743
|
+
def write(
|
|
744
|
+
self,
|
|
745
|
+
path,
|
|
746
|
+
lossy=True,
|
|
747
|
+
alpha=True,
|
|
748
|
+
overwriteAllowed=True,
|
|
749
|
+
resample=None,
|
|
750
|
+
):
|
|
751
|
+
"""
|
|
752
|
+
Output the current image to a file.
|
|
753
|
+
|
|
754
|
+
:param path: output path.
|
|
755
|
+
:param lossy: if false, emit a lossless file.
|
|
756
|
+
:param alpha: True if an alpha channel is allowed.
|
|
757
|
+
:param overwriteAllowed: if False, raise an exception if the output
|
|
758
|
+
path exists.
|
|
759
|
+
:param resample: one of the ``ResampleMethod`` enum values. Defaults
|
|
760
|
+
to ``NP_NEAREST`` for lossless and non-uint8 data and to
|
|
761
|
+
``PIL_LANCZOS`` for lossy uint8 data.
|
|
762
|
+
"""
|
|
763
|
+
if os.path.exists(path):
|
|
764
|
+
if overwriteAllowed:
|
|
765
|
+
if os.path.isdir(path):
|
|
766
|
+
shutil.rmtree(path)
|
|
767
|
+
else:
|
|
768
|
+
os.remove(path)
|
|
769
|
+
else:
|
|
770
|
+
raise TileSourceError('Output path exists (%s).' % str(path))
|
|
771
|
+
|
|
772
|
+
suffix = Path(path).suffix
|
|
773
|
+
source = self
|
|
774
|
+
|
|
775
|
+
if self.crop:
|
|
776
|
+
left, top, width, height = self.crop
|
|
777
|
+
source = new()
|
|
778
|
+
source._zarr.attrs.update(self._zarr.attrs)
|
|
779
|
+
for frame in self.getMetadata().get('frames', [{'Index': 0}]):
|
|
780
|
+
frame_position = {
|
|
781
|
+
k.replace('Index', '').lower(): v
|
|
782
|
+
for k, v in frame.items()
|
|
783
|
+
if k.replace('Index', '').lower() in self._axes
|
|
784
|
+
}
|
|
785
|
+
for tile in self.tileIterator(
|
|
786
|
+
frame=frame['Index'],
|
|
787
|
+
region=dict(top=top, left=left, width=width, height=height),
|
|
788
|
+
resample=False,
|
|
789
|
+
):
|
|
790
|
+
source.addTile(
|
|
791
|
+
tile['tile'],
|
|
792
|
+
x=tile['x'] - left,
|
|
793
|
+
y=tile['y'] - top,
|
|
794
|
+
axes=list(self._axes.keys()),
|
|
795
|
+
**frame_position,
|
|
796
|
+
)
|
|
797
|
+
|
|
798
|
+
if suffix in ['.zarr', '.db', '.sqlite', '.zip']:
|
|
799
|
+
if resample is None:
|
|
800
|
+
resample = (
|
|
801
|
+
ResampleMethod.PIL_LANCZOS
|
|
802
|
+
if lossy and source.dtype == np.uint8
|
|
803
|
+
else ResampleMethod.NP_NEAREST
|
|
804
|
+
)
|
|
805
|
+
source._generateDownsampledLevels(resample)
|
|
806
|
+
|
|
807
|
+
if suffix == '.zarr':
|
|
808
|
+
shutil.copytree(source._tempdir.name, path)
|
|
809
|
+
elif suffix in ['.db', '.sqlite']:
|
|
810
|
+
sqlite_store = zarr.SQLiteStore(path)
|
|
811
|
+
zarr.copy_store(source._zarr_store, sqlite_store, if_exists='replace')
|
|
812
|
+
sqlite_store.close()
|
|
813
|
+
elif suffix == '.zip':
|
|
814
|
+
zip_store = zarr.ZipStore(path)
|
|
815
|
+
zarr.copy_store(source._zarr_store, zip_store, if_exists='replace')
|
|
816
|
+
zip_store.close()
|
|
817
|
+
|
|
818
|
+
else:
|
|
819
|
+
from large_image_converter import convert
|
|
820
|
+
|
|
821
|
+
attrs_path = Path(source._tempdir.name) / '.zattrs'
|
|
822
|
+
params = {}
|
|
823
|
+
if lossy and self.dtype == np.uint8:
|
|
824
|
+
params['compression'] = 'jpeg'
|
|
825
|
+
convert(str(attrs_path), path, overwrite=overwriteAllowed, **params)
|
|
826
|
+
|
|
441
827
|
|
|
442
828
|
def open(*args, **kwargs):
|
|
443
829
|
"""
|
|
@@ -451,3 +837,11 @@ def canRead(*args, **kwargs):
|
|
|
451
837
|
Check if an input can be read by the module class.
|
|
452
838
|
"""
|
|
453
839
|
return ZarrFileTileSource.canRead(*args, **kwargs)
|
|
840
|
+
|
|
841
|
+
|
|
842
|
+
def new(*args, **kwargs):
|
|
843
|
+
"""
|
|
844
|
+
Create a new image, collecting the results from patches of numpy arrays or
|
|
845
|
+
smaller images.
|
|
846
|
+
"""
|
|
847
|
+
return ZarrFileTileSource(NEW_IMAGE_PATH_FLAG + str(uuid.uuid4()), *args, **kwargs)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: large-image-source-zarr
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.28.2.dev12
|
|
4
4
|
Summary: A OME Zarr tilesource for large_image.
|
|
5
5
|
Home-page: https://github.com/girder/large_image
|
|
6
6
|
Author: Kitware, Inc.
|
|
@@ -16,8 +16,14 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.11
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.12
|
|
18
18
|
Requires-Python: >=3.6
|
|
19
|
-
Provides-Extra: girder
|
|
20
19
|
License-File: LICENSE
|
|
20
|
+
Requires-Dist: large-image>=1.28.2.dev12
|
|
21
|
+
Requires-Dist: zarr
|
|
22
|
+
Requires-Dist: imagecodecs-numcodecs
|
|
23
|
+
Provides-Extra: girder
|
|
24
|
+
Requires-Dist: girder-large-image>=1.28.2.dev12; extra == "girder"
|
|
25
|
+
Provides-Extra: all
|
|
26
|
+
Requires-Dist: large-image-converter; extra == "all"
|
|
21
27
|
|
|
22
28
|
A OME Zarr tilesource for large_image.
|
|
23
29
|
|
|
@@ -53,9 +53,15 @@ setup(
|
|
|
53
53
|
install_requires=[
|
|
54
54
|
f'large-image{limit_version}',
|
|
55
55
|
'zarr',
|
|
56
|
+
# I am uncertain why this is required, since numcodecs is required by
|
|
57
|
+
# zarr; but without it some jpeg encoded data cannot be read
|
|
58
|
+
'imagecodecs-numcodecs',
|
|
56
59
|
],
|
|
57
60
|
extras_require={
|
|
58
61
|
'girder': f'girder-large-image{limit_version}',
|
|
62
|
+
'all': [
|
|
63
|
+
'large-image-converter',
|
|
64
|
+
],
|
|
59
65
|
},
|
|
60
66
|
keywords='large_image, tile source',
|
|
61
67
|
packages=find_packages(exclude=['test', 'test.*']),
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|