ngio 0.5.0b6__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.
Files changed (88) hide show
  1. ngio/__init__.py +69 -0
  2. ngio/common/__init__.py +28 -0
  3. ngio/common/_dimensions.py +335 -0
  4. ngio/common/_masking_roi.py +153 -0
  5. ngio/common/_pyramid.py +408 -0
  6. ngio/common/_roi.py +315 -0
  7. ngio/common/_synt_images_utils.py +101 -0
  8. ngio/common/_zoom.py +188 -0
  9. ngio/experimental/__init__.py +5 -0
  10. ngio/experimental/iterators/__init__.py +15 -0
  11. ngio/experimental/iterators/_abstract_iterator.py +390 -0
  12. ngio/experimental/iterators/_feature.py +189 -0
  13. ngio/experimental/iterators/_image_processing.py +130 -0
  14. ngio/experimental/iterators/_mappers.py +48 -0
  15. ngio/experimental/iterators/_rois_utils.py +126 -0
  16. ngio/experimental/iterators/_segmentation.py +235 -0
  17. ngio/hcs/__init__.py +19 -0
  18. ngio/hcs/_plate.py +1354 -0
  19. ngio/images/__init__.py +44 -0
  20. ngio/images/_abstract_image.py +967 -0
  21. ngio/images/_create_synt_container.py +132 -0
  22. ngio/images/_create_utils.py +423 -0
  23. ngio/images/_image.py +926 -0
  24. ngio/images/_label.py +411 -0
  25. ngio/images/_masked_image.py +531 -0
  26. ngio/images/_ome_zarr_container.py +1237 -0
  27. ngio/images/_table_ops.py +471 -0
  28. ngio/io_pipes/__init__.py +75 -0
  29. ngio/io_pipes/_io_pipes.py +361 -0
  30. ngio/io_pipes/_io_pipes_masked.py +488 -0
  31. ngio/io_pipes/_io_pipes_roi.py +146 -0
  32. ngio/io_pipes/_io_pipes_types.py +56 -0
  33. ngio/io_pipes/_match_shape.py +377 -0
  34. ngio/io_pipes/_ops_axes.py +344 -0
  35. ngio/io_pipes/_ops_slices.py +411 -0
  36. ngio/io_pipes/_ops_slices_utils.py +199 -0
  37. ngio/io_pipes/_ops_transforms.py +104 -0
  38. ngio/io_pipes/_zoom_transform.py +180 -0
  39. ngio/ome_zarr_meta/__init__.py +65 -0
  40. ngio/ome_zarr_meta/_meta_handlers.py +536 -0
  41. ngio/ome_zarr_meta/ngio_specs/__init__.py +77 -0
  42. ngio/ome_zarr_meta/ngio_specs/_axes.py +515 -0
  43. ngio/ome_zarr_meta/ngio_specs/_channels.py +462 -0
  44. ngio/ome_zarr_meta/ngio_specs/_dataset.py +89 -0
  45. ngio/ome_zarr_meta/ngio_specs/_ngio_hcs.py +539 -0
  46. ngio/ome_zarr_meta/ngio_specs/_ngio_image.py +438 -0
  47. ngio/ome_zarr_meta/ngio_specs/_pixel_size.py +122 -0
  48. ngio/ome_zarr_meta/v04/__init__.py +27 -0
  49. ngio/ome_zarr_meta/v04/_custom_models.py +18 -0
  50. ngio/ome_zarr_meta/v04/_v04_spec.py +473 -0
  51. ngio/ome_zarr_meta/v05/__init__.py +27 -0
  52. ngio/ome_zarr_meta/v05/_custom_models.py +18 -0
  53. ngio/ome_zarr_meta/v05/_v05_spec.py +511 -0
  54. ngio/resources/20200812-CardiomyocyteDifferentiation14-Cycle1_B03/mask.png +0 -0
  55. ngio/resources/20200812-CardiomyocyteDifferentiation14-Cycle1_B03/nuclei.png +0 -0
  56. ngio/resources/20200812-CardiomyocyteDifferentiation14-Cycle1_B03/raw.jpg +0 -0
  57. ngio/resources/__init__.py +55 -0
  58. ngio/resources/resource_model.py +36 -0
  59. ngio/tables/__init__.py +43 -0
  60. ngio/tables/_abstract_table.py +270 -0
  61. ngio/tables/_tables_container.py +449 -0
  62. ngio/tables/backends/__init__.py +57 -0
  63. ngio/tables/backends/_abstract_backend.py +240 -0
  64. ngio/tables/backends/_anndata.py +139 -0
  65. ngio/tables/backends/_anndata_utils.py +90 -0
  66. ngio/tables/backends/_csv.py +19 -0
  67. ngio/tables/backends/_json.py +92 -0
  68. ngio/tables/backends/_parquet.py +19 -0
  69. ngio/tables/backends/_py_arrow_backends.py +222 -0
  70. ngio/tables/backends/_table_backends.py +226 -0
  71. ngio/tables/backends/_utils.py +608 -0
  72. ngio/tables/v1/__init__.py +23 -0
  73. ngio/tables/v1/_condition_table.py +71 -0
  74. ngio/tables/v1/_feature_table.py +125 -0
  75. ngio/tables/v1/_generic_table.py +49 -0
  76. ngio/tables/v1/_roi_table.py +575 -0
  77. ngio/transforms/__init__.py +5 -0
  78. ngio/transforms/_zoom.py +19 -0
  79. ngio/utils/__init__.py +45 -0
  80. ngio/utils/_cache.py +48 -0
  81. ngio/utils/_datasets.py +165 -0
  82. ngio/utils/_errors.py +37 -0
  83. ngio/utils/_fractal_fsspec_store.py +42 -0
  84. ngio/utils/_zarr_utils.py +534 -0
  85. ngio-0.5.0b6.dist-info/METADATA +148 -0
  86. ngio-0.5.0b6.dist-info/RECORD +88 -0
  87. ngio-0.5.0b6.dist-info/WHEEL +4 -0
  88. ngio-0.5.0b6.dist-info/licenses/LICENSE +28 -0
@@ -0,0 +1,132 @@
1
+ """Abstract class for handling OME-NGFF images."""
2
+
3
+ from collections.abc import Mapping, Sequence
4
+ from typing import Any, Literal
5
+
6
+ import numpy as np
7
+ import PIL.Image
8
+ from zarr.core.array import CompressorLike
9
+
10
+ from ngio.common._pyramid import ChunksLike, ShardsLike
11
+ from ngio.common._synt_images_utils import fit_to_shape
12
+ from ngio.images._ome_zarr_container import OmeZarrContainer, create_ome_zarr_from_array
13
+ from ngio.ome_zarr_meta.ngio_specs import (
14
+ Channel,
15
+ DefaultNgffVersion,
16
+ NgffVersions,
17
+ )
18
+ from ngio.resources import AVAILABLE_SAMPLES, SampleInfo, get_sample_info
19
+ from ngio.tables import (
20
+ DefaultTableBackend,
21
+ TableBackend,
22
+ )
23
+ from ngio.utils import (
24
+ StoreOrGroup,
25
+ )
26
+
27
+
28
+ def create_synthetic_ome_zarr(
29
+ store: StoreOrGroup,
30
+ shape: Sequence[int],
31
+ reference_sample: AVAILABLE_SAMPLES | SampleInfo = "Cardiomyocyte",
32
+ levels: int | list[str] = 5,
33
+ table_backend: TableBackend = DefaultTableBackend,
34
+ scaling_factors: Sequence[float] | Literal["auto"] = "auto",
35
+ axes_names: Sequence[str] | None = None,
36
+ channels_meta: Sequence[str | Channel] | None = None,
37
+ ngff_version: NgffVersions = DefaultNgffVersion,
38
+ chunks: ChunksLike = "auto",
39
+ shards: ShardsLike | None = None,
40
+ dimension_separator: Literal[".", "/"] = "/",
41
+ compressors: CompressorLike = "auto",
42
+ extra_array_kwargs: Mapping[str, Any] | None = None,
43
+ overwrite: bool = False,
44
+ ) -> OmeZarrContainer:
45
+ """Create a synthetic OME-Zarr image with the given shape and metadata.
46
+
47
+ Args:
48
+ store (StoreOrGroup): The Zarr store or group to create the image in.
49
+ shape (Sequence[int]): The shape of the image.
50
+ reference_sample (AVAILABLE_SAMPLES | SampleInfo): The reference sample to use.
51
+ Defaults to "Cardiomyocyte".
52
+ levels (int | list[str]): The number of levels in the pyramid or a list of
53
+ level names. Defaults to 5.
54
+ table_backend (TableBackend): Table backend to be used to store tables.
55
+ Defaults to DefaultTableBackend.
56
+ scaling_factors (Sequence[float] | Literal["auto"]): The down-scaling factors
57
+ for the pyramid levels. Defaults to "auto".
58
+ axes_names (Sequence[str] | None): The names of the axes. If None the
59
+ canonical names are used. Defaults to None.
60
+ channels_meta (Sequence[str | Channel] | None): The channels metadata.
61
+ Defaults to None.
62
+ ngff_version (NgffVersions): The version of the OME-Zarr specification.
63
+ Defaults to DefaultNgffVersion.
64
+ chunks (ChunksLike): The chunk shape. Defaults to "auto".
65
+ shards (ShardsLike | None): The shard shape. Defaults to None.
66
+ dimension_separator (Literal[".", "/"]): The separator to use for
67
+ dimensions. Defaults to "/".
68
+ compressors (CompressorLike): The compressors to use. Defaults to "auto".
69
+ extra_array_kwargs (Mapping[str, Any] | None): Extra arguments to pass to
70
+ the zarr array creation. Defaults to None.
71
+ overwrite (bool): Whether to overwrite an existing image. Defaults to False.
72
+ """
73
+ if isinstance(reference_sample, str):
74
+ sample_info = get_sample_info(reference_sample)
75
+ else:
76
+ sample_info = reference_sample
77
+
78
+ raw = np.asarray(PIL.Image.open(sample_info.img_path))
79
+ raw = fit_to_shape(arr=raw, out_shape=tuple(shape))
80
+ raw = raw / np.max(raw) * (2**16 - 1)
81
+ raw = raw.astype(np.uint16)
82
+ ome_zarr = create_ome_zarr_from_array(
83
+ store=store,
84
+ array=raw,
85
+ pixelsize=sample_info.xy_pixelsize,
86
+ z_spacing=sample_info.z_spacing,
87
+ time_spacing=sample_info.time_spacing,
88
+ levels=levels,
89
+ space_unit=sample_info.space_unit,
90
+ time_unit=sample_info.time_unit,
91
+ axes_names=axes_names,
92
+ channels_meta=channels_meta,
93
+ scaling_factors=scaling_factors,
94
+ extra_array_kwargs=extra_array_kwargs,
95
+ name=sample_info.name,
96
+ chunks=chunks,
97
+ shards=shards,
98
+ overwrite=overwrite,
99
+ dimension_separator=dimension_separator,
100
+ compressors=compressors,
101
+ ngff_version=ngff_version,
102
+ )
103
+
104
+ image = ome_zarr.get_image()
105
+ well_table = image.build_image_roi_table()
106
+ ome_zarr.add_table("well_ROI_table", table=well_table, backend=table_backend)
107
+
108
+ for label_info in sample_info.labels:
109
+ ome_zarr.derive_label(name=label_info.name)
110
+ label = ome_zarr.get_label(name=label_info.name)
111
+
112
+ ref_label = np.asarray(PIL.Image.open(label_info.label_path))
113
+ ref_label = ref_label.astype(label_info.dtype)
114
+
115
+ ref_label = fit_to_shape(
116
+ arr=ref_label,
117
+ out_shape=label.shape,
118
+ ensure_unique_info=label_info.ensure_unique_labels,
119
+ )
120
+ ref_label = ref_label.astype(np.uint32)
121
+ label.set_array(ref_label)
122
+ label.consolidate()
123
+
124
+ if label_info.create_masking_table:
125
+ masking_table = label.build_masking_roi_table()
126
+ ome_zarr.add_table(
127
+ name=f"{label_info.name}_masking_table",
128
+ table=masking_table,
129
+ backend=table_backend,
130
+ )
131
+
132
+ return ome_zarr
@@ -0,0 +1,423 @@
1
+ """Utility functions for working with OME-Zarr images."""
2
+
3
+ import warnings
4
+ from collections.abc import Mapping, Sequence
5
+ from typing import Any, Literal, TypeVar
6
+
7
+ from zarr.core.array import CompressorLike
8
+
9
+ from ngio.common._pyramid import ChunksLike, ImagePyramidBuilder, ShardsLike
10
+ from ngio.ome_zarr_meta import (
11
+ NgioImageMeta,
12
+ NgioLabelMeta,
13
+ update_ngio_meta,
14
+ )
15
+ from ngio.ome_zarr_meta.ngio_specs import (
16
+ AxesHandler,
17
+ Channel,
18
+ ChannelsMeta,
19
+ DefaultNgffVersion,
20
+ DefaultSpaceUnit,
21
+ DefaultTimeUnit,
22
+ NgffVersions,
23
+ SpaceUnits,
24
+ TimeUnits,
25
+ build_canonical_axes_handler,
26
+ canonical_axes_order,
27
+ canonical_label_axes_order,
28
+ )
29
+ from ngio.ome_zarr_meta.ngio_specs._axes import AxesSetup
30
+ from ngio.utils import NgioValueError, StoreOrGroup, ZarrGroupHandler
31
+
32
+ _image_or_label_meta = TypeVar("_image_or_label_meta", NgioImageMeta, NgioLabelMeta)
33
+
34
+
35
+ def _build_axes_handler(
36
+ *,
37
+ shape: tuple[int, ...],
38
+ axes_names: Sequence[str] | None,
39
+ default_channel_order: tuple[str, ...],
40
+ space_units: SpaceUnits | str | None = DefaultSpaceUnit,
41
+ time_units: TimeUnits | str | None = DefaultTimeUnit,
42
+ axes_setup: AxesSetup | None = None,
43
+ allow_non_canonical_axes: bool = False,
44
+ strict_canonical_order: bool = False,
45
+ ) -> AxesHandler:
46
+ """Compute axes names for given shape."""
47
+ if axes_names is None:
48
+ axes_names = default_channel_order[-len(shape) :]
49
+ # Validate length
50
+ if len(axes_names) != len(shape):
51
+ raise NgioValueError(
52
+ f"Number of axes names {axes_names} does not match the number of "
53
+ f"dimensions {shape}."
54
+ )
55
+ return build_canonical_axes_handler(
56
+ axes_names=axes_names,
57
+ space_units=space_units,
58
+ time_units=time_units,
59
+ axes_setup=axes_setup,
60
+ allow_non_canonical_axes=allow_non_canonical_axes,
61
+ strict_canonical_order=strict_canonical_order,
62
+ )
63
+
64
+
65
+ def _align_to_axes(
66
+ *,
67
+ values: dict[str, float],
68
+ axes_handler: AxesHandler,
69
+ default_value: float = 1.0,
70
+ ) -> tuple[float, ...]:
71
+ """Align given values to axes names."""
72
+ aligned_values = [default_value] * len(axes_handler.axes_names)
73
+ for ax, value in values.items():
74
+ index = axes_handler.get_index(ax)
75
+ if index is not None:
76
+ aligned_values[index] = value
77
+ return tuple(aligned_values)
78
+
79
+
80
+ def _check_deprecated_scaling_factors(
81
+ *,
82
+ yx_scaling_factor: float | tuple[float, float] | None = None,
83
+ z_scaling_factor: float | None = None,
84
+ scaling_factors: Sequence[float] | Literal["auto"] = "auto",
85
+ shape: tuple[int, ...],
86
+ ) -> Sequence[float] | Literal["auto"]:
87
+ if yx_scaling_factor is not None or z_scaling_factor is not None:
88
+ warnings.warn(
89
+ "The 'yx_scaling_factor' and 'z_scaling_factor' arguments are deprecated "
90
+ "and will be removed in future versions. Please use the 'scaling_factors' "
91
+ "argument instead.",
92
+ DeprecationWarning,
93
+ stacklevel=2,
94
+ )
95
+ if scaling_factors != "auto":
96
+ raise NgioValueError(
97
+ "Cannot use both 'scaling_factors' and deprecated "
98
+ "'yx_scaling_factor'/'z_scaling_factor' arguments."
99
+ )
100
+ if isinstance(yx_scaling_factor, tuple):
101
+ if len(yx_scaling_factor) != 2:
102
+ raise NgioValueError(
103
+ "yx_scaling_factor tuple must have length 2 for y and x scaling."
104
+ )
105
+ y_scale = yx_scaling_factor[0]
106
+ x_scale = yx_scaling_factor[1]
107
+ else:
108
+ y_scale = yx_scaling_factor if yx_scaling_factor is not None else 2.0
109
+ x_scale = yx_scaling_factor if yx_scaling_factor is not None else 2.0
110
+ z_scale = z_scaling_factor if z_scaling_factor is not None else 1.0
111
+ scaling_factors = (z_scale, x_scale, y_scale)
112
+ if len(scaling_factors) < len(shape):
113
+ padding = (1.0,) * (len(shape) - len(scaling_factors))
114
+ scaling_factors = padding + scaling_factors
115
+
116
+ return scaling_factors
117
+ return scaling_factors
118
+
119
+
120
+ def _compute_scaling_factors(
121
+ *,
122
+ scaling_factors: Sequence[float] | Literal["auto"],
123
+ shape: tuple[int, ...],
124
+ axes_handler: AxesHandler,
125
+ xy_scaling_factor: float | tuple[float, float] | None = None,
126
+ z_scaling_factor: float | None = None,
127
+ ) -> tuple[float, ...]:
128
+ """Compute scaling factors for given axes names."""
129
+ # TODO remove with ngio 0.6
130
+ scaling_factors = _check_deprecated_scaling_factors(
131
+ yx_scaling_factor=xy_scaling_factor,
132
+ z_scaling_factor=z_scaling_factor,
133
+ scaling_factors=scaling_factors,
134
+ shape=shape,
135
+ )
136
+ if scaling_factors == "auto":
137
+ return _align_to_axes(
138
+ values={
139
+ "x": 2.0,
140
+ "y": 2.0,
141
+ "z": 1.0,
142
+ },
143
+ axes_handler=axes_handler,
144
+ )
145
+ if len(scaling_factors) != len(shape):
146
+ raise NgioValueError(
147
+ "Length of scaling_factors does not match the number of dimensions."
148
+ )
149
+ return tuple(scaling_factors)
150
+
151
+
152
+ def _compute_base_scale(
153
+ *,
154
+ pixelsize: float | tuple[float, float],
155
+ z_spacing: float,
156
+ time_spacing: float,
157
+ axes_handler: AxesHandler,
158
+ ) -> tuple[float, ...]:
159
+ """Compute base scale for given axes names."""
160
+ if isinstance(pixelsize, tuple):
161
+ if len(pixelsize) != 2:
162
+ raise NgioValueError(
163
+ "pixelsize tuple must have length 2 for y and x pixel sizes."
164
+ )
165
+ x_size = pixelsize[1]
166
+ y_size = pixelsize[0]
167
+ else:
168
+ x_size = pixelsize
169
+ y_size = pixelsize
170
+ return _align_to_axes(
171
+ values={
172
+ "x": x_size,
173
+ "y": y_size,
174
+ "z": z_spacing,
175
+ "t": time_spacing,
176
+ },
177
+ axes_handler=axes_handler,
178
+ )
179
+
180
+
181
+ def _create_image_like_group(
182
+ *,
183
+ store: StoreOrGroup,
184
+ pyramid_builder: ImagePyramidBuilder,
185
+ meta: _image_or_label_meta,
186
+ overwrite: bool = False,
187
+ ) -> ZarrGroupHandler:
188
+ """Advanced create empty image container function placeholder."""
189
+ mode = "w" if overwrite else "w-"
190
+ group_handler = ZarrGroupHandler(
191
+ store=store, mode=mode, cache=False, zarr_format=meta.zarr_format
192
+ )
193
+ update_ngio_meta(group_handler, meta)
194
+ # Reopen in r+ mode
195
+ group_handler = group_handler.reopen_handler()
196
+ # Write the pyramid
197
+ pyramid_builder.to_zarr(group=group_handler.group)
198
+ return group_handler
199
+
200
+
201
+ def _add_channels_meta(
202
+ *,
203
+ meta: _image_or_label_meta,
204
+ channels_meta: Sequence[str | Channel] | None = None,
205
+ ) -> _image_or_label_meta:
206
+ """Create ChannelsMeta from given channels_meta input."""
207
+ if isinstance(meta, NgioLabelMeta):
208
+ if channels_meta is not None:
209
+ raise NgioValueError(
210
+ "Cannot add channels_meta to NgioLabelMeta. "
211
+ "Labels do not have channels."
212
+ )
213
+ else:
214
+ return meta
215
+ if channels_meta is None:
216
+ return meta
217
+ list_of_channels = []
218
+ for c in channels_meta:
219
+ if isinstance(c, str):
220
+ channel = Channel.default_init(label=c)
221
+ elif isinstance(c, Channel):
222
+ channel = c
223
+ else:
224
+ raise NgioValueError(
225
+ "channels_meta must be a list of strings or Channel objects."
226
+ )
227
+ list_of_channels.append(channel)
228
+
229
+ channels_meta_ = ChannelsMeta(channels=list_of_channels)
230
+ meta.set_channels_meta(channels_meta=channels_meta_)
231
+ return meta
232
+
233
+
234
+ def init_image_like(
235
+ *,
236
+ # Where to create the image
237
+ store: StoreOrGroup,
238
+ # Ngff image parameters
239
+ meta_type: type[_image_or_label_meta],
240
+ shape: Sequence[int],
241
+ pixelsize: float | tuple[float, float],
242
+ z_spacing: float = 1.0,
243
+ time_spacing: float = 1.0,
244
+ scaling_factors: Sequence[float] | Literal["auto"] = "auto",
245
+ levels: int | list[str] = 5,
246
+ space_unit: SpaceUnits | str | None = DefaultSpaceUnit,
247
+ time_unit: TimeUnits | str | None = DefaultTimeUnit,
248
+ axes_names: Sequence[str] | None = None,
249
+ name: str | None = None,
250
+ channels_meta: Sequence[str | Channel] | None = None,
251
+ ngff_version: NgffVersions = DefaultNgffVersion,
252
+ # Zarr Array parameters
253
+ chunks: ChunksLike = "auto",
254
+ shards: ShardsLike | None = None,
255
+ dtype: str = "uint16",
256
+ dimension_separator: Literal[".", "/"] = "/",
257
+ compressors: CompressorLike = "auto",
258
+ extra_array_kwargs: Mapping[str, Any] | None = None,
259
+ # internal axes configuration for advanced use cases
260
+ axes_setup: AxesSetup | None = None,
261
+ allow_non_canonical_axes: bool = False,
262
+ strict_canonical_order: bool = False,
263
+ # Whether to overwrite existing image
264
+ overwrite: bool = False,
265
+ # Deprecated arguments
266
+ yx_scaling_factor: float | tuple[float, float] | None = None,
267
+ z_scaling_factor: float | None = None,
268
+ ) -> ZarrGroupHandler:
269
+ """Create an empty OME-Zarr image with the given shape and metadata."""
270
+ shape = tuple(shape)
271
+ if meta_type is NgioImageMeta:
272
+ default_axes_order = canonical_axes_order()
273
+ else:
274
+ default_axes_order = canonical_label_axes_order()
275
+
276
+ axes_handler = _build_axes_handler(
277
+ shape=shape,
278
+ axes_names=axes_names,
279
+ default_channel_order=default_axes_order,
280
+ space_units=space_unit,
281
+ time_units=time_unit,
282
+ axes_setup=axes_setup,
283
+ allow_non_canonical_axes=allow_non_canonical_axes,
284
+ strict_canonical_order=strict_canonical_order,
285
+ )
286
+ base_scale = _compute_base_scale(
287
+ pixelsize=pixelsize,
288
+ z_spacing=z_spacing,
289
+ time_spacing=time_spacing,
290
+ axes_handler=axes_handler,
291
+ )
292
+ scaling_factors = _compute_scaling_factors(
293
+ scaling_factors=scaling_factors,
294
+ shape=shape,
295
+ axes_handler=axes_handler,
296
+ xy_scaling_factor=yx_scaling_factor,
297
+ z_scaling_factor=z_scaling_factor,
298
+ )
299
+ if isinstance(levels, int):
300
+ levels_paths = tuple(str(i) for i in range(levels))
301
+ else:
302
+ levels_paths = tuple(levels)
303
+
304
+ pyramid_builder = ImagePyramidBuilder.from_scaling_factors(
305
+ levels_paths=levels_paths,
306
+ scaling_factors=scaling_factors,
307
+ base_shape=shape,
308
+ base_scale=base_scale,
309
+ axes=axes_handler.axes_names,
310
+ chunks=chunks,
311
+ data_type=dtype,
312
+ dimension_separator=dimension_separator,
313
+ compressors=compressors,
314
+ shards=shards,
315
+ zarr_format=2 if ngff_version == "0.4" else 3,
316
+ other_array_kwargs=extra_array_kwargs,
317
+ )
318
+ meta = meta_type.default_init(
319
+ levels=[p.path for p in pyramid_builder.levels],
320
+ axes_handler=axes_handler,
321
+ scales=[p.scale for p in pyramid_builder.levels],
322
+ translations=[None for _ in pyramid_builder.levels],
323
+ name=name,
324
+ version=ngff_version,
325
+ )
326
+ meta = _add_channels_meta(meta=meta, channels_meta=channels_meta)
327
+ # Keep this creation at the end to avoid partial creations on errors
328
+ return _create_image_like_group(
329
+ store=store,
330
+ pyramid_builder=pyramid_builder,
331
+ meta=meta,
332
+ overwrite=overwrite,
333
+ )
334
+
335
+
336
+ def init_image_like_from_shapes(
337
+ *,
338
+ # Where to create the image
339
+ store: StoreOrGroup,
340
+ # Ngff image parameters
341
+ meta_type: type[_image_or_label_meta],
342
+ shapes: Sequence[tuple[int, ...]],
343
+ pixelsize: float | tuple[float, float],
344
+ z_spacing: float = 1.0,
345
+ time_spacing: float = 1.0,
346
+ levels: list[str] | None = None,
347
+ space_unit: SpaceUnits | str | None = DefaultSpaceUnit,
348
+ time_unit: TimeUnits | str | None = DefaultTimeUnit,
349
+ axes_names: Sequence[str] | None = None,
350
+ name: str | None = None,
351
+ channels_meta: Sequence[str | Channel] | None = None,
352
+ ngff_version: NgffVersions = DefaultNgffVersion,
353
+ # Zarr Array parameters
354
+ chunks: ChunksLike = "auto",
355
+ shards: ShardsLike | None = None,
356
+ dtype: str = "uint16",
357
+ dimension_separator: Literal[".", "/"] = "/",
358
+ compressors: CompressorLike = "auto",
359
+ extra_array_kwargs: Mapping[str, Any] | None = None,
360
+ # internal axes configuration for advanced use cases
361
+ axes_setup: AxesSetup | None = None,
362
+ allow_non_canonical_axes: bool = False,
363
+ strict_canonical_order: bool = False,
364
+ # Whether to overwrite existing image
365
+ overwrite: bool = False,
366
+ ) -> ZarrGroupHandler:
367
+ """Create an empty OME-Zarr image with the given shape and metadata."""
368
+ base_shape = shapes[0]
369
+ if meta_type is NgioImageMeta:
370
+ default_axes_order = canonical_axes_order()
371
+ else:
372
+ default_axes_order = canonical_label_axes_order()
373
+
374
+ axes_handler = _build_axes_handler(
375
+ shape=base_shape,
376
+ axes_names=axes_names,
377
+ default_channel_order=default_axes_order,
378
+ space_units=space_unit,
379
+ time_units=time_unit,
380
+ axes_setup=axes_setup,
381
+ allow_non_canonical_axes=allow_non_canonical_axes,
382
+ strict_canonical_order=strict_canonical_order,
383
+ )
384
+ base_scale = _compute_base_scale(
385
+ pixelsize=pixelsize,
386
+ z_spacing=z_spacing,
387
+ time_spacing=time_spacing,
388
+ axes_handler=axes_handler,
389
+ )
390
+ if levels is None:
391
+ levels_paths = tuple(str(i) for i in range(len(shapes)))
392
+ else:
393
+ levels_paths = tuple(levels)
394
+
395
+ pyramid_builder = ImagePyramidBuilder.from_shapes(
396
+ shapes=shapes,
397
+ base_scale=base_scale,
398
+ levels_paths=levels_paths,
399
+ axes=axes_handler.axes_names,
400
+ chunks=chunks,
401
+ data_type=dtype,
402
+ dimension_separator=dimension_separator,
403
+ compressors=compressors,
404
+ shards=shards,
405
+ zarr_format=2 if ngff_version == "0.4" else 3,
406
+ other_array_kwargs=extra_array_kwargs,
407
+ )
408
+ meta = meta_type.default_init(
409
+ levels=[p.path for p in pyramid_builder.levels],
410
+ axes_handler=axes_handler,
411
+ scales=[p.scale for p in pyramid_builder.levels],
412
+ translations=[None for _ in pyramid_builder.levels],
413
+ name=name,
414
+ version=ngff_version,
415
+ )
416
+ meta = _add_channels_meta(meta=meta, channels_meta=channels_meta)
417
+ # Keep this creation at the end to avoid partial creations on errors
418
+ return _create_image_like_group(
419
+ store=store,
420
+ pyramid_builder=pyramid_builder,
421
+ meta=meta,
422
+ overwrite=overwrite,
423
+ )