ngio 0.5.0__py3-none-any.whl → 0.5.0a2__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.
- ngio/__init__.py +2 -5
- ngio/common/__init__.py +6 -11
- ngio/common/_masking_roi.py +54 -34
- ngio/common/_pyramid.py +85 -309
- ngio/common/_roi.py +330 -258
- ngio/experimental/iterators/_feature.py +3 -3
- ngio/experimental/iterators/_rois_utils.py +11 -10
- ngio/hcs/_plate.py +60 -132
- ngio/images/_abstract_image.py +35 -539
- ngio/images/_create.py +287 -0
- ngio/images/_create_synt_container.py +42 -39
- ngio/images/_image.py +250 -516
- ngio/images/_label.py +172 -249
- ngio/images/_masked_image.py +2 -2
- ngio/images/_ome_zarr_container.py +241 -644
- ngio/io_pipes/_io_pipes.py +9 -9
- ngio/io_pipes/_io_pipes_masked.py +7 -7
- ngio/io_pipes/_io_pipes_roi.py +6 -6
- ngio/io_pipes/_io_pipes_types.py +3 -3
- ngio/io_pipes/_match_shape.py +8 -6
- ngio/io_pipes/_ops_slices_utils.py +5 -8
- ngio/ome_zarr_meta/__init__.py +18 -29
- ngio/ome_zarr_meta/_meta_handlers.py +708 -392
- ngio/ome_zarr_meta/ngio_specs/__init__.py +0 -4
- ngio/ome_zarr_meta/ngio_specs/_axes.py +51 -152
- ngio/ome_zarr_meta/ngio_specs/_dataset.py +22 -13
- ngio/ome_zarr_meta/ngio_specs/_ngio_hcs.py +91 -129
- ngio/ome_zarr_meta/ngio_specs/_ngio_image.py +68 -57
- ngio/ome_zarr_meta/v04/__init__.py +1 -5
- ngio/ome_zarr_meta/v04/{_v04_spec.py → _v04_spec_utils.py} +85 -54
- ngio/ome_zarr_meta/v05/__init__.py +1 -5
- ngio/ome_zarr_meta/v05/{_v05_spec.py → _v05_spec_utils.py} +87 -64
- ngio/resources/__init__.py +1 -1
- ngio/resources/resource_model.py +1 -1
- ngio/tables/_tables_container.py +11 -62
- ngio/tables/backends/_anndata.py +8 -58
- ngio/tables/backends/_anndata_utils.py +6 -1
- ngio/tables/backends/_csv.py +19 -3
- ngio/tables/backends/_json.py +13 -10
- ngio/tables/backends/_non_zarr_backends.py +196 -0
- ngio/tables/backends/_parquet.py +31 -3
- ngio/tables/v1/_roi_table.py +24 -41
- ngio/utils/__init__.py +12 -6
- ngio/utils/_datasets.py +0 -6
- ngio/utils/_logger.py +50 -0
- ngio/utils/_zarr_utils.py +58 -167
- {ngio-0.5.0.dist-info → ngio-0.5.0a2.dist-info}/METADATA +4 -11
- ngio-0.5.0a2.dist-info/RECORD +89 -0
- {ngio-0.5.0.dist-info → ngio-0.5.0a2.dist-info}/WHEEL +1 -1
- ngio/images/_create_utils.py +0 -406
- ngio/tables/backends/_py_arrow_backends.py +0 -222
- ngio-0.5.0.dist-info/RECORD +0 -88
- {ngio-0.5.0.dist-info → ngio-0.5.0a2.dist-info}/licenses/LICENSE +0 -0
ngio/images/_abstract_image.py
CHANGED
|
@@ -1,32 +1,19 @@
|
|
|
1
1
|
"""Generic class to handle Image-like data in a OME-NGFF file."""
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
from
|
|
5
|
-
from collections.abc import Mapping, Sequence
|
|
6
|
-
from typing import Any, Literal
|
|
3
|
+
from collections.abc import Sequence
|
|
4
|
+
from typing import Generic, Literal, TypeVar
|
|
7
5
|
|
|
8
6
|
import dask.array as da
|
|
9
7
|
import numpy as np
|
|
10
8
|
import zarr
|
|
11
|
-
from zarr.core.array import CompressorLike
|
|
12
9
|
|
|
13
10
|
from ngio.common import (
|
|
14
11
|
Dimensions,
|
|
15
12
|
InterpolationOrder,
|
|
16
13
|
Roi,
|
|
14
|
+
RoiPixels,
|
|
17
15
|
consolidate_pyramid,
|
|
18
16
|
)
|
|
19
|
-
from ngio.common._pyramid import (
|
|
20
|
-
ChunksLike,
|
|
21
|
-
ShardsLike,
|
|
22
|
-
compute_scales_from_shapes,
|
|
23
|
-
compute_shapes_from_scaling_factors,
|
|
24
|
-
)
|
|
25
|
-
from ngio.images._create_utils import (
|
|
26
|
-
_image_or_label_meta,
|
|
27
|
-
compute_base_scale,
|
|
28
|
-
init_image_like_from_shapes,
|
|
29
|
-
)
|
|
30
17
|
from ngio.io_pipes import (
|
|
31
18
|
DaskGetter,
|
|
32
19
|
DaskRoiGetter,
|
|
@@ -44,32 +31,15 @@ from ngio.ome_zarr_meta import (
|
|
|
44
31
|
Dataset,
|
|
45
32
|
ImageMetaHandler,
|
|
46
33
|
LabelMetaHandler,
|
|
47
|
-
NgioImageMeta,
|
|
48
34
|
PixelSize,
|
|
49
35
|
)
|
|
50
|
-
from ngio.ome_zarr_meta.ngio_specs import (
|
|
51
|
-
Channel,
|
|
52
|
-
NgffVersions,
|
|
53
|
-
NgioLabelMeta,
|
|
54
|
-
)
|
|
55
|
-
from ngio.ome_zarr_meta.ngio_specs._axes import (
|
|
56
|
-
AxesSetup,
|
|
57
|
-
DefaultSpaceUnit,
|
|
58
|
-
DefaultTimeUnit,
|
|
59
|
-
SpaceUnits,
|
|
60
|
-
TimeUnits,
|
|
61
|
-
)
|
|
62
36
|
from ngio.tables import RoiTable
|
|
63
|
-
from ngio.utils import
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
StoreOrGroup,
|
|
67
|
-
ZarrGroupHandler,
|
|
68
|
-
)
|
|
69
|
-
from ngio.utils._zarr_utils import find_dimension_separator
|
|
37
|
+
from ngio.utils import NgioFileExistsError, ZarrGroupHandler
|
|
38
|
+
|
|
39
|
+
_image_handler = TypeVar("_image_handler", ImageMetaHandler, LabelMetaHandler)
|
|
70
40
|
|
|
71
41
|
|
|
72
|
-
class AbstractImage(
|
|
42
|
+
class AbstractImage(Generic[_image_handler]):
|
|
73
43
|
"""A class to handle a single image (or level) in an OME-Zarr image.
|
|
74
44
|
|
|
75
45
|
This class is meant to be subclassed by specific image types.
|
|
@@ -79,7 +49,7 @@ class AbstractImage(ABC):
|
|
|
79
49
|
self,
|
|
80
50
|
group_handler: ZarrGroupHandler,
|
|
81
51
|
path: str,
|
|
82
|
-
meta_handler:
|
|
52
|
+
meta_handler: _image_handler,
|
|
83
53
|
) -> None:
|
|
84
54
|
"""Initialize the Image at a single level.
|
|
85
55
|
|
|
@@ -108,21 +78,14 @@ class AbstractImage(ABC):
|
|
|
108
78
|
return self._path
|
|
109
79
|
|
|
110
80
|
@property
|
|
111
|
-
|
|
112
|
-
def meta_handler(self) -> ImageMetaHandler | LabelMetaHandler:
|
|
81
|
+
def meta_handler(self) -> _image_handler:
|
|
113
82
|
"""Return the metadata."""
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
@property
|
|
117
|
-
@abstractmethod
|
|
118
|
-
def meta(self) -> NgioImageMeta | NgioLabelMeta:
|
|
119
|
-
"""Return the metadata."""
|
|
120
|
-
pass
|
|
83
|
+
return self._meta_handler
|
|
121
84
|
|
|
122
85
|
@property
|
|
123
86
|
def dataset(self) -> Dataset:
|
|
124
87
|
"""Return the dataset of the image."""
|
|
125
|
-
return self.meta_handler.
|
|
88
|
+
return self.meta_handler.meta.get_dataset(path=self.path)
|
|
126
89
|
|
|
127
90
|
@property
|
|
128
91
|
def dimensions(self) -> Dimensions:
|
|
@@ -143,11 +106,6 @@ class AbstractImage(ABC):
|
|
|
143
106
|
"""Return the axes handler of the image."""
|
|
144
107
|
return self.dataset.axes_handler
|
|
145
108
|
|
|
146
|
-
@property
|
|
147
|
-
def axes_setup(self) -> AxesSetup:
|
|
148
|
-
"""Return the axes setup of the image."""
|
|
149
|
-
return self.axes_handler.axes_setup
|
|
150
|
-
|
|
151
109
|
@property
|
|
152
110
|
def axes(self) -> tuple[str, ...]:
|
|
153
111
|
"""Return the axes of the image."""
|
|
@@ -217,47 +175,6 @@ class AbstractImage(ABC):
|
|
|
217
175
|
"""Return True if the image has the given axis."""
|
|
218
176
|
return self.axes_handler.has_axis(axis)
|
|
219
177
|
|
|
220
|
-
def set_axes_unit(
|
|
221
|
-
self,
|
|
222
|
-
space_unit: SpaceUnits = DefaultSpaceUnit,
|
|
223
|
-
time_unit: TimeUnits = DefaultTimeUnit,
|
|
224
|
-
) -> None:
|
|
225
|
-
"""Set the axes unit of the image.
|
|
226
|
-
|
|
227
|
-
Args:
|
|
228
|
-
space_unit (SpaceUnits): The space unit of the image.
|
|
229
|
-
time_unit (TimeUnits): The time unit of the image.
|
|
230
|
-
"""
|
|
231
|
-
meta = self._meta_handler.get_meta()
|
|
232
|
-
meta = meta.to_units(space_unit=space_unit, time_unit=time_unit)
|
|
233
|
-
self._meta_handler.update_meta(meta) # type: ignore
|
|
234
|
-
|
|
235
|
-
def set_axes_names(self, axes_names: Sequence[str]) -> None:
|
|
236
|
-
"""Set the axes names of the label.
|
|
237
|
-
|
|
238
|
-
Args:
|
|
239
|
-
axes_names (Sequence[str]): The axes names to set.
|
|
240
|
-
"""
|
|
241
|
-
meta = self._meta_handler.get_meta()
|
|
242
|
-
meta = meta.rename_axes(axes_names=axes_names)
|
|
243
|
-
self._meta_handler._axes_setup = meta.axes_handler.axes_setup
|
|
244
|
-
self._meta_handler.update_meta(meta) # type: ignore
|
|
245
|
-
|
|
246
|
-
def set_name(
|
|
247
|
-
self,
|
|
248
|
-
name: str,
|
|
249
|
-
) -> None:
|
|
250
|
-
"""Set the name of the image in the metadata.
|
|
251
|
-
|
|
252
|
-
This does not change the group name or any paths.
|
|
253
|
-
|
|
254
|
-
Args:
|
|
255
|
-
name (str): The name of the image.
|
|
256
|
-
"""
|
|
257
|
-
meta = self._meta_handler.get_meta()
|
|
258
|
-
meta = meta.rename_image(name=name)
|
|
259
|
-
self._meta_handler.update_meta(meta) # type: ignore
|
|
260
|
-
|
|
261
178
|
def _get_as_numpy(
|
|
262
179
|
self,
|
|
263
180
|
axes_order: Sequence[str] | None = None,
|
|
@@ -285,7 +202,7 @@ class AbstractImage(ABC):
|
|
|
285
202
|
|
|
286
203
|
def _get_roi_as_numpy(
|
|
287
204
|
self,
|
|
288
|
-
roi: Roi,
|
|
205
|
+
roi: Roi | RoiPixels,
|
|
289
206
|
axes_order: Sequence[str] | None = None,
|
|
290
207
|
transforms: Sequence[TransformProtocol] | None = None,
|
|
291
208
|
**slicing_kwargs: SlicingInputType,
|
|
@@ -335,7 +252,7 @@ class AbstractImage(ABC):
|
|
|
335
252
|
|
|
336
253
|
def _get_roi_as_dask(
|
|
337
254
|
self,
|
|
338
|
-
roi: Roi,
|
|
255
|
+
roi: Roi | RoiPixels,
|
|
339
256
|
axes_order: Sequence[str] | None = None,
|
|
340
257
|
transforms: Sequence[TransformProtocol] | None = None,
|
|
341
258
|
**slicing_kwargs: SlicingInputType,
|
|
@@ -392,7 +309,7 @@ class AbstractImage(ABC):
|
|
|
392
309
|
|
|
393
310
|
def _get_roi(
|
|
394
311
|
self,
|
|
395
|
-
roi: Roi,
|
|
312
|
+
roi: Roi | RoiPixels,
|
|
396
313
|
axes_order: Sequence[str] | None = None,
|
|
397
314
|
transforms: Sequence[TransformProtocol] | None = None,
|
|
398
315
|
mode: Literal["numpy", "dask"] = "numpy",
|
|
@@ -468,7 +385,7 @@ class AbstractImage(ABC):
|
|
|
468
385
|
|
|
469
386
|
def _set_roi(
|
|
470
387
|
self,
|
|
471
|
-
roi: Roi,
|
|
388
|
+
roi: Roi | RoiPixels,
|
|
472
389
|
patch: np.ndarray | da.Array,
|
|
473
390
|
axes_order: Sequence[str] | None = None,
|
|
474
391
|
transforms: Sequence[TransformProtocol] | None = None,
|
|
@@ -527,14 +444,25 @@ class AbstractImage(ABC):
|
|
|
527
444
|
|
|
528
445
|
def roi(self, name: str | None = "image") -> Roi:
|
|
529
446
|
"""Return the ROI covering the entire image."""
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
447
|
+
dim_x = self.dimensions.get("x")
|
|
448
|
+
dim_y = self.dimensions.get("y")
|
|
449
|
+
assert dim_x is not None and dim_y is not None
|
|
450
|
+
dim_z = self.dimensions.get("z")
|
|
451
|
+
z = None if dim_z is None else 0
|
|
452
|
+
dim_t = self.dimensions.get("t")
|
|
453
|
+
t = None if dim_t is None else 0
|
|
454
|
+
roi_px = RoiPixels(
|
|
455
|
+
name=name,
|
|
456
|
+
x=0,
|
|
457
|
+
y=0,
|
|
458
|
+
z=z,
|
|
459
|
+
t=t,
|
|
460
|
+
x_length=dim_x,
|
|
461
|
+
y_length=dim_y,
|
|
462
|
+
z_length=dim_z,
|
|
463
|
+
t_length=dim_t,
|
|
464
|
+
)
|
|
465
|
+
return roi_px.to_roi(pixel_size=self.pixel_size)
|
|
538
466
|
|
|
539
467
|
def build_image_roi_table(self, name: str | None = "image") -> RoiTable:
|
|
540
468
|
"""Build the ROI table containing the ROI covering the entire image."""
|
|
@@ -648,7 +576,7 @@ def consolidate_image(
|
|
|
648
576
|
mode: Literal["dask", "numpy", "coarsen"] = "dask",
|
|
649
577
|
) -> None:
|
|
650
578
|
"""Consolidate the image on disk."""
|
|
651
|
-
target_paths = image.
|
|
579
|
+
target_paths = image._meta_handler.meta.paths
|
|
652
580
|
targets = [
|
|
653
581
|
image._group_handler.get_array(path)
|
|
654
582
|
for path in target_paths
|
|
@@ -657,435 +585,3 @@ def consolidate_image(
|
|
|
657
585
|
consolidate_pyramid(
|
|
658
586
|
source=image.zarr_array, targets=targets, order=order, mode=mode
|
|
659
587
|
)
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
def _shapes_from_ref_image(
|
|
663
|
-
ref_image: AbstractImage,
|
|
664
|
-
) -> tuple[list[tuple[int, ...]], list[tuple[float, ...]]]:
|
|
665
|
-
"""Rebuild base shape based on a new shape."""
|
|
666
|
-
meta = ref_image.meta
|
|
667
|
-
paths = meta.paths
|
|
668
|
-
index_path = paths.index(ref_image.path)
|
|
669
|
-
sub_paths = paths[index_path:]
|
|
670
|
-
group_handler = ref_image._group_handler
|
|
671
|
-
shapes, scales = [], []
|
|
672
|
-
for path in sub_paths:
|
|
673
|
-
zarr_array = group_handler.get_array(path)
|
|
674
|
-
shapes.append(zarr_array.shape)
|
|
675
|
-
scales.append(meta.get_dataset(path=path).scale)
|
|
676
|
-
if len(shapes) == len(paths):
|
|
677
|
-
return shapes, scales
|
|
678
|
-
missing_levels = len(paths) - len(shapes)
|
|
679
|
-
extended_shapes = compute_shapes_from_scaling_factors(
|
|
680
|
-
base_shape=shapes[-1],
|
|
681
|
-
scaling_factors=ref_image.meta.scaling_factor(),
|
|
682
|
-
num_levels=missing_levels + 1,
|
|
683
|
-
)
|
|
684
|
-
shapes.extend(extended_shapes[1:])
|
|
685
|
-
extended_scales = compute_scales_from_shapes(
|
|
686
|
-
shapes=extended_shapes,
|
|
687
|
-
base_scale=scales[-1],
|
|
688
|
-
)
|
|
689
|
-
scales.extend(extended_scales[1:])
|
|
690
|
-
return shapes, scales
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
def _shapes_from_new_shape(
|
|
694
|
-
ref_image: AbstractImage,
|
|
695
|
-
shape: Sequence[int],
|
|
696
|
-
) -> tuple[list[tuple[int, ...]], list[tuple[float, ...]]]:
|
|
697
|
-
"""Rebuild pyramid shapes based on a new base shape."""
|
|
698
|
-
if len(shape) != len(ref_image.shape):
|
|
699
|
-
raise NgioValueError(
|
|
700
|
-
"The shape of the new image does not match the reference image."
|
|
701
|
-
f"Got shape {shape} for reference shape {ref_image.shape}."
|
|
702
|
-
)
|
|
703
|
-
base_shape = tuple(shape)
|
|
704
|
-
scaling_factors = ref_image.meta.scaling_factor()
|
|
705
|
-
num_levels = len(ref_image.meta.paths)
|
|
706
|
-
shapes = compute_shapes_from_scaling_factors(
|
|
707
|
-
base_shape=base_shape,
|
|
708
|
-
scaling_factors=scaling_factors,
|
|
709
|
-
num_levels=num_levels,
|
|
710
|
-
)
|
|
711
|
-
scales = compute_scales_from_shapes(
|
|
712
|
-
shapes=shapes,
|
|
713
|
-
base_scale=ref_image.dataset.scale,
|
|
714
|
-
)
|
|
715
|
-
return shapes, scales
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
def _compute_pyramid_shapes(
|
|
719
|
-
ref_image: AbstractImage,
|
|
720
|
-
shape: Sequence[int] | None,
|
|
721
|
-
) -> tuple[list[tuple[int, ...]], list[tuple[float, ...]]]:
|
|
722
|
-
"""Rebuild pyramid shapes based on a new base shape."""
|
|
723
|
-
if shape is None:
|
|
724
|
-
return _shapes_from_ref_image(ref_image=ref_image)
|
|
725
|
-
return _shapes_from_new_shape(ref_image=ref_image, shape=shape)
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
def _check_len_compatibility(
|
|
729
|
-
ref_shape: tuple[int, ...],
|
|
730
|
-
chunks: ChunksLike,
|
|
731
|
-
shards: ShardsLike | None,
|
|
732
|
-
translation: Sequence[float] | None = None,
|
|
733
|
-
) -> None:
|
|
734
|
-
"""Check if the chunks and shards are compatible with the reference shape.
|
|
735
|
-
|
|
736
|
-
Args:
|
|
737
|
-
ref_shape: The reference shape.
|
|
738
|
-
chunks: The chunks to check.
|
|
739
|
-
shards: The shards to check.
|
|
740
|
-
translation: The translation to check.
|
|
741
|
-
"""
|
|
742
|
-
if chunks != "auto":
|
|
743
|
-
if len(chunks) != len(ref_shape):
|
|
744
|
-
raise NgioValueError(
|
|
745
|
-
"The length of the chunks must be the same as the number of dimensions."
|
|
746
|
-
)
|
|
747
|
-
if shards is not None and shards != "auto":
|
|
748
|
-
if len(shards) != len(ref_shape):
|
|
749
|
-
raise NgioValueError(
|
|
750
|
-
"The length of the shards must be the same as the number of dimensions."
|
|
751
|
-
)
|
|
752
|
-
if translation is not None:
|
|
753
|
-
if len(translation) != len(ref_shape):
|
|
754
|
-
raise NgioValueError(
|
|
755
|
-
"The length of the translation must be the same as the number of "
|
|
756
|
-
"dimensions."
|
|
757
|
-
)
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
def _apply_channel_policy(
|
|
761
|
-
ref_image: AbstractImage,
|
|
762
|
-
channels_policy: Literal["squeeze", "same", "singleton"] | int,
|
|
763
|
-
shapes: list[tuple[int, ...]],
|
|
764
|
-
axes: tuple[str, ...],
|
|
765
|
-
chunks: ChunksLike,
|
|
766
|
-
shards: ShardsLike | None,
|
|
767
|
-
translation: Sequence[float],
|
|
768
|
-
scales: list[tuple[float, ...]] | tuple[float, ...],
|
|
769
|
-
) -> tuple[
|
|
770
|
-
list[tuple[int, ...]],
|
|
771
|
-
tuple[str, ...],
|
|
772
|
-
ChunksLike,
|
|
773
|
-
ShardsLike | None,
|
|
774
|
-
tuple[float, ...],
|
|
775
|
-
list[tuple[float, ...]] | tuple[float, ...],
|
|
776
|
-
]:
|
|
777
|
-
"""Apply the channel policy to the shapes and axes.
|
|
778
|
-
|
|
779
|
-
Args:
|
|
780
|
-
ref_image: The reference image.
|
|
781
|
-
channels_policy: The channels policy to apply.
|
|
782
|
-
shapes: The shapes of the pyramid levels.
|
|
783
|
-
axes: The axes of the image.
|
|
784
|
-
chunks: The chunks of the image.
|
|
785
|
-
shards: The shards of the image.
|
|
786
|
-
translation: The translation of the image.
|
|
787
|
-
scales: The scales of the image.
|
|
788
|
-
|
|
789
|
-
Returns:
|
|
790
|
-
The new shapes and axes after applying the channel policy.
|
|
791
|
-
"""
|
|
792
|
-
translation = tuple(translation)
|
|
793
|
-
if channels_policy == "same":
|
|
794
|
-
return shapes, axes, chunks, shards, translation, scales
|
|
795
|
-
|
|
796
|
-
if channels_policy == "singleton":
|
|
797
|
-
# Treat 'singleton' as setting channel size to 1
|
|
798
|
-
channels_policy = 1
|
|
799
|
-
|
|
800
|
-
channel_index = ref_image.axes_handler.get_index("c")
|
|
801
|
-
if channel_index is None:
|
|
802
|
-
if channels_policy == "squeeze":
|
|
803
|
-
return shapes, axes, chunks, shards, translation, scales
|
|
804
|
-
raise NgioValueError(
|
|
805
|
-
f"Cannot apply channel policy {channels_policy=} to an image "
|
|
806
|
-
"without channels axis."
|
|
807
|
-
)
|
|
808
|
-
if channels_policy == "squeeze":
|
|
809
|
-
new_shapes = []
|
|
810
|
-
for shape in shapes:
|
|
811
|
-
new_shape = shape[:channel_index] + shape[channel_index + 1 :]
|
|
812
|
-
new_shapes.append(new_shape)
|
|
813
|
-
|
|
814
|
-
if isinstance(scales, tuple):
|
|
815
|
-
new_scales = scales[:channel_index] + scales[channel_index + 1 :]
|
|
816
|
-
else:
|
|
817
|
-
new_scales = []
|
|
818
|
-
for scale in scales:
|
|
819
|
-
new_scale = scale[:channel_index] + scale[channel_index + 1 :]
|
|
820
|
-
new_scales.append(new_scale)
|
|
821
|
-
|
|
822
|
-
new_axes = axes[:channel_index] + axes[channel_index + 1 :]
|
|
823
|
-
if chunks == "auto":
|
|
824
|
-
new_chunks: ChunksLike = "auto"
|
|
825
|
-
else:
|
|
826
|
-
new_chunks = chunks[:channel_index] + chunks[channel_index + 1 :]
|
|
827
|
-
if shards == "auto" or shards is None:
|
|
828
|
-
new_shards: ShardsLike | None = shards
|
|
829
|
-
else:
|
|
830
|
-
new_shards = shards[:channel_index] + shards[channel_index + 1 :]
|
|
831
|
-
|
|
832
|
-
translation = translation[:channel_index] + translation[channel_index + 1 :]
|
|
833
|
-
return new_shapes, new_axes, new_chunks, new_shards, translation, new_scales
|
|
834
|
-
elif isinstance(channels_policy, int):
|
|
835
|
-
new_shapes = []
|
|
836
|
-
for shape in shapes:
|
|
837
|
-
new_shape = (
|
|
838
|
-
*shape[:channel_index],
|
|
839
|
-
channels_policy,
|
|
840
|
-
*shape[channel_index + 1 :],
|
|
841
|
-
)
|
|
842
|
-
new_shapes.append(new_shape)
|
|
843
|
-
return new_shapes, axes, chunks, shards, translation, scales
|
|
844
|
-
else:
|
|
845
|
-
raise NgioValueError(
|
|
846
|
-
f"Invalid channels policy: {channels_policy}. "
|
|
847
|
-
"Must be 'squeeze', 'same', or an integer."
|
|
848
|
-
)
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
def _check_channels_meta_compatibility(
|
|
852
|
-
meta_type: type[_image_or_label_meta],
|
|
853
|
-
ref_image: AbstractImage,
|
|
854
|
-
channels_meta: Sequence[str | Channel] | None,
|
|
855
|
-
) -> Sequence[str | Channel] | None:
|
|
856
|
-
"""Check if the channels metadata is compatible with the reference image.
|
|
857
|
-
|
|
858
|
-
Args:
|
|
859
|
-
meta_type: The metadata type.
|
|
860
|
-
ref_image: The reference image.
|
|
861
|
-
channels_meta: The channels metadata to check.
|
|
862
|
-
|
|
863
|
-
Returns:
|
|
864
|
-
The channels metadata if compatible, None otherwise.
|
|
865
|
-
"""
|
|
866
|
-
if issubclass(meta_type, NgioLabelMeta):
|
|
867
|
-
if channels_meta is not None:
|
|
868
|
-
raise NgioValueError("Cannot set channels_meta for a label image.")
|
|
869
|
-
return None
|
|
870
|
-
if channels_meta is not None:
|
|
871
|
-
return channels_meta
|
|
872
|
-
assert isinstance(ref_image.meta, NgioImageMeta)
|
|
873
|
-
ref_meta = ref_image.meta
|
|
874
|
-
index_c = ref_meta.axes_handler.get_index("c")
|
|
875
|
-
if index_c is None:
|
|
876
|
-
return None
|
|
877
|
-
|
|
878
|
-
# If the channels number does not match, return None
|
|
879
|
-
# Else return the channels metadata from the reference image
|
|
880
|
-
ref_shape = ref_image.shape
|
|
881
|
-
ref_num_channels = ref_shape[index_c] if index_c is not None else 1
|
|
882
|
-
channels_ = ref_meta.channels_meta.channels if ref_meta.channels_meta else []
|
|
883
|
-
# Reset to None if number of channels do not match
|
|
884
|
-
channels_meta_ = channels_ if ref_num_channels == len(channels_) else None
|
|
885
|
-
return channels_meta_
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
def adapt_scales(
|
|
889
|
-
scales: list[tuple[float, ...]],
|
|
890
|
-
pixelsize: float | tuple[float, float] | None,
|
|
891
|
-
z_spacing: float | None,
|
|
892
|
-
time_spacing: float | None,
|
|
893
|
-
ref_image: AbstractImage,
|
|
894
|
-
) -> list[tuple[float, ...]] | tuple[float, ...]:
|
|
895
|
-
if pixelsize is None and z_spacing is None and time_spacing is None:
|
|
896
|
-
return scales
|
|
897
|
-
pixel_size = ref_image.pixel_size
|
|
898
|
-
if pixelsize is None:
|
|
899
|
-
pixelsize = (pixel_size.y, pixel_size.x)
|
|
900
|
-
if z_spacing is None:
|
|
901
|
-
z_spacing = pixel_size.z
|
|
902
|
-
else:
|
|
903
|
-
z_spacing = z_spacing
|
|
904
|
-
if time_spacing is None:
|
|
905
|
-
time_spacing = pixel_size.t
|
|
906
|
-
else:
|
|
907
|
-
time_spacing = time_spacing
|
|
908
|
-
base_scale = compute_base_scale(
|
|
909
|
-
pixelsize=pixelsize,
|
|
910
|
-
z_spacing=z_spacing,
|
|
911
|
-
time_spacing=time_spacing,
|
|
912
|
-
axes_handler=ref_image.axes_handler,
|
|
913
|
-
)
|
|
914
|
-
return base_scale
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
def abstract_derive(
|
|
918
|
-
*,
|
|
919
|
-
ref_image: AbstractImage,
|
|
920
|
-
meta_type: type[_image_or_label_meta],
|
|
921
|
-
store: StoreOrGroup,
|
|
922
|
-
overwrite: bool = False,
|
|
923
|
-
# Metadata parameters
|
|
924
|
-
shape: Sequence[int] | None = None,
|
|
925
|
-
pixelsize: float | tuple[float, float] | None = None,
|
|
926
|
-
z_spacing: float | None = None,
|
|
927
|
-
time_spacing: float | None = None,
|
|
928
|
-
name: str | None = None,
|
|
929
|
-
translation: Sequence[float] | None = None,
|
|
930
|
-
channels_policy: Literal["squeeze", "same", "singleton"] | int = "same",
|
|
931
|
-
channels_meta: Sequence[str | Channel] | None = None,
|
|
932
|
-
ngff_version: NgffVersions | None = None,
|
|
933
|
-
# Zarr Array parameters
|
|
934
|
-
chunks: ChunksLike | None = None,
|
|
935
|
-
shards: ShardsLike | None = None,
|
|
936
|
-
dtype: str | None = None,
|
|
937
|
-
dimension_separator: Literal[".", "/"] | None = None,
|
|
938
|
-
compressors: CompressorLike | None = None,
|
|
939
|
-
extra_array_kwargs: Mapping[str, Any] | None = None,
|
|
940
|
-
# Deprecated arguments
|
|
941
|
-
labels: Sequence[str] | None = None,
|
|
942
|
-
pixel_size: PixelSize | None = None,
|
|
943
|
-
) -> tuple[ZarrGroupHandler, AxesSetup]:
|
|
944
|
-
"""Create an empty OME-Zarr image from an existing image.
|
|
945
|
-
|
|
946
|
-
If a kwarg is not provided, the value from the reference image will be used.
|
|
947
|
-
|
|
948
|
-
Args:
|
|
949
|
-
ref_image (AbstractImage): The reference image to derive from.
|
|
950
|
-
meta_type (type[_image_or_label_meta]): The metadata type to use.
|
|
951
|
-
store (StoreOrGroup): The Zarr store or group to create the image in.
|
|
952
|
-
overwrite (bool): Whether to overwrite an existing image.
|
|
953
|
-
shape (Sequence[int] | None): The shape of the new image.
|
|
954
|
-
pixelsize (float | tuple[float, float] | None): The pixel size of the new image.
|
|
955
|
-
z_spacing (float | None): The z spacing of the new image.
|
|
956
|
-
time_spacing (float | None): The time spacing of the new image.
|
|
957
|
-
name (str | None): The name of the new image.
|
|
958
|
-
translation (Sequence[float] | None): The translation for each axis
|
|
959
|
-
at the highest resolution level. Defaults to None.
|
|
960
|
-
channels_policy (Literal["squeeze", "same", "singleton"] | int):
|
|
961
|
-
Possible policies:
|
|
962
|
-
- If "squeeze", the channels axis will be removed (no matter its size).
|
|
963
|
-
- If "same", the channels axis will be kept as is (if it exists).
|
|
964
|
-
- If "singleton", the channels axis will be set to size 1.
|
|
965
|
-
- If an integer is provided, the channels axis will be changed to have that
|
|
966
|
-
size.
|
|
967
|
-
channels_meta (Sequence[str | Channel] | None): The channels metadata
|
|
968
|
-
of the new image.
|
|
969
|
-
ngff_version (NgffVersions | None): The NGFF version to use.
|
|
970
|
-
chunks (ChunksLike | None): The chunk shape of the new image.
|
|
971
|
-
shards (ShardsLike | None): The shard shape of the new image.
|
|
972
|
-
dtype (str | None): The data type of the new image.
|
|
973
|
-
dimension_separator (Literal[".", "/"] | None): The separator to use for
|
|
974
|
-
dimensions.
|
|
975
|
-
compressors (CompressorLike | None): The compressors to use.
|
|
976
|
-
extra_array_kwargs (Mapping[str, Any] | None): Extra arguments to pass to
|
|
977
|
-
the zarr array creation.
|
|
978
|
-
labels (Sequence[str] | None): The labels of the new image.
|
|
979
|
-
This argument is DEPRECATED please use channels_meta instead.
|
|
980
|
-
pixel_size (PixelSize | None): The pixel size of the new image.
|
|
981
|
-
This argument is DEPRECATED please use pixelsize, z_spacing,
|
|
982
|
-
and time_spacing instead.
|
|
983
|
-
|
|
984
|
-
Returns:
|
|
985
|
-
ImagesContainer: The new derived image.
|
|
986
|
-
|
|
987
|
-
"""
|
|
988
|
-
# TODO: remove in ngio 0.6
|
|
989
|
-
if labels is not None:
|
|
990
|
-
warnings.warn(
|
|
991
|
-
"The 'labels' argument is deprecated and will be removed in "
|
|
992
|
-
"ngio=0.6. Please use 'channels_meta' instead.",
|
|
993
|
-
DeprecationWarning,
|
|
994
|
-
stacklevel=2,
|
|
995
|
-
)
|
|
996
|
-
channels_meta = list(labels)
|
|
997
|
-
if pixel_size is not None:
|
|
998
|
-
warnings.warn(
|
|
999
|
-
"The 'pixel_size' argument is deprecated and will be removed in "
|
|
1000
|
-
"ngio=0.6. Please use 'pixelsize', 'z_spacing', and 'time_spacing'"
|
|
1001
|
-
"instead.",
|
|
1002
|
-
DeprecationWarning,
|
|
1003
|
-
stacklevel=2,
|
|
1004
|
-
)
|
|
1005
|
-
pixelsize = (pixel_size.y, pixel_size.x)
|
|
1006
|
-
# End of deprecated arguments handling
|
|
1007
|
-
ref_meta = ref_image.meta
|
|
1008
|
-
|
|
1009
|
-
shapes, scales = _compute_pyramid_shapes(
|
|
1010
|
-
shape=shape,
|
|
1011
|
-
ref_image=ref_image,
|
|
1012
|
-
)
|
|
1013
|
-
ref_shape = next(iter(shapes))
|
|
1014
|
-
|
|
1015
|
-
scales = adapt_scales(
|
|
1016
|
-
scales=scales,
|
|
1017
|
-
pixelsize=pixelsize,
|
|
1018
|
-
z_spacing=z_spacing,
|
|
1019
|
-
time_spacing=time_spacing,
|
|
1020
|
-
ref_image=ref_image,
|
|
1021
|
-
)
|
|
1022
|
-
|
|
1023
|
-
if name is None:
|
|
1024
|
-
name = ref_meta.name
|
|
1025
|
-
|
|
1026
|
-
if dtype is None:
|
|
1027
|
-
dtype = ref_image.dtype
|
|
1028
|
-
|
|
1029
|
-
if dimension_separator is None:
|
|
1030
|
-
dimension_separator = find_dimension_separator(ref_image.zarr_array)
|
|
1031
|
-
|
|
1032
|
-
if compressors is None:
|
|
1033
|
-
compressors = ref_image.zarr_array.compressors # type: ignore
|
|
1034
|
-
|
|
1035
|
-
if translation is None:
|
|
1036
|
-
translation = ref_image.dataset.translation
|
|
1037
|
-
|
|
1038
|
-
if chunks is None:
|
|
1039
|
-
chunks = ref_image.zarr_array.chunks
|
|
1040
|
-
if shards is None:
|
|
1041
|
-
shards = ref_image.zarr_array.shards
|
|
1042
|
-
|
|
1043
|
-
_check_len_compatibility(
|
|
1044
|
-
ref_shape=ref_shape,
|
|
1045
|
-
chunks=chunks,
|
|
1046
|
-
shards=shards,
|
|
1047
|
-
translation=translation,
|
|
1048
|
-
)
|
|
1049
|
-
|
|
1050
|
-
if ngff_version is None:
|
|
1051
|
-
ngff_version = ref_meta.version
|
|
1052
|
-
|
|
1053
|
-
shapes, axes, chunks, shards, translation, scales = _apply_channel_policy(
|
|
1054
|
-
ref_image=ref_image,
|
|
1055
|
-
channels_policy=channels_policy,
|
|
1056
|
-
shapes=shapes,
|
|
1057
|
-
axes=ref_image.axes,
|
|
1058
|
-
chunks=chunks,
|
|
1059
|
-
shards=shards,
|
|
1060
|
-
translation=translation,
|
|
1061
|
-
scales=scales,
|
|
1062
|
-
)
|
|
1063
|
-
channels_meta_ = _check_channels_meta_compatibility(
|
|
1064
|
-
meta_type=meta_type,
|
|
1065
|
-
ref_image=ref_image,
|
|
1066
|
-
channels_meta=channels_meta,
|
|
1067
|
-
)
|
|
1068
|
-
|
|
1069
|
-
handler, axes_setup = init_image_like_from_shapes(
|
|
1070
|
-
store=store,
|
|
1071
|
-
meta_type=meta_type,
|
|
1072
|
-
shapes=shapes,
|
|
1073
|
-
base_scale=scales,
|
|
1074
|
-
levels=ref_meta.paths,
|
|
1075
|
-
translation=translation,
|
|
1076
|
-
time_unit=ref_image.time_unit,
|
|
1077
|
-
space_unit=ref_image.space_unit,
|
|
1078
|
-
axes_names=axes,
|
|
1079
|
-
name=name,
|
|
1080
|
-
axes_setup=ref_image.axes_setup,
|
|
1081
|
-
channels_meta=channels_meta_,
|
|
1082
|
-
chunks=chunks,
|
|
1083
|
-
shards=shards,
|
|
1084
|
-
dtype=dtype,
|
|
1085
|
-
dimension_separator=dimension_separator,
|
|
1086
|
-
compressors=compressors,
|
|
1087
|
-
overwrite=overwrite,
|
|
1088
|
-
ngff_version=ngff_version,
|
|
1089
|
-
extra_array_kwargs=extra_array_kwargs,
|
|
1090
|
-
)
|
|
1091
|
-
return handler, axes_setup
|