ngio 0.2.0a2__py3-none-any.whl → 0.2.0b1__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 (50) hide show
  1. ngio/__init__.py +4 -4
  2. ngio/common/__init__.py +12 -2
  3. ngio/common/_array_pipe.py +106 -0
  4. ngio/common/_axes_transforms.py +3 -2
  5. ngio/common/_dimensions.py +7 -0
  6. ngio/common/_masking_roi.py +158 -0
  7. ngio/common/_pyramid.py +16 -11
  8. ngio/common/_roi.py +74 -0
  9. ngio/common/_slicer.py +1 -2
  10. ngio/common/_zoom.py +5 -3
  11. ngio/hcs/__init__.py +2 -57
  12. ngio/hcs/plate.py +399 -0
  13. ngio/images/abstract_image.py +97 -28
  14. ngio/images/create.py +48 -29
  15. ngio/images/image.py +121 -57
  16. ngio/images/label.py +131 -86
  17. ngio/images/masked_image.py +259 -0
  18. ngio/images/omezarr_container.py +250 -77
  19. ngio/ome_zarr_meta/__init__.py +25 -13
  20. ngio/ome_zarr_meta/_meta_handlers.py +718 -69
  21. ngio/ome_zarr_meta/ngio_specs/__init__.py +8 -0
  22. ngio/ome_zarr_meta/ngio_specs/_channels.py +11 -0
  23. ngio/ome_zarr_meta/ngio_specs/_ngio_hcs.py +374 -2
  24. ngio/ome_zarr_meta/ngio_specs/_ngio_image.py +174 -113
  25. ngio/ome_zarr_meta/ngio_specs/_pixel_size.py +35 -3
  26. ngio/ome_zarr_meta/v04/__init__.py +17 -5
  27. ngio/ome_zarr_meta/v04/_v04_spec_utils.py +85 -12
  28. ngio/tables/__init__.py +2 -0
  29. ngio/tables/_validators.py +2 -4
  30. ngio/tables/backends/_anndata_utils.py +2 -1
  31. ngio/tables/backends/_anndata_v1.py +2 -1
  32. ngio/tables/backends/_json_v1.py +1 -1
  33. ngio/tables/tables_container.py +12 -2
  34. ngio/tables/v1/__init__.py +1 -2
  35. ngio/tables/v1/_feature_table.py +7 -5
  36. ngio/tables/v1/_generic_table.py +65 -11
  37. ngio/tables/v1/_roi_table.py +145 -27
  38. ngio/utils/__init__.py +3 -0
  39. ngio/utils/_datasets.py +4 -2
  40. ngio/utils/_fractal_fsspec_store.py +13 -0
  41. ngio/utils/_logger.py +3 -1
  42. ngio/utils/_zarr_utils.py +25 -2
  43. {ngio-0.2.0a2.dist-info → ngio-0.2.0b1.dist-info}/METADATA +4 -1
  44. ngio-0.2.0b1.dist-info/RECORD +54 -0
  45. ngio/ome_zarr_meta/_generic_handlers.py +0 -320
  46. ngio/ome_zarr_meta/v04/_meta_handlers.py +0 -54
  47. ngio/tables/v1/_masking_roi_table.py +0 -175
  48. ngio-0.2.0a2.dist-info/RECORD +0 -53
  49. {ngio-0.2.0a2.dist-info → ngio-0.2.0b1.dist-info}/WHEEL +0 -0
  50. {ngio-0.2.0a2.dist-info → ngio-0.2.0b1.dist-info}/licenses/LICENSE +0 -0
ngio/images/create.py CHANGED
@@ -5,11 +5,11 @@ from typing import TypeVar
5
5
 
6
6
  from ngio.common._pyramid import init_empty_pyramid
7
7
  from ngio.ome_zarr_meta import (
8
- ImplementedImageMetaHandlers,
9
- ImplementedLabelMetaHandlers,
10
8
  NgioImageMeta,
11
9
  NgioLabelMeta,
12
10
  PixelSize,
11
+ get_image_meta_handler,
12
+ get_label_meta_handler,
13
13
  )
14
14
  from ngio.ome_zarr_meta.ngio_specs import (
15
15
  SpaceUnits,
@@ -17,19 +17,19 @@ from ngio.ome_zarr_meta.ngio_specs import (
17
17
  canonical_axes_order,
18
18
  canonical_label_axes_order,
19
19
  )
20
- from ngio.utils import StoreOrGroup, ZarrGroupHandler
20
+ from ngio.utils import NgioValueError, StoreOrGroup, ZarrGroupHandler
21
21
 
22
22
  _image_or_label_meta = TypeVar("_image_or_label_meta", NgioImageMeta, NgioLabelMeta)
23
23
 
24
24
 
25
25
  def _init_generic_meta(
26
26
  meta_type: type[_image_or_label_meta],
27
- xy_pixelsize: float,
27
+ pixelsize: float,
28
28
  axes_names: Collection[str],
29
29
  z_spacing: float = 1.0,
30
30
  time_spacing: float = 1.0,
31
31
  levels: int | list[str] = 5,
32
- xy_scaling_factor: float = 2.0,
32
+ yx_scaling_factor: float | tuple[float, float] = 2.0,
33
33
  z_scaling_factor: float = 1.0,
34
34
  space_unit: SpaceUnits | str | None = None,
35
35
  time_unit: TimeUnits | str | None = None,
@@ -41,8 +41,16 @@ def _init_generic_meta(
41
41
  for ax in axes_names:
42
42
  if ax == "z":
43
43
  scaling_factors.append(z_scaling_factor)
44
- elif ax in ["x", "y"]:
45
- scaling_factors.append(xy_scaling_factor)
44
+ elif ax in ["x"]:
45
+ if isinstance(yx_scaling_factor, tuple):
46
+ scaling_factors.append(yx_scaling_factor[1])
47
+ else:
48
+ scaling_factors.append(yx_scaling_factor)
49
+ elif ax in ["y"]:
50
+ if isinstance(yx_scaling_factor, tuple):
51
+ scaling_factors.append(yx_scaling_factor[0])
52
+ else:
53
+ scaling_factors.append(yx_scaling_factor)
46
54
  else:
47
55
  scaling_factors.append(1.0)
48
56
 
@@ -51,18 +59,18 @@ def _init_generic_meta(
51
59
  elif isinstance(space_unit, str):
52
60
  space_unit = SpaceUnits(space_unit)
53
61
  elif not isinstance(space_unit, SpaceUnits):
54
- raise ValueError(f"space_unit can not be {type(space_unit)}.")
62
+ raise NgioValueError(f"space_unit can not be {type(space_unit)}.")
55
63
 
56
64
  if time_unit is None:
57
65
  time_unit = TimeUnits.seconds
58
66
  elif isinstance(time_unit, str):
59
67
  time_unit = TimeUnits(time_unit)
60
68
  elif not isinstance(time_unit, TimeUnits):
61
- raise ValueError(f"time_units can not be {type(time_unit)}.")
69
+ raise NgioValueError(f"time_units can not be {type(time_unit)}.")
62
70
 
63
71
  pixel_sizes = PixelSize(
64
- x=xy_pixelsize,
65
- y=xy_pixelsize,
72
+ x=pixelsize,
73
+ y=pixelsize,
66
74
  z=z_spacing,
67
75
  t=time_spacing,
68
76
  space_unit=space_unit,
@@ -83,11 +91,11 @@ def _init_generic_meta(
83
91
  def _create_empty_label(
84
92
  store: StoreOrGroup,
85
93
  shape: Collection[int],
86
- xy_pixelsize: float,
94
+ pixelsize: float,
87
95
  z_spacing: float = 1.0,
88
96
  time_spacing: float = 1.0,
89
97
  levels: int | list[str] = 5,
90
- xy_scaling_factor: float = 2.0,
98
+ yx_scaling_factor: float | tuple[float, float] = 2.0,
91
99
  z_scaling_factor: float = 1.0,
92
100
  space_unit: SpaceUnits | str | None = None,
93
101
  time_unit: TimeUnits | str | None = None,
@@ -103,13 +111,13 @@ def _create_empty_label(
103
111
  Args:
104
112
  store (StoreOrGroup): The Zarr store or group to create the image in.
105
113
  shape (Collection[int]): The shape of the image.
106
- xy_pixelsize (float): The pixel size in x and y dimensions.
114
+ pixelsize (float): The pixel size in x and y dimensions.
107
115
  z_spacing (float, optional): The spacing between z slices. Defaults to 1.0.
108
116
  time_spacing (float, optional): The spacing between time points.
109
117
  Defaults to 1.0.
110
118
  levels (int | list[str], optional): The number of levels in the pyramid or a
111
119
  list of level names. Defaults to 5.
112
- xy_scaling_factor (float, optional): The down-scaling factor in x and y
120
+ yx_scaling_factor (float, optional): The down-scaling factor in x and y
113
121
  dimensions. Defaults to 2.0.
114
122
  z_scaling_factor (float, optional): The down-scaling factor in z dimension.
115
123
  Defaults to 1.0.
@@ -132,13 +140,19 @@ def _create_empty_label(
132
140
  if axes_names is None:
133
141
  axes_names = canonical_label_axes_order()[-len(shape) :]
134
142
 
143
+ if len(axes_names) != len(shape):
144
+ raise NgioValueError(
145
+ f"Number of axes names {axes_names} does not match the number of "
146
+ f"dimensions {shape}."
147
+ )
148
+
135
149
  meta, scaling_factors = _init_generic_meta(
136
150
  meta_type=NgioLabelMeta,
137
- xy_pixelsize=xy_pixelsize,
151
+ pixelsize=pixelsize,
138
152
  z_spacing=z_spacing,
139
153
  time_spacing=time_spacing,
140
154
  levels=levels,
141
- xy_scaling_factor=xy_scaling_factor,
155
+ yx_scaling_factor=yx_scaling_factor,
142
156
  z_scaling_factor=z_scaling_factor,
143
157
  space_unit=space_unit,
144
158
  time_unit=time_unit,
@@ -149,9 +163,7 @@ def _create_empty_label(
149
163
 
150
164
  mode = "w" if overwrite else "w-"
151
165
  group_handler = ZarrGroupHandler(store=store, mode=mode, cache=False)
152
- image_handler = ImplementedLabelMetaHandlers().get_handler(
153
- version=version, group_handler=group_handler
154
- )
166
+ image_handler = get_label_meta_handler(version=version, group_handler=group_handler)
155
167
  image_handler.write_meta(meta)
156
168
 
157
169
  init_empty_pyramid(
@@ -163,17 +175,18 @@ def _create_empty_label(
163
175
  dtype=dtype,
164
176
  mode="a",
165
177
  )
178
+ group_handler._mode = "r+"
166
179
  return group_handler
167
180
 
168
181
 
169
182
  def _create_empty_image(
170
183
  store: StoreOrGroup,
171
184
  shape: Collection[int],
172
- xy_pixelsize: float,
185
+ pixelsize: float,
173
186
  z_spacing: float = 1.0,
174
187
  time_spacing: float = 1.0,
175
188
  levels: int | list[str] = 5,
176
- xy_scaling_factor: float = 2,
189
+ yx_scaling_factor: float | tuple[float, float] = 2,
177
190
  z_scaling_factor: float = 1.0,
178
191
  space_unit: SpaceUnits | str | None = None,
179
192
  time_unit: TimeUnits | str | None = None,
@@ -189,13 +202,13 @@ def _create_empty_image(
189
202
  Args:
190
203
  store (StoreOrGroup): The Zarr store or group to create the image in.
191
204
  shape (Collection[int]): The shape of the image.
192
- xy_pixelsize (float): The pixel size in x and y dimensions.
205
+ pixelsize (float): The pixel size in x and y dimensions.
193
206
  z_spacing (float, optional): The spacing between z slices. Defaults to 1.0.
194
207
  time_spacing (float, optional): The spacing between time points.
195
208
  Defaults to 1.0.
196
209
  levels (int | list[str], optional): The number of levels in the pyramid or a
197
210
  list of level names. Defaults to 5.
198
- xy_scaling_factor (float, optional): The down-scaling factor in x and y
211
+ yx_scaling_factor (float, optional): The down-scaling factor in x and y
199
212
  dimensions. Defaults to 2.0.
200
213
  z_scaling_factor (float, optional): The down-scaling factor in z dimension.
201
214
  Defaults to 1.0.
@@ -218,13 +231,19 @@ def _create_empty_image(
218
231
  if axes_names is None:
219
232
  axes_names = canonical_axes_order()[-len(shape) :]
220
233
 
234
+ if len(axes_names) != len(shape):
235
+ raise NgioValueError(
236
+ f"Number of axes names {axes_names} does not match the number of "
237
+ f"dimensions {shape}."
238
+ )
239
+
221
240
  meta, scaling_factors = _init_generic_meta(
222
241
  meta_type=NgioImageMeta,
223
- xy_pixelsize=xy_pixelsize,
242
+ pixelsize=pixelsize,
224
243
  z_spacing=z_spacing,
225
244
  time_spacing=time_spacing,
226
245
  levels=levels,
227
- xy_scaling_factor=xy_scaling_factor,
246
+ yx_scaling_factor=yx_scaling_factor,
228
247
  z_scaling_factor=z_scaling_factor,
229
248
  space_unit=space_unit,
230
249
  time_unit=time_unit,
@@ -234,9 +253,7 @@ def _create_empty_image(
234
253
  )
235
254
  mode = "w" if overwrite else "w-"
236
255
  group_handler = ZarrGroupHandler(store=store, mode=mode, cache=False)
237
- image_handler = ImplementedImageMetaHandlers().get_handler(
238
- version=version, group_handler=group_handler
239
- )
256
+ image_handler = get_image_meta_handler(version=version, group_handler=group_handler)
240
257
  image_handler.write_meta(meta)
241
258
 
242
259
  init_empty_pyramid(
@@ -248,4 +265,6 @@ def _create_empty_image(
248
265
  dtype=dtype,
249
266
  mode="a",
250
267
  )
268
+
269
+ group_handler._mode = "r+"
251
270
  return group_handler
ngio/images/image.py CHANGED
@@ -10,9 +10,9 @@ from ngio.images.abstract_image import AbstractImage, consolidate_image
10
10
  from ngio.images.create import _create_empty_image
11
11
  from ngio.ome_zarr_meta import (
12
12
  ImageMetaHandler,
13
- ImplementedImageMetaHandlers,
14
13
  NgioImageMeta,
15
14
  PixelSize,
15
+ find_image_meta_handler,
16
16
  )
17
17
  from ngio.ome_zarr_meta.ngio_specs import Channel, ChannelsMeta, ChannelVisualisation
18
18
  from ngio.utils import (
@@ -60,9 +60,7 @@ class Image(AbstractImage[ImageMetaHandler]):
60
60
 
61
61
  """
62
62
  if meta_handler is None:
63
- meta_handler = ImplementedImageMetaHandlers().find_meta_handler(
64
- group_handler
65
- )
63
+ meta_handler = find_image_meta_handler(group_handler)
66
64
  super().__init__(
67
65
  group_handler=group_handler, path=path, meta_handler=meta_handler
68
66
  )
@@ -109,9 +107,7 @@ class ImagesContainer:
109
107
  def __init__(self, group_handler: ZarrGroupHandler) -> None:
110
108
  """Initialize the LabelGroupHandler."""
111
109
  self._group_handler = group_handler
112
- self._meta_handler = ImplementedImageMetaHandlers().find_meta_handler(
113
- group_handler
114
- )
110
+ self._meta_handler = find_image_meta_handler(group_handler)
115
111
 
116
112
  @property
117
113
  def meta(self) -> NgioImageMeta:
@@ -170,17 +166,20 @@ class ImagesContainer:
170
166
  be shown by default.
171
167
  omero_kwargs(dict): Extra fields to store in the omero attributes.
172
168
  """
173
- ref = self.get()
169
+ low_res_dataset = self.meta.get_lowest_resolution_dataset()
170
+ ref_image = self.get(path=low_res_dataset.path)
174
171
 
175
172
  if percentiles is not None:
176
173
  start, end = compute_image_percentile(
177
- ref, start_percentile=percentiles[0], end_percentile=percentiles[1]
174
+ ref_image,
175
+ start_percentile=percentiles[0],
176
+ end_percentile=percentiles[1],
178
177
  )
179
178
  else:
180
179
  start, end = None, None
181
180
 
182
181
  if labels is None:
183
- labels = ref.num_channels
182
+ labels = ref_image.num_channels
184
183
 
185
184
  channel_meta = ChannelsMeta.default_init(
186
185
  labels=labels,
@@ -189,7 +188,7 @@ class ImagesContainer:
189
188
  start=start,
190
189
  end=end,
191
190
  active=active,
192
- data_type=ref.dtype,
191
+ data_type=ref_image.dtype,
193
192
  **omero_kwargs,
194
193
  )
195
194
 
@@ -206,9 +205,10 @@ class ImagesContainer:
206
205
  if self.meta._channels_meta is None:
207
206
  raise NgioValidationError("The channels meta is not initialized.")
208
207
 
209
- image = self.get()
208
+ low_res_dataset = self.meta.get_lowest_resolution_dataset()
209
+ ref_image = self.get(path=low_res_dataset.path)
210
210
  starts, ends = compute_image_percentile(
211
- image, start_percentile=start_percentile, end_percentile=end_percentile
211
+ ref_image, start_percentile=start_percentile, end_percentile=end_percentile
212
212
  )
213
213
 
214
214
  channels = []
@@ -235,20 +235,40 @@ class ImagesContainer:
235
235
  store: StoreOrGroup,
236
236
  ref_path: str | None = None,
237
237
  shape: Collection[int] | None = None,
238
+ labels: Collection[str] | None = None,
239
+ pixel_size: PixelSize | None = None,
240
+ axes_names: Collection[str] | None = None,
238
241
  chunks: Collection[int] | None = None,
239
- xy_scaling_factor: float = 2.0,
240
- z_scaling_factor: float = 1.0,
242
+ dtype: str | None = None,
241
243
  overwrite: bool = False,
242
244
  ) -> "ImagesContainer":
243
- """Create an OME-Zarr image from a numpy array."""
245
+ """Create an empty OME-Zarr image from an existing image.
246
+
247
+ Args:
248
+ store (StoreOrGroup): The Zarr store or group to create the image in.
249
+ ref_path (str | None): The path to the reference image in
250
+ the image container.
251
+ shape (Collection[int] | None): The shape of the new image.
252
+ labels (Collection[str] | None): The labels of the new image.
253
+ pixel_size (PixelSize | None): The pixel size of the new image.
254
+ axes_names (Collection[str] | None): The axes names of the new image.
255
+ chunks (Collection[int] | None): The chunk shape of the new image.
256
+ dtype (str | None): The data type of the new image.
257
+ overwrite (bool): Whether to overwrite an existing image.
258
+
259
+ Returns:
260
+ ImagesContainer: The new image
261
+ """
244
262
  return derive_image_container(
245
263
  image_container=self,
246
264
  store=store,
247
265
  ref_path=ref_path,
248
266
  shape=shape,
267
+ labels=labels,
268
+ pixel_size=pixel_size,
269
+ axes_names=axes_names,
249
270
  chunks=chunks,
250
- xy_scaling_factor=xy_scaling_factor,
251
- z_scaling_factor=z_scaling_factor,
271
+ dtype=dtype,
252
272
  overwrite=overwrite,
253
273
  )
254
274
 
@@ -256,13 +276,20 @@ class ImagesContainer:
256
276
  self,
257
277
  path: str | None = None,
258
278
  pixel_size: PixelSize | None = None,
259
- highest_resolution: bool = True,
279
+ strict: bool = False,
260
280
  ) -> Image:
261
- """Get an image at a specific level."""
262
- if path is not None or pixel_size is not None:
263
- highest_resolution = False
281
+ """Get an image at a specific level.
282
+
283
+ Args:
284
+ path (str | None): The path to the image in the omezarr file.
285
+ pixel_size (PixelSize | None): The pixel size of the image.
286
+ strict (bool): Only used if the pixel size is provided. If True, the
287
+ pixel size must match the image pixel size exactly. If False, the
288
+ closest pixel size level will be returned.
289
+
290
+ """
264
291
  dataset = self._meta_handler.meta.get_dataset(
265
- path=path, pixel_size=pixel_size, highest_resolution=highest_resolution
292
+ path=path, pixel_size=pixel_size, strict=strict
266
293
  )
267
294
  return Image(
268
295
  group_handler=self._group_handler,
@@ -308,7 +335,6 @@ def compute_image_percentile(
308
335
 
309
336
  starts.append(float(_s_perc))
310
337
  ends.append(float(_e_perc))
311
-
312
338
  return starts, ends
313
339
 
314
340
 
@@ -317,12 +343,31 @@ def derive_image_container(
317
343
  store: StoreOrGroup,
318
344
  ref_path: str | None = None,
319
345
  shape: Collection[int] | None = None,
346
+ labels: Collection[str] | None = None,
347
+ pixel_size: PixelSize | None = None,
348
+ axes_names: Collection[str] | None = None,
320
349
  chunks: Collection[int] | None = None,
321
- xy_scaling_factor: float = 2.0,
322
- z_scaling_factor: float = 1.0,
350
+ dtype: str | None = None,
323
351
  overwrite: bool = False,
324
352
  ) -> ImagesContainer:
325
- """Create an OME-Zarr image from a numpy array."""
353
+ """Create an empty OME-Zarr image from an existing image.
354
+
355
+ Args:
356
+ image_container (ImagesContainer): The image container to derive the new image.
357
+ store (StoreOrGroup): The Zarr store or group to create the image in.
358
+ ref_path (str | None): The path to the reference image in the image container.
359
+ shape (Collection[int] | None): The shape of the new image.
360
+ labels (Collection[str] | None): The labels of the new image.
361
+ pixel_size (PixelSize | None): The pixel size of the new image.
362
+ axes_names (Collection[str] | None): The axes names of the new image.
363
+ chunks (Collection[int] | None): The chunk shape of the new image.
364
+ dtype (str | None): The data type of the new image.
365
+ overwrite (bool): Whether to overwrite an existing image.
366
+
367
+ Returns:
368
+ ImagesContainer: The new image
369
+
370
+ """
326
371
  if ref_path is None:
327
372
  ref_image = image_container.get()
328
373
  else:
@@ -332,58 +377,77 @@ def derive_image_container(
332
377
 
333
378
  if shape is None:
334
379
  shape = ref_image.shape
335
- else:
336
- if len(shape) != len(ref_image.shape):
337
- raise NgioValidationError(
338
- "The shape of the new image does not match the reference image."
339
- )
380
+
381
+ if pixel_size is None:
382
+ pixel_size = ref_image.pixel_size
383
+
384
+ if axes_names is None:
385
+ axes_names = ref_meta.axes_mapper.on_disk_axes_names
386
+
387
+ if len(axes_names) != len(shape):
388
+ raise NgioValidationError(
389
+ "The axes names of the new image does not match the reference image."
390
+ f"Got {axes_names} for shape {shape}."
391
+ )
340
392
 
341
393
  if chunks is None:
342
394
  chunks = ref_image.chunks
343
- else:
344
- if len(chunks) != len(ref_image.chunks):
345
- raise NgioValidationError(
346
- "The chunks of the new image does not match the reference image."
347
- )
348
395
 
396
+ if len(chunks) != len(shape):
397
+ raise NgioValidationError(
398
+ "The chunks of the new image does not match the reference image."
399
+ f"Got {chunks} for shape {shape}."
400
+ )
401
+
402
+ if dtype is None:
403
+ dtype = ref_image.dtype
349
404
  handler = _create_empty_image(
350
405
  store=store,
351
406
  shape=shape,
352
- xy_pixelsize=ref_image.pixel_size.x,
353
- z_spacing=ref_image.pixel_size.z,
354
- time_spacing=ref_image.pixel_size.t,
407
+ pixelsize=pixel_size.x,
408
+ z_spacing=pixel_size.z,
409
+ time_spacing=pixel_size.t,
355
410
  levels=ref_meta.levels,
356
- xy_scaling_factor=xy_scaling_factor,
357
- z_scaling_factor=z_scaling_factor,
358
- time_unit=ref_image.pixel_size.time_unit,
359
- space_unit=ref_image.pixel_size.space_unit,
360
- axes_names=ref_image.dataset.axes_mapper.on_disk_axes_names,
411
+ yx_scaling_factor=ref_meta.yx_scaling(),
412
+ z_scaling_factor=ref_meta.z_scaling(),
413
+ time_unit=pixel_size.time_unit,
414
+ space_unit=pixel_size.space_unit,
415
+ axes_names=axes_names,
361
416
  chunks=chunks,
362
- dtype=ref_image.dtype,
417
+ dtype=dtype,
363
418
  overwrite=overwrite,
364
419
  version=ref_meta.version,
365
420
  )
366
-
367
421
  image_container = ImagesContainer(handler)
368
422
 
369
423
  if ref_image.num_channels == image_container.num_channels:
370
- labels = ref_image.channel_labels
424
+ _labels = ref_image.channel_labels
371
425
  wavelength_id = ref_image.wavelength_ids
426
+
372
427
  colors = [
373
428
  c.channel_visualisation.color for c in ref_image._channels_meta.channels
374
429
  ]
375
430
  active = [
376
431
  c.channel_visualisation.active for c in ref_image._channels_meta.channels
377
432
  ]
378
-
379
- image_container.initialize_channel_meta(
380
- labels=labels,
381
- wavelength_id=wavelength_id,
382
- percentiles=None,
383
- colors=colors,
384
- active=active,
385
- )
386
433
  else:
387
- image_container.initialize_channel_meta()
434
+ _labels = None
435
+ wavelength_id = None
436
+ colors = None
437
+ active = None
388
438
 
439
+ if labels is not None:
440
+ if len(labels) != image_container.num_channels:
441
+ raise NgioValidationError(
442
+ "The number of labels does not match the number of channels."
443
+ )
444
+ _labels = labels
445
+
446
+ image_container.initialize_channel_meta(
447
+ labels=_labels,
448
+ wavelength_id=wavelength_id,
449
+ percentiles=None,
450
+ colors=colors,
451
+ active=active,
452
+ )
389
453
  return image_container