large-image-source-zarr 1.29.1.dev6__tar.gz → 1.29.1.dev16__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.29.1.dev6 → large-image-source-zarr-1.29.1.dev16}/PKG-INFO +3 -3
- {large-image-source-zarr-1.29.1.dev6 → large-image-source-zarr-1.29.1.dev16}/large_image_source_zarr/__init__.py +182 -27
- {large-image-source-zarr-1.29.1.dev6 → large-image-source-zarr-1.29.1.dev16}/large_image_source_zarr.egg-info/PKG-INFO +3 -3
- {large-image-source-zarr-1.29.1.dev6 → large-image-source-zarr-1.29.1.dev16}/large_image_source_zarr.egg-info/requires.txt +2 -2
- {large-image-source-zarr-1.29.1.dev6 → large-image-source-zarr-1.29.1.dev16}/LICENSE +0 -0
- {large-image-source-zarr-1.29.1.dev6 → large-image-source-zarr-1.29.1.dev16}/README.rst +0 -0
- {large-image-source-zarr-1.29.1.dev6 → large-image-source-zarr-1.29.1.dev16}/large_image_source_zarr/girder_source.py +0 -0
- {large-image-source-zarr-1.29.1.dev6 → large-image-source-zarr-1.29.1.dev16}/large_image_source_zarr.egg-info/SOURCES.txt +0 -0
- {large-image-source-zarr-1.29.1.dev6 → large-image-source-zarr-1.29.1.dev16}/large_image_source_zarr.egg-info/dependency_links.txt +0 -0
- {large-image-source-zarr-1.29.1.dev6 → large-image-source-zarr-1.29.1.dev16}/large_image_source_zarr.egg-info/entry_points.txt +0 -0
- {large-image-source-zarr-1.29.1.dev6 → large-image-source-zarr-1.29.1.dev16}/large_image_source_zarr.egg-info/top_level.txt +0 -0
- {large-image-source-zarr-1.29.1.dev6 → large-image-source-zarr-1.29.1.dev16}/pyproject.toml +0 -0
- {large-image-source-zarr-1.29.1.dev6 → large-image-source-zarr-1.29.1.dev16}/setup.cfg +0 -0
- {large-image-source-zarr-1.29.1.dev6 → large-image-source-zarr-1.29.1.dev16}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: large-image-source-zarr
|
|
3
|
-
Version: 1.29.1.
|
|
3
|
+
Version: 1.29.1.dev16
|
|
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.
|
|
@@ -17,11 +17,11 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.12
|
|
18
18
|
Requires-Python: >=3.6
|
|
19
19
|
License-File: LICENSE
|
|
20
|
-
Requires-Dist: large-image>=1.29.1.
|
|
20
|
+
Requires-Dist: large-image>=1.29.1.dev16
|
|
21
21
|
Requires-Dist: zarr
|
|
22
22
|
Requires-Dist: imagecodecs-numcodecs
|
|
23
23
|
Provides-Extra: girder
|
|
24
|
-
Requires-Dist: girder-large-image>=1.29.1.
|
|
24
|
+
Requires-Dist: girder-large-image>=1.29.1.dev16; extra == "girder"
|
|
25
25
|
Provides-Extra: all
|
|
26
26
|
Requires-Dist: large-image-converter; extra == "all"
|
|
27
27
|
|
|
@@ -79,6 +79,7 @@ class ZarrFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
|
79
79
|
def _initOpen(self, **kwargs):
|
|
80
80
|
self._largeImagePath = str(self._getLargeImagePath())
|
|
81
81
|
self._zarr = None
|
|
82
|
+
self._editable = False
|
|
82
83
|
if not os.path.isfile(self._largeImagePath) and '//:' not in self._largeImagePath:
|
|
83
84
|
raise TileSourceFileNotFoundError(self._largeImagePath) from None
|
|
84
85
|
try:
|
|
@@ -126,7 +127,11 @@ class ZarrFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
|
126
127
|
self._framecount = 0
|
|
127
128
|
self._mm_x = 0
|
|
128
129
|
self._mm_y = 0
|
|
130
|
+
self._channelNames = []
|
|
131
|
+
self._channelColors = []
|
|
132
|
+
self._imageDescription = None
|
|
129
133
|
self._levels = []
|
|
134
|
+
self._associatedImages = {}
|
|
130
135
|
|
|
131
136
|
def __del__(self):
|
|
132
137
|
if not hasattr(self, '_derivedSource'):
|
|
@@ -312,6 +317,8 @@ class ZarrFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
|
312
317
|
Validate that we can read tiles from the zarr parent group in
|
|
313
318
|
self._zarr. Set up the appropriate class variables.
|
|
314
319
|
"""
|
|
320
|
+
if self._editable:
|
|
321
|
+
self._writeInternalMetadata()
|
|
315
322
|
found = self._scanZarrGroup(self._zarr)
|
|
316
323
|
if found['best'] is None:
|
|
317
324
|
msg = 'No data array that can be used.'
|
|
@@ -324,8 +331,10 @@ class ZarrFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
|
324
331
|
msg = 'Conflicting xy axis data.'
|
|
325
332
|
raise TileSourceError(msg)
|
|
326
333
|
self._channels = found['channels']
|
|
327
|
-
self._associatedImages =
|
|
328
|
-
(
|
|
334
|
+
self._associatedImages = {
|
|
335
|
+
g.name.replace('/', ''): (g, a)
|
|
336
|
+
for g, a in found['associated'] if not any(g is gb for gb, _ in self._series)
|
|
337
|
+
}
|
|
329
338
|
self.sizeX = baseArray.shape[self._axes['x']]
|
|
330
339
|
self.sizeY = baseArray.shape[self._axes['y']]
|
|
331
340
|
self.tileWidth = (
|
|
@@ -429,6 +438,8 @@ class ZarrFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
|
429
438
|
|
|
430
439
|
:returns: a dictionary of data or None.
|
|
431
440
|
"""
|
|
441
|
+
if self._editable:
|
|
442
|
+
self._writeInternalMetadata()
|
|
432
443
|
result = {}
|
|
433
444
|
result['zarr'] = {
|
|
434
445
|
'base': self._zarr.attrs.asdict(),
|
|
@@ -445,7 +456,7 @@ class ZarrFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
|
445
456
|
|
|
446
457
|
:return: the list of image keys.
|
|
447
458
|
"""
|
|
448
|
-
return
|
|
459
|
+
return list(self._associatedImages.keys())
|
|
449
460
|
|
|
450
461
|
def _getAssociatedImage(self, imageKey):
|
|
451
462
|
"""
|
|
@@ -454,15 +465,9 @@ class ZarrFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
|
454
465
|
:param imageKey: the key of the associated image.
|
|
455
466
|
:return: the image in PIL format or None.
|
|
456
467
|
"""
|
|
457
|
-
if not
|
|
458
|
-
return
|
|
459
|
-
try:
|
|
460
|
-
idx = int(imageKey[6:])
|
|
461
|
-
except Exception:
|
|
462
|
-
return
|
|
463
|
-
if idx < 0 or idx >= len(self._associatedImages):
|
|
468
|
+
if imageKey not in self._associatedImages:
|
|
464
469
|
return
|
|
465
|
-
group, arr = self._associatedImages[
|
|
470
|
+
group, arr = self._associatedImages[imageKey]
|
|
466
471
|
axes = self._getGeneralAxes(arr)
|
|
467
472
|
trans = [idx for idx in range(len(arr.shape))
|
|
468
473
|
if idx not in axes.values()] + [axes['y'], axes['x']]
|
|
@@ -565,8 +570,6 @@ class ZarrFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
|
565
570
|
:param kwargs: start locations for any additional axes. Note that
|
|
566
571
|
``level`` is a reserved word and not permitted for an axis name.
|
|
567
572
|
"""
|
|
568
|
-
# TODO: improve band bookkeeping
|
|
569
|
-
|
|
570
573
|
self._checkEditable()
|
|
571
574
|
store_path = str(kwargs.pop('level', 0))
|
|
572
575
|
placement = {
|
|
@@ -629,20 +632,8 @@ class ZarrFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
|
629
632
|
path=store_path,
|
|
630
633
|
)
|
|
631
634
|
|
|
632
|
-
# If base data changed, update large_image attributes
|
|
635
|
+
# If base data changed, update large_image attributes
|
|
633
636
|
if store_path == '0':
|
|
634
|
-
self._zarr.attrs.update({
|
|
635
|
-
'multiscales': [{
|
|
636
|
-
'version': '0.5-dev',
|
|
637
|
-
'axes': [{
|
|
638
|
-
'name': a,
|
|
639
|
-
'type': 'space' if a in ['x', 'y'] else 'other',
|
|
640
|
-
} for a in axes],
|
|
641
|
-
'datasets': [{'path': 0}],
|
|
642
|
-
}],
|
|
643
|
-
'omero': {'version': '0.5-dev'},
|
|
644
|
-
})
|
|
645
|
-
|
|
646
637
|
self._dtype = tile.dtype
|
|
647
638
|
self._bandCount = new_dims.get(axes[-1]) # last axis is assumed to be bands
|
|
648
639
|
self.sizeX = new_dims.get('x')
|
|
@@ -657,6 +648,113 @@ class ZarrFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
|
657
648
|
self.levels = int(max(1, math.ceil(math.log(max(
|
|
658
649
|
self.sizeX / self.tileWidth, self.sizeY / self.tileHeight)) / math.log(2)) + 1))
|
|
659
650
|
|
|
651
|
+
def addAssociatedImage(self, image, imageKey=None):
|
|
652
|
+
"""
|
|
653
|
+
Add an associated image to this source.
|
|
654
|
+
|
|
655
|
+
:param image: a numpy array, PIL Image, or a binary string
|
|
656
|
+
with an image. The numpy array can have 2 or 3 dimensions.
|
|
657
|
+
"""
|
|
658
|
+
data, _ = _imageToNumpy(image)
|
|
659
|
+
with self._addLock:
|
|
660
|
+
if imageKey is None:
|
|
661
|
+
# Each associated image should be in its own group
|
|
662
|
+
num_existing = len(self.getAssociatedImagesList())
|
|
663
|
+
imageKey = f'image_{num_existing + 1}'
|
|
664
|
+
group = self._zarr.require_group(imageKey)
|
|
665
|
+
arr = zarr.array(
|
|
666
|
+
data,
|
|
667
|
+
store=self._zarr_store,
|
|
668
|
+
path=f'{imageKey}/image',
|
|
669
|
+
)
|
|
670
|
+
self._associatedImages[imageKey] = (group, arr)
|
|
671
|
+
|
|
672
|
+
def _writeInternalMetadata(self):
|
|
673
|
+
self._checkEditable()
|
|
674
|
+
with self._addLock:
|
|
675
|
+
name = str(self._tempdir.name).split('/')[-1]
|
|
676
|
+
arrays = dict(self._zarr.arrays())
|
|
677
|
+
channel_axis = self._axes.get('s') or self._axes.get('c')
|
|
678
|
+
datasets = []
|
|
679
|
+
axes = []
|
|
680
|
+
channels = []
|
|
681
|
+
rdefs = {'model': 'color' if len(self._channelColors) else 'greyscale'}
|
|
682
|
+
sorted_axes = [a[0] for a in sorted(self._axes.items(), key=lambda item: item[1])]
|
|
683
|
+
for arr_name in arrays:
|
|
684
|
+
level = int(arr_name)
|
|
685
|
+
scale = [1.0 for a in sorted_axes]
|
|
686
|
+
scale[self._axes.get('x')] = self._mm_x * (2 ** level)
|
|
687
|
+
scale[self._axes.get('y')] = self._mm_y * (2 ** level)
|
|
688
|
+
dataset_metadata = {
|
|
689
|
+
'path': arr_name,
|
|
690
|
+
'coordinateTransformations': [{
|
|
691
|
+
'type': 'scale',
|
|
692
|
+
'scale': scale,
|
|
693
|
+
}],
|
|
694
|
+
}
|
|
695
|
+
datasets.append(dataset_metadata)
|
|
696
|
+
for a in sorted_axes:
|
|
697
|
+
axis_metadata = {'name': a}
|
|
698
|
+
if a in ['x', 'y']:
|
|
699
|
+
axis_metadata['type'] = 'space'
|
|
700
|
+
axis_metadata['unit'] = 'millimeter'
|
|
701
|
+
elif a in ['s', 'c']:
|
|
702
|
+
axis_metadata['type'] = 'channel'
|
|
703
|
+
elif a == 't':
|
|
704
|
+
rdefs['defaultT'] = 0
|
|
705
|
+
elif a == 'z':
|
|
706
|
+
rdefs['defaultZ'] = 0
|
|
707
|
+
axes.append(axis_metadata)
|
|
708
|
+
if channel_axis and len(arrays) > 0:
|
|
709
|
+
base_array = list(arrays.values())[0]
|
|
710
|
+
base_shape = base_array.shape
|
|
711
|
+
for c in range(base_shape[channel_axis]):
|
|
712
|
+
channel_metadata = {
|
|
713
|
+
'active': True,
|
|
714
|
+
'coefficient': 1,
|
|
715
|
+
'color': 'FFFFFF',
|
|
716
|
+
'family': 'linear',
|
|
717
|
+
'inverted': False,
|
|
718
|
+
'label': f'Band {c + 1}',
|
|
719
|
+
}
|
|
720
|
+
channel_data = base_array[..., c]
|
|
721
|
+
channel_min = np.min(channel_data)
|
|
722
|
+
channel_max = np.max(channel_data)
|
|
723
|
+
channel_metadata['window'] = {
|
|
724
|
+
'end': channel_max,
|
|
725
|
+
'max': channel_max,
|
|
726
|
+
'min': channel_min,
|
|
727
|
+
'start': channel_min,
|
|
728
|
+
}
|
|
729
|
+
if len(self._channelNames) > c:
|
|
730
|
+
channel_metadata['label'] = self._channelNames[c]
|
|
731
|
+
if len(self._channelColors) > c:
|
|
732
|
+
channel_metadata['color'] = self._channelColors[c]
|
|
733
|
+
channels.append(channel_metadata)
|
|
734
|
+
# Guidelines from https://ngff.openmicroscopy.org/latest/
|
|
735
|
+
self._zarr.attrs.update({
|
|
736
|
+
'multiscales': [{
|
|
737
|
+
'version': '0.5',
|
|
738
|
+
'name': name,
|
|
739
|
+
'axes': axes,
|
|
740
|
+
'datasets': datasets,
|
|
741
|
+
'metadata': {
|
|
742
|
+
'description': self._imageDescription or '',
|
|
743
|
+
'kwargs': {
|
|
744
|
+
'multichannel': (channel_axis is not None),
|
|
745
|
+
},
|
|
746
|
+
},
|
|
747
|
+
}],
|
|
748
|
+
'omero': {
|
|
749
|
+
'id': 1,
|
|
750
|
+
'version': '0.5',
|
|
751
|
+
'name': name,
|
|
752
|
+
'channels': channels,
|
|
753
|
+
'rdefs': rdefs,
|
|
754
|
+
},
|
|
755
|
+
'bioformats2raw.layout': 3,
|
|
756
|
+
})
|
|
757
|
+
|
|
660
758
|
@property
|
|
661
759
|
def crop(self):
|
|
662
760
|
"""
|
|
@@ -682,6 +780,59 @@ class ZarrFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
|
682
780
|
raise TileSourceError(msg)
|
|
683
781
|
self._crop = (x, y, w, h)
|
|
684
782
|
|
|
783
|
+
@property
|
|
784
|
+
def mm_x(self):
|
|
785
|
+
return self._mm_x
|
|
786
|
+
|
|
787
|
+
@mm_x.setter
|
|
788
|
+
def mm_x(self, value):
|
|
789
|
+
self._checkEditable()
|
|
790
|
+
value = float(value) if value is not None else None
|
|
791
|
+
if value is not None and value <= 0:
|
|
792
|
+
msg = 'mm_x must be positive or None'
|
|
793
|
+
raise TileSourceError(msg)
|
|
794
|
+
self._mm_x = value
|
|
795
|
+
|
|
796
|
+
@property
|
|
797
|
+
def mm_y(self):
|
|
798
|
+
return self._mm_y
|
|
799
|
+
|
|
800
|
+
@mm_y.setter
|
|
801
|
+
def mm_y(self, value):
|
|
802
|
+
self._checkEditable()
|
|
803
|
+
value = float(value) if value is not None else None
|
|
804
|
+
if value is not None and value <= 0:
|
|
805
|
+
msg = 'mm_y must be positive or None'
|
|
806
|
+
raise TileSourceError(msg)
|
|
807
|
+
self._mm_y = value
|
|
808
|
+
|
|
809
|
+
@property
|
|
810
|
+
def imageDescription(self):
|
|
811
|
+
return self._imageDescription
|
|
812
|
+
|
|
813
|
+
@imageDescription.setter
|
|
814
|
+
def imageDescription(self, description):
|
|
815
|
+
self._checkEditable()
|
|
816
|
+
self._imageDescription = description
|
|
817
|
+
|
|
818
|
+
@property
|
|
819
|
+
def channelNames(self):
|
|
820
|
+
return self._channelNames
|
|
821
|
+
|
|
822
|
+
@channelNames.setter
|
|
823
|
+
def channelNames(self, names):
|
|
824
|
+
self._checkEditable()
|
|
825
|
+
self._channelNames = names
|
|
826
|
+
|
|
827
|
+
@property
|
|
828
|
+
def channelColors(self):
|
|
829
|
+
return self._channelColors
|
|
830
|
+
|
|
831
|
+
@channelColors.setter
|
|
832
|
+
def channelColors(self, colors):
|
|
833
|
+
self._checkEditable()
|
|
834
|
+
self._channelColors = colors
|
|
835
|
+
|
|
685
836
|
def _generateDownsampledLevels(self, resample_method):
|
|
686
837
|
self._checkEditable()
|
|
687
838
|
current_arrays = dict(self._zarr.arrays())
|
|
@@ -705,6 +856,7 @@ class ZarrFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
|
705
856
|
width=4096 + tile_overlap['x'],
|
|
706
857
|
height=4096 + tile_overlap['y'],
|
|
707
858
|
)
|
|
859
|
+
sorted_axes = [a[0] for a in sorted(self._axes.items(), key=lambda item: item[1])]
|
|
708
860
|
for level in range(1, self.levels):
|
|
709
861
|
scale_factor = 2 ** level
|
|
710
862
|
iterator_output = dict(
|
|
@@ -739,7 +891,7 @@ class ZarrFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
|
739
891
|
x=x,
|
|
740
892
|
y=y,
|
|
741
893
|
**frame_position,
|
|
742
|
-
axes=
|
|
894
|
+
axes=sorted_axes,
|
|
743
895
|
level=level,
|
|
744
896
|
)
|
|
745
897
|
self._validateZarr() # refresh self._levels before continuing
|
|
@@ -799,6 +951,8 @@ class ZarrFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
|
799
951
|
**frame_position,
|
|
800
952
|
)
|
|
801
953
|
|
|
954
|
+
source._writeInternalMetadata()
|
|
955
|
+
|
|
802
956
|
if suffix in ['.zarr', '.db', '.sqlite', '.zip']:
|
|
803
957
|
if resample is None:
|
|
804
958
|
resample = (
|
|
@@ -807,6 +961,7 @@ class ZarrFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
|
807
961
|
else ResampleMethod.NP_NEAREST
|
|
808
962
|
)
|
|
809
963
|
source._generateDownsampledLevels(resample)
|
|
964
|
+
source._writeInternalMetadata() # rewrite with new level datasets
|
|
810
965
|
|
|
811
966
|
if suffix == '.zarr':
|
|
812
967
|
shutil.copytree(source._tempdir.name, path)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: large-image-source-zarr
|
|
3
|
-
Version: 1.29.1.
|
|
3
|
+
Version: 1.29.1.dev16
|
|
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.
|
|
@@ -17,11 +17,11 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.12
|
|
18
18
|
Requires-Python: >=3.6
|
|
19
19
|
License-File: LICENSE
|
|
20
|
-
Requires-Dist: large-image>=1.29.1.
|
|
20
|
+
Requires-Dist: large-image>=1.29.1.dev16
|
|
21
21
|
Requires-Dist: zarr
|
|
22
22
|
Requires-Dist: imagecodecs-numcodecs
|
|
23
23
|
Provides-Extra: girder
|
|
24
|
-
Requires-Dist: girder-large-image>=1.29.1.
|
|
24
|
+
Requires-Dist: girder-large-image>=1.29.1.dev16; extra == "girder"
|
|
25
25
|
Provides-Extra: all
|
|
26
26
|
Requires-Dist: large-image-converter; extra == "all"
|
|
27
27
|
|
|
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
|
|
File without changes
|