ngio 0.2.1__py3-none-any.whl → 0.2.3__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 (38) hide show
  1. ngio/__init__.py +20 -2
  2. ngio/common/_pyramid.py +5 -1
  3. ngio/common/_roi.py +2 -2
  4. ngio/hcs/__init__.py +16 -2
  5. ngio/hcs/plate.py +496 -18
  6. ngio/images/abstract_image.py +11 -0
  7. ngio/images/create.py +25 -36
  8. ngio/images/image.py +80 -6
  9. ngio/images/label.py +38 -9
  10. ngio/images/ome_zarr_container.py +70 -33
  11. ngio/ome_zarr_meta/__init__.py +5 -3
  12. ngio/ome_zarr_meta/ngio_specs/__init__.py +10 -2
  13. ngio/ome_zarr_meta/ngio_specs/_axes.py +90 -65
  14. ngio/ome_zarr_meta/ngio_specs/_dataset.py +46 -8
  15. ngio/ome_zarr_meta/ngio_specs/_ngio_hcs.py +242 -70
  16. ngio/ome_zarr_meta/ngio_specs/_ngio_image.py +49 -11
  17. ngio/ome_zarr_meta/ngio_specs/_pixel_size.py +28 -11
  18. ngio/ome_zarr_meta/v04/_custom_models.py +18 -0
  19. ngio/ome_zarr_meta/v04/_v04_spec_utils.py +2 -2
  20. ngio/tables/_validators.py +1 -83
  21. ngio/tables/backends/__init__.py +27 -1
  22. ngio/tables/backends/_abstract_backend.py +207 -22
  23. ngio/tables/backends/_anndata_utils.py +3 -109
  24. ngio/tables/backends/_anndata_v1.py +43 -46
  25. ngio/tables/backends/_csv_v1.py +162 -0
  26. ngio/tables/backends/_json_v1.py +54 -18
  27. ngio/tables/backends/_table_backends.py +98 -18
  28. ngio/tables/backends/_utils.py +458 -0
  29. ngio/tables/tables_container.py +3 -1
  30. ngio/tables/v1/_feature_table.py +20 -11
  31. ngio/tables/v1/_generic_table.py +20 -15
  32. ngio/tables/v1/_roi_table.py +7 -9
  33. ngio/utils/_zarr_utils.py +46 -32
  34. {ngio-0.2.1.dist-info → ngio-0.2.3.dist-info}/METADATA +3 -1
  35. ngio-0.2.3.dist-info/RECORD +57 -0
  36. ngio-0.2.1.dist-info/RECORD +0 -54
  37. {ngio-0.2.1.dist-info → ngio-0.2.3.dist-info}/WHEEL +0 -0
  38. {ngio-0.2.1.dist-info → ngio-0.2.3.dist-info}/licenses/LICENSE +0 -0
ngio/images/create.py CHANGED
@@ -12,7 +12,10 @@ from ngio.ome_zarr_meta import (
12
12
  get_label_meta_handler,
13
13
  )
14
14
  from ngio.ome_zarr_meta.ngio_specs import (
15
- NgffVersion,
15
+ DefaultNgffVersion,
16
+ DefaultSpaceUnit,
17
+ DefaultTimeUnit,
18
+ NgffVersions,
16
19
  SpaceUnits,
17
20
  TimeUnits,
18
21
  canonical_axes_order,
@@ -32,10 +35,10 @@ def _init_generic_meta(
32
35
  levels: int | list[str] = 5,
33
36
  yx_scaling_factor: float | tuple[float, float] = 2.0,
34
37
  z_scaling_factor: float = 1.0,
35
- space_unit: SpaceUnits | str | None = None,
36
- time_unit: TimeUnits | str | None = None,
38
+ space_unit: SpaceUnits | str | None = DefaultSpaceUnit,
39
+ time_unit: TimeUnits | str | None = DefaultTimeUnit,
37
40
  name: str | None = None,
38
- version: NgffVersion = "0.4",
41
+ version: NgffVersions = DefaultNgffVersion,
39
42
  ) -> tuple[_image_or_label_meta, list[float]]:
40
43
  """Initialize the metadata for an image or label."""
41
44
  scaling_factors = []
@@ -55,20 +58,6 @@ def _init_generic_meta(
55
58
  else:
56
59
  scaling_factors.append(1.0)
57
60
 
58
- if space_unit is None:
59
- space_unit = SpaceUnits.micrometer
60
- elif isinstance(space_unit, str):
61
- space_unit = SpaceUnits(space_unit)
62
- elif not isinstance(space_unit, SpaceUnits):
63
- raise NgioValueError(f"space_unit can not be {type(space_unit)}.")
64
-
65
- if time_unit is None:
66
- time_unit = TimeUnits.seconds
67
- elif isinstance(time_unit, str):
68
- time_unit = TimeUnits(time_unit)
69
- elif not isinstance(time_unit, TimeUnits):
70
- raise NgioValueError(f"time_units can not be {type(time_unit)}.")
71
-
72
61
  pixel_sizes = PixelSize(
73
62
  x=pixelsize,
74
63
  y=pixelsize,
@@ -89,7 +78,7 @@ def _init_generic_meta(
89
78
  return meta, scaling_factors
90
79
 
91
80
 
92
- def _create_empty_label(
81
+ def create_empty_label_container(
93
82
  store: StoreOrGroup,
94
83
  shape: Collection[int],
95
84
  pixelsize: float,
@@ -98,14 +87,14 @@ def _create_empty_label(
98
87
  levels: int | list[str] = 5,
99
88
  yx_scaling_factor: float | tuple[float, float] = 2.0,
100
89
  z_scaling_factor: float = 1.0,
101
- space_unit: SpaceUnits | str | None = None,
102
- time_unit: TimeUnits | str | None = None,
90
+ space_unit: SpaceUnits | str | None = DefaultSpaceUnit,
91
+ time_unit: TimeUnits | str | None = DefaultTimeUnit,
103
92
  axes_names: Collection[str] | None = None,
104
93
  name: str | None = None,
105
94
  chunks: Collection[int] | None = None,
106
95
  dtype: str = "uint16",
107
96
  overwrite: bool = False,
108
- version: NgffVersion = "0.4",
97
+ version: NgffVersions = DefaultNgffVersion,
109
98
  ) -> ZarrGroupHandler:
110
99
  """Create an empty label with the given shape and metadata.
111
100
 
@@ -122,10 +111,10 @@ def _create_empty_label(
122
111
  dimensions. Defaults to 2.0.
123
112
  z_scaling_factor (float, optional): The down-scaling factor in z dimension.
124
113
  Defaults to 1.0.
125
- space_unit (SpaceUnits | str | None, optional): The unit of space. Defaults to
126
- None.
127
- time_unit (TimeUnits | str | None, optional): The unit of time. Defaults to
128
- None.
114
+ space_unit (SpaceUnits, optional): The unit of space. Defaults to
115
+ DefaultSpaceUnit.
116
+ time_unit (TimeUnits, optional): The unit of time. Defaults to
117
+ DefaultTimeUnit.
129
118
  axes_names (Collection[str] | None, optional): The names of the axes.
130
119
  If None the canonical names are used. Defaults to None.
131
120
  name (str | None, optional): The name of the image. Defaults to None.
@@ -135,7 +124,7 @@ def _create_empty_label(
135
124
  overwrite (bool, optional): Whether to overwrite an existing image.
136
125
  Defaults to True.
137
126
  version (str, optional): The version of the OME-Zarr specification.
138
- Defaults to "0.4".
127
+ Defaults to DefaultVersion.
139
128
 
140
129
  """
141
130
  if axes_names is None:
@@ -180,7 +169,7 @@ def _create_empty_label(
180
169
  return group_handler
181
170
 
182
171
 
183
- def _create_empty_image(
172
+ def create_empty_image_container(
184
173
  store: StoreOrGroup,
185
174
  shape: Collection[int],
186
175
  pixelsize: float,
@@ -189,14 +178,14 @@ def _create_empty_image(
189
178
  levels: int | list[str] = 5,
190
179
  yx_scaling_factor: float | tuple[float, float] = 2,
191
180
  z_scaling_factor: float = 1.0,
192
- space_unit: SpaceUnits | str | None = None,
193
- time_unit: TimeUnits | str | None = None,
181
+ space_unit: SpaceUnits | str | None = DefaultSpaceUnit,
182
+ time_unit: TimeUnits | str | None = DefaultTimeUnit,
194
183
  axes_names: Collection[str] | None = None,
195
184
  name: str | None = None,
196
185
  chunks: Collection[int] | None = None,
197
186
  dtype: str = "uint16",
198
187
  overwrite: bool = False,
199
- version: NgffVersion = "0.4",
188
+ version: NgffVersions = DefaultNgffVersion,
200
189
  ) -> ZarrGroupHandler:
201
190
  """Create an empty OME-Zarr image with the given shape and metadata.
202
191
 
@@ -213,10 +202,10 @@ def _create_empty_image(
213
202
  dimensions. Defaults to 2.0.
214
203
  z_scaling_factor (float, optional): The down-scaling factor in z dimension.
215
204
  Defaults to 1.0.
216
- space_unit (SpaceUnits | str | None, optional): The unit of space. Defaults to
217
- None.
218
- time_unit (TimeUnits | str | None, optional): The unit of time. Defaults to
219
- None.
205
+ space_unit (SpaceUnits, optional): The unit of space. Defaults to
206
+ DefaultSpaceUnit.
207
+ time_unit (TimeUnits, optional): The unit of time. Defaults to
208
+ DefaultTimeUnit.
220
209
  axes_names (Collection[str] | None, optional): The names of the axes.
221
210
  If None the canonical names are used. Defaults to None.
222
211
  name (str | None, optional): The name of the image. Defaults to None.
@@ -226,7 +215,7 @@ def _create_empty_image(
226
215
  overwrite (bool, optional): Whether to overwrite an existing image.
227
216
  Defaults to True.
228
217
  version (str, optional): The version of the OME-Zarr specification.
229
- Defaults to "0.4".
218
+ Defaults to DefaultVersion.
230
219
 
231
220
  """
232
221
  if axes_names is None:
ngio/images/image.py CHANGED
@@ -7,14 +7,22 @@ from dask import array as da
7
7
 
8
8
  from ngio.common import Dimensions
9
9
  from ngio.images.abstract_image import AbstractImage, consolidate_image
10
- from ngio.images.create import _create_empty_image
10
+ from ngio.images.create import create_empty_image_container
11
11
  from ngio.ome_zarr_meta import (
12
12
  ImageMetaHandler,
13
13
  NgioImageMeta,
14
14
  PixelSize,
15
15
  find_image_meta_handler,
16
16
  )
17
- from ngio.ome_zarr_meta.ngio_specs import Channel, ChannelsMeta, ChannelVisualisation
17
+ from ngio.ome_zarr_meta.ngio_specs import (
18
+ Channel,
19
+ ChannelsMeta,
20
+ ChannelVisualisation,
21
+ DefaultSpaceUnit,
22
+ DefaultTimeUnit,
23
+ SpaceUnits,
24
+ TimeUnits,
25
+ )
18
26
  from ngio.utils import (
19
27
  NgioValidationError,
20
28
  StoreOrGroup,
@@ -142,10 +150,12 @@ class ImagesContainer:
142
150
  image = self.get()
143
151
  return image.wavelength_ids
144
152
 
145
- def initialize_channel_meta(
153
+ def set_channel_meta(
146
154
  self,
147
155
  labels: Collection[str] | int | None = None,
148
156
  wavelength_id: Collection[str] | None = None,
157
+ start: Collection[float] | None = None,
158
+ end: Collection[float] | None = None,
149
159
  percentiles: tuple[float, float] | None = None,
150
160
  colors: Collection[str] | None = None,
151
161
  active: Collection[bool] | None = None,
@@ -158,6 +168,10 @@ class ImagesContainer:
158
168
  If an integer is provided, the channels will be named "channel_i".
159
169
  wavelength_id(Collection[str] | None): The wavelength ID of the channel.
160
170
  If None, the wavelength ID will be the same as the channel name.
171
+ start(Collection[float] | None): The start value for each channel.
172
+ If None, the start value will be computed from the image.
173
+ end(Collection[float] | None): The end value for each channel.
174
+ If None, the end value will be computed from the image.
161
175
  percentiles(tuple[float, float] | None): The start and end percentiles
162
176
  for each channel. If None, the percentiles will not be computed.
163
177
  colors(Collection[str, NgioColors] | None): The list of colors for the
@@ -169,12 +183,40 @@ class ImagesContainer:
169
183
  low_res_dataset = self.meta.get_lowest_resolution_dataset()
170
184
  ref_image = self.get(path=low_res_dataset.path)
171
185
 
186
+ if start is not None and end is None:
187
+ raise NgioValidationError(
188
+ "If start is provided, end must be provided as well."
189
+ )
190
+ if end is not None and start is None:
191
+ raise NgioValidationError(
192
+ "If end is provided, start must be provided as well."
193
+ )
194
+
195
+ if start is not None and percentiles is not None:
196
+ raise NgioValidationError(
197
+ "If start and end are provided, percentiles must be None."
198
+ )
199
+
172
200
  if percentiles is not None:
173
201
  start, end = compute_image_percentile(
174
202
  ref_image,
175
203
  start_percentile=percentiles[0],
176
204
  end_percentile=percentiles[1],
177
205
  )
206
+ elif start is not None and end is not None:
207
+ if len(start) != len(end):
208
+ raise NgioValidationError(
209
+ "The start and end lists must have the same length."
210
+ )
211
+ if len(start) != self.num_channels:
212
+ raise NgioValidationError(
213
+ "The start and end lists must have the same length as "
214
+ "the number of channels."
215
+ )
216
+
217
+ start = list(start)
218
+ end = list(end)
219
+
178
220
  else:
179
221
  start, end = None, None
180
222
 
@@ -196,7 +238,7 @@ class ImagesContainer:
196
238
  meta.set_channels_meta(channel_meta)
197
239
  self._meta_handler.write_meta(meta)
198
240
 
199
- def update_percentiles(
241
+ def set_channel_percentiles(
200
242
  self,
201
243
  start_percentile: float = 0.1,
202
244
  end_percentile: float = 99.9,
@@ -230,6 +272,21 @@ class ImagesContainer:
230
272
  meta.set_channels_meta(new_meta)
231
273
  self._meta_handler.write_meta(meta)
232
274
 
275
+ def set_axes_unit(
276
+ self,
277
+ space_unit: SpaceUnits = DefaultSpaceUnit,
278
+ time_unit: TimeUnits = DefaultTimeUnit,
279
+ ) -> None:
280
+ """Set the axes unit of the image.
281
+
282
+ Args:
283
+ space_unit (SpaceUnits): The space unit of the image.
284
+ time_unit (TimeUnits): The time unit of the image.
285
+ """
286
+ meta = self.meta
287
+ meta = meta.to_units(space_unit=space_unit, time_unit=time_unit)
288
+ self._meta_handler.write_meta(meta)
289
+
233
290
  def derive(
234
291
  self,
235
292
  store: StoreOrGroup,
@@ -238,6 +295,7 @@ class ImagesContainer:
238
295
  labels: Collection[str] | None = None,
239
296
  pixel_size: PixelSize | None = None,
240
297
  axes_names: Collection[str] | None = None,
298
+ name: str | None = None,
241
299
  chunks: Collection[int] | None = None,
242
300
  dtype: str | None = None,
243
301
  overwrite: bool = False,
@@ -252,6 +310,7 @@ class ImagesContainer:
252
310
  labels (Collection[str] | None): The labels of the new image.
253
311
  pixel_size (PixelSize | None): The pixel size of the new image.
254
312
  axes_names (Collection[str] | None): The axes names of the new image.
313
+ name (str | None): The name of the new image.
255
314
  chunks (Collection[int] | None): The chunk shape of the new image.
256
315
  dtype (str | None): The data type of the new image.
257
316
  overwrite (bool): Whether to overwrite an existing image.
@@ -267,6 +326,7 @@ class ImagesContainer:
267
326
  labels=labels,
268
327
  pixel_size=pixel_size,
269
328
  axes_names=axes_names,
329
+ name=name,
270
330
  chunks=chunks,
271
331
  dtype=dtype,
272
332
  overwrite=overwrite,
@@ -346,6 +406,7 @@ def derive_image_container(
346
406
  labels: Collection[str] | None = None,
347
407
  pixel_size: PixelSize | None = None,
348
408
  axes_names: Collection[str] | None = None,
409
+ name: str | None = None,
349
410
  chunks: Collection[int] | None = None,
350
411
  dtype: str | None = None,
351
412
  overwrite: bool = False,
@@ -360,6 +421,7 @@ def derive_image_container(
360
421
  labels (Collection[str] | None): The labels of the new image.
361
422
  pixel_size (PixelSize | None): The pixel size of the new image.
362
423
  axes_names (Collection[str] | None): The axes names of the new image.
424
+ name (str | None): The name of the new image.
363
425
  chunks (Collection[int] | None): The chunk shape of the new image.
364
426
  dtype (str | None): The data type of the new image.
365
427
  overwrite (bool): Whether to overwrite an existing image.
@@ -399,9 +461,12 @@ def derive_image_container(
399
461
  f"Got {chunks} for shape {shape}."
400
462
  )
401
463
 
464
+ if name is None:
465
+ name = ref_meta.name
466
+
402
467
  if dtype is None:
403
468
  dtype = ref_image.dtype
404
- handler = _create_empty_image(
469
+ handler = create_empty_image_container(
405
470
  store=store,
406
471
  shape=shape,
407
472
  pixelsize=pixel_size.x,
@@ -413,6 +478,7 @@ def derive_image_container(
413
478
  time_unit=pixel_size.time_unit,
414
479
  space_unit=pixel_size.space_unit,
415
480
  axes_names=axes_names,
481
+ name=name,
416
482
  chunks=chunks,
417
483
  dtype=dtype,
418
484
  overwrite=overwrite,
@@ -430,11 +496,17 @@ def derive_image_container(
430
496
  active = [
431
497
  c.channel_visualisation.active for c in ref_image._channels_meta.channels
432
498
  ]
499
+ start = [
500
+ c.channel_visualisation.start for c in ref_image._channels_meta.channels
501
+ ]
502
+ end = [c.channel_visualisation.end for c in ref_image._channels_meta.channels]
433
503
  else:
434
504
  _labels = None
435
505
  wavelength_id = None
436
506
  colors = None
437
507
  active = None
508
+ start = None
509
+ end = None
438
510
 
439
511
  if labels is not None:
440
512
  if len(labels) != image_container.num_channels:
@@ -443,11 +515,13 @@ def derive_image_container(
443
515
  )
444
516
  _labels = labels
445
517
 
446
- image_container.initialize_channel_meta(
518
+ image_container.set_channel_meta(
447
519
  labels=_labels,
448
520
  wavelength_id=wavelength_id,
449
521
  percentiles=None,
450
522
  colors=colors,
451
523
  active=active,
524
+ start=start,
525
+ end=end,
452
526
  )
453
527
  return image_container
ngio/images/label.py CHANGED
@@ -5,7 +5,7 @@ from typing import Literal
5
5
 
6
6
  from ngio.common import compute_masking_roi
7
7
  from ngio.images.abstract_image import AbstractImage, consolidate_image
8
- from ngio.images.create import _create_empty_label
8
+ from ngio.images.create import create_empty_label_container
9
9
  from ngio.images.image import Image
10
10
  from ngio.ome_zarr_meta import (
11
11
  LabelMetaHandler,
@@ -13,6 +13,12 @@ from ngio.ome_zarr_meta import (
13
13
  PixelSize,
14
14
  find_label_meta_handler,
15
15
  )
16
+ from ngio.ome_zarr_meta.ngio_specs import (
17
+ DefaultSpaceUnit,
18
+ DefaultTimeUnit,
19
+ SpaceUnits,
20
+ TimeUnits,
21
+ )
16
22
  from ngio.tables import MaskingRoiTable
17
23
  from ngio.utils import (
18
24
  NgioValidationError,
@@ -54,6 +60,21 @@ class Label(AbstractImage[LabelMetaHandler]):
54
60
  """Return the metadata."""
55
61
  return self._meta_handler.meta
56
62
 
63
+ def set_axes_unit(
64
+ self,
65
+ space_unit: SpaceUnits = DefaultSpaceUnit,
66
+ time_unit: TimeUnits = DefaultTimeUnit,
67
+ ) -> None:
68
+ """Set the axes unit of the image.
69
+
70
+ Args:
71
+ space_unit (SpaceUnits): The space unit of the image.
72
+ time_unit (TimeUnits): The time unit of the image.
73
+ """
74
+ meta = self.meta
75
+ meta = meta.to_units(space_unit=space_unit, time_unit=time_unit)
76
+ self._meta_handler.write_meta(meta)
77
+
57
78
  def build_masking_roi_table(self) -> MaskingRoiTable:
58
79
  """Compute the masking ROI table."""
59
80
  return build_masking_roi_table(self)
@@ -112,6 +133,12 @@ class LabelsContainer:
112
133
  closest pixel size level will be returned.
113
134
 
114
135
  """
136
+ if name not in self.list():
137
+ raise NgioValueError(
138
+ f"Label '{name}' not found in the Labels group. "
139
+ f"Available labels: {self.list()}"
140
+ )
141
+
115
142
  group_handler = self._group_handler.derive_handler(name)
116
143
  label_meta_handler = find_label_meta_handler(group_handler)
117
144
  path = label_meta_handler.meta.get_dataset(
@@ -122,7 +149,7 @@ class LabelsContainer:
122
149
  def derive(
123
150
  self,
124
151
  name: str,
125
- ref_image: Image,
152
+ ref_image: Image | Label,
126
153
  shape: Collection[int] | None = None,
127
154
  pixel_size: PixelSize | None = None,
128
155
  axes_names: Collection[str] | None = None,
@@ -136,7 +163,8 @@ class LabelsContainer:
136
163
 
137
164
  Args:
138
165
  store (StoreOrGroup): The Zarr store or group to create the image in.
139
- ref_image (Image): The reference image.
166
+ ref_image (Image | Label): A reference image that will be used to create
167
+ the new image.
140
168
  name (str): The name of the new image.
141
169
  shape (Collection[int] | None): The shape of the new image.
142
170
  pixel_size (PixelSize | None): The pixel size of the new image.
@@ -153,13 +181,13 @@ class LabelsContainer:
153
181
  existing_labels = self.list()
154
182
  if name in existing_labels and not overwrite:
155
183
  raise NgioValueError(
156
- f"Table '{name}' already exists in the group. "
184
+ f"Label '{name}' already exists in the group. "
157
185
  "Use overwrite=True to replace it."
158
186
  )
159
187
 
160
188
  label_group = self._group_handler.get_group(name, create_mode=True)
161
189
 
162
- _derive_label(
190
+ derive_label(
163
191
  store=label_group,
164
192
  ref_image=ref_image,
165
193
  name=name,
@@ -178,9 +206,9 @@ class LabelsContainer:
178
206
  return self.get(name)
179
207
 
180
208
 
181
- def _derive_label(
209
+ def derive_label(
182
210
  store: StoreOrGroup,
183
- ref_image: Image,
211
+ ref_image: Image | Label,
184
212
  name: str,
185
213
  shape: Collection[int] | None = None,
186
214
  pixel_size: PixelSize | None = None,
@@ -193,7 +221,8 @@ def _derive_label(
193
221
 
194
222
  Args:
195
223
  store (StoreOrGroup): The Zarr store or group to create the image in.
196
- ref_image (Image): The reference image.
224
+ ref_image (Image | Label): A reference image that will be used to
225
+ create the new image.
197
226
  name (str): The name of the new image.
198
227
  shape (Collection[int] | None): The shape of the new image.
199
228
  pixel_size (PixelSize | None): The pixel size of the new image.
@@ -253,7 +282,7 @@ def _derive_label(
253
282
  axes_names = list(axes_names)
254
283
  axes_names = axes_names[:c_axis] + axes_names[c_axis + 1 :]
255
284
 
256
- _ = _create_empty_label(
285
+ _ = create_empty_label_container(
257
286
  store=store,
258
287
  shape=shape,
259
288
  pixelsize=ref_image.pixel_size.x,