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