large-image-source-zarr 1.27.5.dev69__tar.gz → 1.27.5.dev99__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.dev69 → large-image-source-zarr-1.27.5.dev99}/PKG-INFO +1 -1
- {large-image-source-zarr-1.27.5.dev69 → large-image-source-zarr-1.27.5.dev99}/large_image_source_zarr/__init__.py +184 -98
- {large-image-source-zarr-1.27.5.dev69 → large-image-source-zarr-1.27.5.dev99}/large_image_source_zarr.egg-info/PKG-INFO +1 -1
- large-image-source-zarr-1.27.5.dev99/large_image_source_zarr.egg-info/requires.txt +6 -0
- large-image-source-zarr-1.27.5.dev69/large_image_source_zarr.egg-info/requires.txt +0 -6
- {large-image-source-zarr-1.27.5.dev69 → large-image-source-zarr-1.27.5.dev99}/LICENSE +0 -0
- {large-image-source-zarr-1.27.5.dev69 → large-image-source-zarr-1.27.5.dev99}/README.rst +0 -0
- {large-image-source-zarr-1.27.5.dev69 → large-image-source-zarr-1.27.5.dev99}/large_image_source_zarr/girder_source.py +0 -0
- {large-image-source-zarr-1.27.5.dev69 → large-image-source-zarr-1.27.5.dev99}/large_image_source_zarr.egg-info/SOURCES.txt +0 -0
- {large-image-source-zarr-1.27.5.dev69 → large-image-source-zarr-1.27.5.dev99}/large_image_source_zarr.egg-info/dependency_links.txt +0 -0
- {large-image-source-zarr-1.27.5.dev69 → large-image-source-zarr-1.27.5.dev99}/large_image_source_zarr.egg-info/entry_points.txt +0 -0
- {large-image-source-zarr-1.27.5.dev69 → large-image-source-zarr-1.27.5.dev99}/large_image_source_zarr.egg-info/top_level.txt +0 -0
- {large-image-source-zarr-1.27.5.dev69 → large-image-source-zarr-1.27.5.dev99}/pyproject.toml +0 -0
- {large-image-source-zarr-1.27.5.dev69 → large-image-source-zarr-1.27.5.dev99}/setup.cfg +0 -0
- {large-image-source-zarr-1.27.5.dev69 → large-image-source-zarr-1.27.5.dev99}/setup.py +0 -0
|
@@ -17,6 +17,7 @@ from large_image.cache_util import LruCacheMetaclass, methodcache
|
|
|
17
17
|
from large_image.constants import NEW_IMAGE_PATH_FLAG, TILE_FORMAT_NUMPY, SourcePriority
|
|
18
18
|
from large_image.exceptions import TileSourceError, TileSourceFileNotFoundError
|
|
19
19
|
from large_image.tilesource import FileTileSource
|
|
20
|
+
from large_image.tilesource.resample import ResampleMethod, downsampleTileHalfRes
|
|
20
21
|
from large_image.tilesource.utilities import _imageToNumpy, nearPowerOfTwo
|
|
21
22
|
|
|
22
23
|
try:
|
|
@@ -130,7 +131,7 @@ class ZarrFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
|
130
131
|
except Exception:
|
|
131
132
|
pass
|
|
132
133
|
try:
|
|
133
|
-
|
|
134
|
+
self._tempdir.cleanup()
|
|
134
135
|
except Exception:
|
|
135
136
|
pass
|
|
136
137
|
|
|
@@ -516,6 +517,31 @@ class ZarrFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
|
516
517
|
return self._outputTile(tile, TILE_FORMAT_NUMPY, x, y, z,
|
|
517
518
|
pilImageAllowed, numpyAllowed, **kwargs)
|
|
518
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
|
+
|
|
519
545
|
def addTile(self, tile, x=0, y=0, mask=None, axes=None, **kwargs):
|
|
520
546
|
"""
|
|
521
547
|
Add a numpy or image tile to the image, expanding the image as needed
|
|
@@ -537,60 +563,47 @@ class ZarrFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
|
537
563
|
# TODO: improve band bookkeeping
|
|
538
564
|
|
|
539
565
|
self._checkEditable()
|
|
566
|
+
store_path = str(kwargs.pop('level', 0))
|
|
540
567
|
placement = {
|
|
541
568
|
'x': x,
|
|
542
569
|
'y': y,
|
|
543
570
|
**kwargs,
|
|
544
571
|
}
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
tile, mode = _imageToNumpy(tile)
|
|
548
|
-
elif not isinstance(axes, str) and not isinstance(axes, list):
|
|
549
|
-
err = 'Invalid type for axes. Must be str or list[str].'
|
|
550
|
-
raise ValueError(err)
|
|
551
|
-
axes = [x.lower() for x in axes]
|
|
552
|
-
if axes[-1] != 's':
|
|
553
|
-
axes.append('s')
|
|
554
|
-
if mask is not None and len(axes) - 1 == len(mask.shape):
|
|
555
|
-
mask = mask[:, :, np.newaxis]
|
|
556
|
-
if 'x' not in axes or 'y' not in axes:
|
|
557
|
-
err = 'Invalid value for axes. Must contain "y" and "x".'
|
|
558
|
-
raise ValueError(err)
|
|
559
|
-
for k in placement:
|
|
560
|
-
if k not in axes:
|
|
561
|
-
axes[0:0] = [k]
|
|
572
|
+
tile, mask, placement, axes = self._validateNewTile(tile, mask, placement, axes)
|
|
573
|
+
|
|
562
574
|
with self._addLock:
|
|
563
575
|
self._axes = {k: i for i, k in enumerate(axes)}
|
|
564
|
-
while len(tile.shape) < len(axes):
|
|
565
|
-
tile = np.expand_dims(tile, axis=0)
|
|
566
|
-
while mask is not None and len(mask.shape) < len(axes):
|
|
567
|
-
mask = np.expand_dims(mask, axis=0)
|
|
568
|
-
|
|
569
576
|
new_dims = {
|
|
570
577
|
a: max(
|
|
571
|
-
self._dims.get(a, 0),
|
|
578
|
+
self._dims.get(store_path, {}).get(a, 0),
|
|
572
579
|
placement.get(a, 0) + tile.shape[i],
|
|
573
580
|
)
|
|
574
581
|
for a, i in self._axes.items()
|
|
575
582
|
}
|
|
583
|
+
self._dims[store_path] = new_dims
|
|
576
584
|
placement_slices = tuple([
|
|
577
585
|
slice(placement.get(a, 0), placement.get(a, 0) + tile.shape[i], 1)
|
|
578
586
|
for i, a in enumerate(axes)
|
|
579
587
|
])
|
|
580
588
|
|
|
581
589
|
current_arrays = dict(self._zarr.arrays())
|
|
590
|
+
if store_path == '0':
|
|
591
|
+
# if writing to base data, invalidate generated levels
|
|
592
|
+
for path in current_arrays:
|
|
593
|
+
if path != store_path:
|
|
594
|
+
self._zarr_store.rmdir(path)
|
|
582
595
|
chunking = None
|
|
583
|
-
if
|
|
584
|
-
|
|
596
|
+
if store_path not in current_arrays:
|
|
597
|
+
arr = np.empty(tuple(new_dims.values()), dtype=tile.dtype)
|
|
585
598
|
chunking = tuple([
|
|
586
599
|
self._tileSize if a in ['x', 'y'] else
|
|
587
600
|
new_dims.get('s') if a == 's' else 1
|
|
588
601
|
for a in axes
|
|
589
602
|
])
|
|
590
603
|
else:
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
if
|
|
604
|
+
arr = current_arrays[store_path]
|
|
605
|
+
arr.resize(*tuple(new_dims.values()))
|
|
606
|
+
if arr.chunks[-1] != new_dims.get('s'):
|
|
594
607
|
# rechunk if length of samples axis changes
|
|
595
608
|
chunking = tuple([
|
|
596
609
|
self._tileSize if a in ['x', 'y'] else
|
|
@@ -599,40 +612,45 @@ class ZarrFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
|
599
612
|
])
|
|
600
613
|
|
|
601
614
|
if mask is not None:
|
|
602
|
-
|
|
615
|
+
arr[placement_slices] = np.where(mask, tile, arr[placement_slices])
|
|
603
616
|
else:
|
|
604
|
-
|
|
617
|
+
arr[placement_slices] = tile
|
|
605
618
|
if chunking:
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
619
|
+
zarr.array(
|
|
620
|
+
arr,
|
|
621
|
+
chunks=chunking,
|
|
622
|
+
overwrite=True,
|
|
623
|
+
store=self._zarr_store,
|
|
624
|
+
path=store_path,
|
|
625
|
+
)
|
|
626
|
+
|
|
627
|
+
# If base data changed, update large_image attributes and OME metadata
|
|
628
|
+
if store_path == '0':
|
|
629
|
+
self._zarr.attrs.update({
|
|
630
|
+
'multiscales': [{
|
|
631
|
+
'version': '0.5-dev',
|
|
632
|
+
'axes': [{
|
|
633
|
+
'name': a,
|
|
634
|
+
'type': 'space' if a in ['x', 'y'] else 'other',
|
|
635
|
+
} for a in axes],
|
|
636
|
+
'datasets': [{'path': 0}],
|
|
637
|
+
}],
|
|
638
|
+
'omero': {'version': '0.5-dev'},
|
|
639
|
+
})
|
|
640
|
+
|
|
641
|
+
self._dtype = tile.dtype
|
|
642
|
+
self._bandCount = new_dims.get(axes[-1]) # last axis is assumed to be bands
|
|
643
|
+
self.sizeX = new_dims.get('x')
|
|
644
|
+
self.sizeY = new_dims.get('y')
|
|
645
|
+
self._framecount = np.prod([
|
|
646
|
+
length
|
|
647
|
+
for axis, length in new_dims.items()
|
|
648
|
+
if axis in axes[:-3]
|
|
649
|
+
])
|
|
650
|
+
self._cacheValue = str(uuid.uuid4())
|
|
651
|
+
self._levels = None
|
|
652
|
+
self.levels = int(max(1, math.ceil(math.log(max(
|
|
653
|
+
self.sizeX / self.tileWidth, self.sizeY / self.tileHeight)) / math.log(2)) + 1))
|
|
636
654
|
|
|
637
655
|
@property
|
|
638
656
|
def crop(self):
|
|
@@ -659,12 +677,75 @@ class ZarrFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
|
659
677
|
raise TileSourceError(msg)
|
|
660
678
|
self._crop = (x, y, w, h)
|
|
661
679
|
|
|
680
|
+
def _generateDownsampledLevels(self, resample_method):
|
|
681
|
+
self._checkEditable()
|
|
682
|
+
current_arrays = dict(self._zarr.arrays())
|
|
683
|
+
if '0' not in current_arrays:
|
|
684
|
+
msg = 'No root data found, cannot generate lower resolution levels.'
|
|
685
|
+
raise TileSourceError(msg)
|
|
686
|
+
if 'x' not in self._axes or 'y' not in self._axes:
|
|
687
|
+
msg = 'Data must have an X axis and Y axis to generate lower resolution levels.'
|
|
688
|
+
raise TileSourceError(msg)
|
|
689
|
+
|
|
690
|
+
metadata = self.getMetadata()
|
|
691
|
+
|
|
692
|
+
if (
|
|
693
|
+
resample_method.value < ResampleMethod.PIL_MAX_ENUM.value and
|
|
694
|
+
resample_method != ResampleMethod.PIL_NEAREST
|
|
695
|
+
):
|
|
696
|
+
tile_overlap = dict(x=4, y=4, edges=True)
|
|
697
|
+
else:
|
|
698
|
+
tile_overlap = dict(x=0, y=0)
|
|
699
|
+
tile_size = dict(
|
|
700
|
+
width=4096 + tile_overlap['x'],
|
|
701
|
+
height=4096 + tile_overlap['y'],
|
|
702
|
+
)
|
|
703
|
+
for level in range(1, self.levels):
|
|
704
|
+
scale_factor = 2 ** level
|
|
705
|
+
iterator_output = dict(
|
|
706
|
+
maxWidth=self.sizeX // scale_factor,
|
|
707
|
+
maxHeight=self.sizeY // scale_factor,
|
|
708
|
+
)
|
|
709
|
+
for frame in metadata.get('frames', [{'Index': 0}]):
|
|
710
|
+
frame_position = {
|
|
711
|
+
k.replace('Index', '').lower(): v
|
|
712
|
+
for k, v in frame.items()
|
|
713
|
+
if k.replace('Index', '').lower() in self._axes
|
|
714
|
+
}
|
|
715
|
+
for tile in self.tileIterator(
|
|
716
|
+
tile_size=tile_size,
|
|
717
|
+
tile_overlap=tile_overlap,
|
|
718
|
+
frame=frame['Index'],
|
|
719
|
+
output=iterator_output,
|
|
720
|
+
resample=False, # TODO: incorporate resampling in core
|
|
721
|
+
):
|
|
722
|
+
new_tile = downsampleTileHalfRes(tile['tile'], resample_method)
|
|
723
|
+
overlap = {k: int(v / 2) for k, v in tile['tile_overlap'].items()}
|
|
724
|
+
new_tile = new_tile[
|
|
725
|
+
slice(overlap['top'], new_tile.shape[0] - overlap['bottom']),
|
|
726
|
+
slice(overlap['left'], new_tile.shape[1] - overlap['right']),
|
|
727
|
+
]
|
|
728
|
+
|
|
729
|
+
x = int(tile['x'] / 2 + overlap['left'])
|
|
730
|
+
y = int(tile['y'] / 2 + overlap['top'])
|
|
731
|
+
|
|
732
|
+
self.addTile(
|
|
733
|
+
new_tile,
|
|
734
|
+
x=x,
|
|
735
|
+
y=y,
|
|
736
|
+
**frame_position,
|
|
737
|
+
axes=list(self._axes.keys()),
|
|
738
|
+
level=level,
|
|
739
|
+
)
|
|
740
|
+
self._validateZarr() # refresh self._levels before continuing
|
|
741
|
+
|
|
662
742
|
def write(
|
|
663
743
|
self,
|
|
664
744
|
path,
|
|
665
745
|
lossy=True,
|
|
666
746
|
alpha=True,
|
|
667
747
|
overwriteAllowed=True,
|
|
748
|
+
resample=None,
|
|
668
749
|
):
|
|
669
750
|
"""
|
|
670
751
|
Output the current image to a file.
|
|
@@ -684,53 +765,58 @@ class ZarrFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
|
684
765
|
else:
|
|
685
766
|
raise TileSourceError('Output path exists (%s).' % str(path))
|
|
686
767
|
|
|
687
|
-
# TODO: compute half, quarter, etc. resolutions
|
|
688
|
-
self._validateZarr()
|
|
689
768
|
suffix = Path(path).suffix
|
|
690
|
-
|
|
691
|
-
data_store = self._zarr_store
|
|
769
|
+
source = self
|
|
692
770
|
|
|
693
771
|
if self.crop:
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
772
|
+
top, left, height, width = self.crop
|
|
773
|
+
source = new()
|
|
774
|
+
source._zarr.attrs.update(self._zarr.attrs)
|
|
775
|
+
for frame in self.getMetadata().get('frames', [{'Index': 0}]):
|
|
776
|
+
frame_position = {
|
|
777
|
+
k.replace('Index', '').lower(): v
|
|
778
|
+
for k, v in frame.items()
|
|
779
|
+
if k.replace('Index', '').lower() in self._axes
|
|
780
|
+
}
|
|
781
|
+
for tile in self.tileIterator(
|
|
782
|
+
frame=frame['Index'],
|
|
783
|
+
region=dict(top=top, left=left, width=width, height=height),
|
|
784
|
+
resample=False,
|
|
785
|
+
):
|
|
786
|
+
source.addTile(
|
|
787
|
+
tile['tile'],
|
|
788
|
+
x=tile['x'] - left,
|
|
789
|
+
y=tile['y'] - top,
|
|
790
|
+
axes=list(self._axes.keys()),
|
|
791
|
+
**frame_position,
|
|
792
|
+
)
|
|
793
|
+
|
|
794
|
+
if suffix in ['.zarr', '.db', '.sqlite', '.zip']:
|
|
795
|
+
if resample is None:
|
|
796
|
+
resample = (
|
|
797
|
+
ResampleMethod.PIL_LANCZOS
|
|
798
|
+
if lossy and source.dtype == np.uint8
|
|
799
|
+
else ResampleMethod.NP_NEAREST
|
|
708
800
|
)
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
zip_store = zarr.ZipStore(path)
|
|
722
|
-
zarr.copy_store(data_store, zip_store, if_exists='replace')
|
|
723
|
-
zip_store.close()
|
|
801
|
+
source._generateDownsampledLevels(resample)
|
|
802
|
+
|
|
803
|
+
if suffix == '.zarr':
|
|
804
|
+
shutil.copytree(source._tempdir.name, path)
|
|
805
|
+
elif suffix in ['.db', '.sqlite']:
|
|
806
|
+
sqlite_store = zarr.SQLiteStore(path)
|
|
807
|
+
zarr.copy_store(source._zarr_store, sqlite_store, if_exists='replace')
|
|
808
|
+
sqlite_store.close()
|
|
809
|
+
elif suffix == '.zip':
|
|
810
|
+
zip_store = zarr.ZipStore(path)
|
|
811
|
+
zarr.copy_store(source._zarr_store, zip_store, if_exists='replace')
|
|
812
|
+
zip_store.close()
|
|
724
813
|
|
|
725
814
|
else:
|
|
726
815
|
from large_image_converter import convert
|
|
727
816
|
|
|
728
|
-
attrs_path = Path(
|
|
817
|
+
attrs_path = Path(source._tempdir.name) / '.zattrs'
|
|
729
818
|
convert(str(attrs_path), path, overwrite=overwriteAllowed)
|
|
730
819
|
|
|
731
|
-
if self.crop:
|
|
732
|
-
shutil.rmtree(data_dir.name)
|
|
733
|
-
|
|
734
820
|
|
|
735
821
|
def open(*args, **kwargs):
|
|
736
822
|
"""
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{large-image-source-zarr-1.27.5.dev69 → large-image-source-zarr-1.27.5.dev99}/pyproject.toml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|