ngio 0.4.7__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 (57) 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/_datasets.py +6 -0
  49. ngio/utils/_zarr_utils.py +354 -236
  50. {ngio-0.4.7.dist-info → ngio-0.5.0.dist-info}/METADATA +13 -6
  51. ngio-0.5.0.dist-info/RECORD +88 -0
  52. ngio/images/_create.py +0 -276
  53. ngio/tables/backends/_non_zarr_backends.py +0 -196
  54. ngio/utils/_logger.py +0 -50
  55. ngio-0.4.7.dist-info/RECORD +0 -85
  56. {ngio-0.4.7.dist-info → ngio-0.5.0.dist-info}/WHEEL +0 -0
  57. {ngio-0.4.7.dist-info → ngio-0.5.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,32 +1,31 @@
1
1
  """HCS (High Content Screening) specific metadata classes for NGIO."""
2
2
 
3
+ import warnings
3
4
  from typing import Annotated
4
5
 
5
- from ome_zarr_models.v04.hcs import HCSAttrs
6
- from ome_zarr_models.v04.plate import (
6
+ from ome_zarr_models.common.plate import (
7
7
  Acquisition,
8
8
  Column,
9
- Plate,
9
+ PlateBase,
10
10
  Row,
11
11
  WellInPlate,
12
12
  )
13
- from ome_zarr_models.v04.well import WellAttrs as WellAttrs04
14
- from ome_zarr_models.v04.well_types import WellImage as WellImage04
15
- from ome_zarr_models.v04.well_types import WellMeta as WellMeta04
13
+ from ome_zarr_models.common.well_types import WellImage as WellImageCommon
16
14
  from pydantic import BaseModel, SkipValidation, field_serializer
17
15
 
18
16
  from ngio.ome_zarr_meta.ngio_specs._ngio_image import DefaultNgffVersion, NgffVersions
19
- from ngio.utils import NgioValueError, ngio_logger
17
+ from ngio.utils import NgioValueError
20
18
 
21
19
 
22
20
  def path_in_well_validation(path: str) -> str:
23
21
  """Validate the path in the well."""
24
22
  # Check if the value contains only alphanumeric characters
25
23
  if not path.isalnum():
26
- ngio_logger.warning(
24
+ warnings.warn(
27
25
  f"Path '{path}' contains non-alphanumeric characters. "
28
26
  "This may cause issues with some tools. "
29
- "Consider using only alphanumeric characters in the path."
27
+ "Consider using only alphanumeric characters in the path.",
28
+ stacklevel=2,
30
29
  )
31
30
  return path
32
31
 
@@ -41,7 +40,7 @@ class ImageInWellPath(BaseModel):
41
40
  acquisition_name: str | None = None
42
41
 
43
42
 
44
- class CustomWellImage(WellImage04):
43
+ class CustomWellImage(WellImageCommon):
45
44
  path: Annotated[str, SkipValidation]
46
45
 
47
46
  @field_serializer("path")
@@ -50,32 +49,29 @@ class CustomWellImage(WellImage04):
50
49
  return path_in_well_validation(value)
51
50
 
52
51
 
53
- class CustomWellMeta(WellMeta04):
54
- images: list[CustomWellImage] # type: ignore (override of WellMeta04.images)
55
-
52
+ class PlateWithVersion(PlateBase):
53
+ version: NgffVersions
56
54
 
57
- class CustomWellAttrs(WellAttrs04):
58
- well: CustomWellMeta # type: ignore (override of WellAttrs04.well)
59
55
 
60
-
61
- class NgioWellMeta(CustomWellAttrs):
56
+ class NgioWellMeta(BaseModel):
62
57
  """HCS well metadata."""
63
58
 
59
+ images: list[CustomWellImage] # type: ignore (override of WellMeta04.images)
60
+ version: NgffVersions
61
+
64
62
  @classmethod
65
63
  def default_init(
66
64
  cls,
67
- version: NgffVersions | None = None,
65
+ ngff_version: NgffVersions = DefaultNgffVersion,
68
66
  ) -> "NgioWellMeta":
69
- if version is None:
70
- version = DefaultNgffVersion
71
- well = cls(well=CustomWellMeta(images=[], version=version))
67
+ well = cls(images=[], version=ngff_version)
72
68
  return well
73
69
 
74
70
  @property
75
71
  def acquisition_ids(self) -> list[int]:
76
72
  """Return the acquisition ids in the well."""
77
73
  acquisitions = []
78
- for images in self.well.images:
74
+ for images in self.images:
79
75
  if (
80
76
  images.acquisition is not None
81
77
  and images.acquisition not in acquisitions
@@ -85,7 +81,7 @@ class NgioWellMeta(CustomWellAttrs):
85
81
 
86
82
  def get_image_acquisition_id(self, image_path: str) -> int | None:
87
83
  """Return the acquisition id for the given image path."""
88
- for images in self.well.images:
84
+ for images in self.images:
89
85
  if images.path == image_path:
90
86
  return images.acquisition
91
87
  raise NgioValueError(f"Image at path {image_path} not found in the well.")
@@ -100,11 +96,9 @@ class NgioWellMeta(CustomWellAttrs):
100
96
  acquisition (int | None): The acquisition id to filter the images.
101
97
  """
102
98
  if acquisition is None:
103
- return [images.path for images in self.well.images]
99
+ return [images.path for images in self.images]
104
100
  return [
105
- images.path
106
- for images in self.well.images
107
- if images.acquisition == acquisition
101
+ images.path for images in self.images if images.acquisition == acquisition
108
102
  ]
109
103
 
110
104
  def add_image(
@@ -118,7 +112,7 @@ class NgioWellMeta(CustomWellAttrs):
118
112
  strict (bool): If True, check if the image already exists in the well.
119
113
  If False, do not check if the image already exists in the well.
120
114
  """
121
- list_of_images = self.well.images
115
+ list_of_images = self.images
122
116
  for image in list_of_images:
123
117
  if image.path == path:
124
118
  raise NgioValueError(
@@ -137,9 +131,7 @@ class NgioWellMeta(CustomWellAttrs):
137
131
 
138
132
  new_image = CustomWellImage(path=path, acquisition=acquisition)
139
133
  list_of_images.append(new_image)
140
- return NgioWellMeta(
141
- well=CustomWellMeta(images=list_of_images, version=self.well.version)
142
- )
134
+ return NgioWellMeta(images=list_of_images, version=self.version)
143
135
 
144
136
  def remove_image(self, path: str) -> "NgioWellMeta":
145
137
  """Remove an image from the well.
@@ -147,35 +139,66 @@ class NgioWellMeta(CustomWellAttrs):
147
139
  Args:
148
140
  path (str): The path of the image.
149
141
  """
150
- list_of_images = self.well.images
142
+ list_of_images = self.images
151
143
  for image in list_of_images:
152
144
  if image.path == path:
153
145
  list_of_images.remove(image)
154
- return NgioWellMeta(
155
- well=CustomWellMeta(
156
- images=list_of_images, version=self.well.version
157
- )
158
- )
146
+ return NgioWellMeta(images=list_of_images, version=self.version)
159
147
  raise NgioValueError(f"Image at path {path} not found in the well.")
160
148
 
161
149
 
162
- def _stringify_column(column: str | int) -> str:
150
+ def _format_int_column(column: int, num_digits: int = 2) -> str:
151
+ """Format the column as a string.
152
+
153
+ We make sure to pad the column with zeros
154
+ to have a consistent format.
155
+
156
+ Args:
157
+ column (int): The column to format.
158
+ num_digits (int): The number of digits to pad the column.
159
+
160
+ Returns:
161
+ str: The column as a string.
162
+ """
163
+ return f"{column:0{num_digits}d}"
164
+
165
+
166
+ def _stringify_column(column: str | int, num_digits: int = 2) -> str:
163
167
  """Convert the column to a string.
164
168
 
169
+ This will ensure that columns are always strings.
170
+ and will help with sorting and comparison.
171
+
165
172
  Args:
166
173
  column (str | int): The column to convert.
174
+ num_digits (int): The number of digits to pad the column.
167
175
 
168
176
  Returns:
169
177
  str: The column as a string.
170
178
  """
171
- if isinstance(column, str):
172
- return column
179
+ if isinstance(column, int):
180
+ return _format_int_column(column, num_digits=num_digits)
173
181
 
174
- # Maybe we should pad the column with zeros
175
- return str(column)
182
+ elif isinstance(column, str):
183
+ try:
184
+ column_int = int(column)
185
+ return _format_int_column(column_int, num_digits=num_digits)
186
+ except ValueError:
187
+ pass
188
+ return column
189
+ raise NgioValueError(f"Column {column} must be a string or an integer.")
176
190
 
177
191
 
178
192
  def _find_row_index(rows: list[str], row: str) -> int | None:
193
+ """Find the index of a row in the list of rows.
194
+
195
+ Args:
196
+ rows: List of row names.
197
+ row: The row name to find.
198
+
199
+ Returns:
200
+ The index of the row, or None if not found.
201
+ """
179
202
  try:
180
203
  return rows.index(row)
181
204
  except ValueError:
@@ -183,8 +206,17 @@ def _find_row_index(rows: list[str], row: str) -> int | None:
183
206
 
184
207
 
185
208
  def _find_column_index(columns: list[str], column: str | int) -> int | None:
186
- _num_columns = [int(columns) for columns in columns]
187
- column = int(column)
209
+ """Find the index of a column in the list of columns.
210
+
211
+ Args:
212
+ columns: List of column names.
213
+ column: The column name or number to find.
214
+
215
+ Returns:
216
+ The index of the column, or None if not found.
217
+ """
218
+ _num_columns = [_stringify_column(columns) for columns in columns]
219
+ column = _stringify_column(column)
188
220
  try:
189
221
  return _num_columns.index(column)
190
222
  except ValueError:
@@ -194,6 +226,16 @@ def _find_column_index(columns: list[str], column: str | int) -> int | None:
194
226
  def _relabel_wells(
195
227
  wells: list[WellInPlate], rows: list[Row], columns: list[Column]
196
228
  ) -> list[WellInPlate]:
229
+ """Relabel well indices after rows or columns have been added.
230
+
231
+ Args:
232
+ wells: List of wells to relabel.
233
+ rows: Updated list of rows.
234
+ columns: Updated list of columns.
235
+
236
+ Returns:
237
+ List of wells with updated row and column indices.
238
+ """
197
239
  new_wells = []
198
240
  _rows = [row.name for row in rows]
199
241
  _columns = [column.name for column in columns]
@@ -218,26 +260,30 @@ def _relabel_wells(
218
260
  return new_wells
219
261
 
220
262
 
221
- class NgioPlateMeta(HCSAttrs):
263
+ class NgioPlateMeta(BaseModel):
222
264
  """HCS plate metadata."""
223
265
 
266
+ plate: PlateWithVersion
267
+ version: NgffVersions
268
+
224
269
  @classmethod
225
270
  def default_init(
226
271
  cls,
227
272
  images: list[ImageInWellPath] | None = None,
228
273
  name: str | None = None,
229
- version: NgffVersions | None = None,
274
+ ngff_version: NgffVersions = DefaultNgffVersion,
230
275
  ) -> "NgioPlateMeta":
231
276
  plate = cls(
232
- plate=Plate(
277
+ plate=PlateWithVersion(
233
278
  rows=[],
234
279
  columns=[],
235
280
  acquisitions=None,
236
281
  wells=[],
237
282
  field_count=None,
238
- version=version,
239
283
  name=name,
240
- )
284
+ version=ngff_version,
285
+ ),
286
+ version=ngff_version,
241
287
  )
242
288
 
243
289
  if images is None:
@@ -294,23 +340,8 @@ class NgioPlateMeta(HCSAttrs):
294
340
  Returns:
295
341
  str: The path of the well.
296
342
  """
297
- if row not in self.rows:
298
- raise NgioValueError(
299
- f"Row {row} not found in the plate. Available rows are {self.rows}."
300
- )
301
-
302
- row_idx = self.rows.index(row)
303
-
304
- _num_columns = [int(columns) for columns in self.columns]
305
-
306
- try:
307
- _column = int(column)
308
- except ValueError:
309
- raise NgioValueError(
310
- f"Column {column} must be an integer or convertible to an integer."
311
- ) from None
312
-
313
- column_idx = _num_columns.index(_column)
343
+ row_idx = _find_row_index(self.rows, row)
344
+ column_idx = _find_column_index(self.columns, column)
314
345
 
315
346
  for well in self.plate.wells:
316
347
  if well.columnIndex == column_idx and well.rowIndex == row_idx:
@@ -346,16 +377,16 @@ class NgioPlateMeta(HCSAttrs):
346
377
  else:
347
378
  wells = self.plate.wells
348
379
 
349
- new_plate = Plate(
380
+ new_plate = PlateWithVersion(
350
381
  rows=rows,
351
382
  columns=self.plate.columns,
352
383
  acquisitions=self.plate.acquisitions,
353
384
  wells=wells,
354
385
  field_count=self.plate.field_count,
355
386
  name=self.plate.name,
356
- version=self.plate.version,
387
+ version=self.version,
357
388
  )
358
- return NgioPlateMeta(plate=new_plate), row_idx
389
+ return NgioPlateMeta(plate=new_plate, version=self.version), row_idx
359
390
 
360
391
  def add_column(self, column: str | int) -> "tuple[NgioPlateMeta, int]":
361
392
  """Add a column to the plate.
@@ -373,7 +404,7 @@ class NgioPlateMeta(HCSAttrs):
373
404
 
374
405
  columns_names.append(_stringify_column(column))
375
406
  # sort as numbers
376
- columns_names.sort(key=lambda x: int(x))
407
+ columns_names.sort(key=lambda x: _stringify_column(x))
377
408
  column_idx = columns_names.index(_stringify_column(column))
378
409
  relabel_wells = True
379
410
 
@@ -384,27 +415,30 @@ class NgioPlateMeta(HCSAttrs):
384
415
  else:
385
416
  wells = self.plate.wells
386
417
 
387
- new_plate = Plate(
418
+ new_plate = PlateWithVersion(
388
419
  rows=self.plate.rows,
389
420
  columns=columns,
390
421
  acquisitions=self.plate.acquisitions,
391
422
  wells=wells,
392
423
  field_count=self.plate.field_count,
393
424
  name=self.plate.name,
394
- version=self.plate.version,
425
+ version=self.version,
395
426
  )
396
- return NgioPlateMeta(plate=new_plate), column_idx
427
+ return NgioPlateMeta(plate=new_plate, version=self.version), column_idx
397
428
 
398
429
  def add_well(
399
430
  self,
400
431
  row: str,
401
432
  column: str | int,
402
433
  ) -> "NgioPlateMeta":
403
- """Add an image to the well.
434
+ """Add a well to the plate.
404
435
 
405
436
  Args:
406
437
  row (str): The row of the well.
407
438
  column (str | int): The column of the well.
439
+
440
+ Returns:
441
+ NgioPlateMeta: Updated plate metadata with the new well.
408
442
  """
409
443
  plate, row_idx = self.add_row(row=row)
410
444
  plate, column_idx = plate.add_column(column=column)
@@ -422,16 +456,16 @@ class NgioPlateMeta(HCSAttrs):
422
456
  )
423
457
  )
424
458
 
425
- new_plate = Plate(
459
+ new_plate = PlateWithVersion(
426
460
  rows=plate.plate.rows,
427
461
  columns=plate.plate.columns,
428
462
  acquisitions=plate.plate.acquisitions,
429
463
  wells=wells,
430
464
  field_count=plate.plate.field_count,
431
465
  name=plate.plate.name,
432
- version=plate.plate.version,
466
+ version=plate.version,
433
467
  )
434
- return NgioPlateMeta(plate=new_plate)
468
+ return NgioPlateMeta(plate=new_plate, version=plate.version)
435
469
 
436
470
  def add_acquisition(
437
471
  self,
@@ -461,16 +495,16 @@ class NgioPlateMeta(HCSAttrs):
461
495
  Acquisition(id=acquisition_id, name=acquisition_name, **acquisition_kwargs)
462
496
  )
463
497
 
464
- new_plate = Plate(
498
+ new_plate = PlateWithVersion(
465
499
  rows=self.plate.rows,
466
500
  columns=self.plate.columns,
467
501
  acquisitions=acquisitions,
468
502
  wells=self.plate.wells,
469
503
  field_count=self.plate.field_count,
470
504
  name=self.plate.name,
471
- version=self.plate.version,
505
+ version=self.version,
472
506
  )
473
- return NgioPlateMeta(plate=new_plate)
507
+ return NgioPlateMeta(plate=new_plate, version=self.version)
474
508
 
475
509
  def remove_well(self, row: str, column: str | int) -> "NgioPlateMeta":
476
510
  """Remove a well from the plate.
@@ -497,30 +531,33 @@ class NgioPlateMeta(HCSAttrs):
497
531
  f"Well at row {row} and column {column} not found in the plate."
498
532
  )
499
533
 
500
- new_plate = Plate(
534
+ new_plate = PlateWithVersion(
501
535
  rows=self.plate.rows,
502
536
  columns=self.plate.columns,
503
537
  acquisitions=self.plate.acquisitions,
504
538
  wells=wells,
505
539
  field_count=self.plate.field_count,
506
540
  name=self.plate.name,
507
- version=self.plate.version,
541
+ version=self.version,
508
542
  )
509
- return NgioPlateMeta(plate=new_plate)
543
+ return NgioPlateMeta(plate=new_plate, version=self.version)
510
544
 
511
545
  def derive(
512
546
  self,
513
547
  name: str | None = None,
514
- version: NgffVersions | None = None,
548
+ ngff_version: NgffVersions | None = None,
515
549
  keep_acquisitions: bool = False,
516
550
  ) -> "NgioPlateMeta":
517
551
  """Derive the plate metadata.
518
552
 
519
553
  Args:
520
- name (str): The name of the derived plate.
521
- version (NgffVersion | None): The version of the derived plate.
554
+ name (str | None): The name of the derived plate.
555
+ ngff_version (NgffVersions | None): The version of the derived plate.
522
556
  If None, use the version of the original plate.
523
557
  keep_acquisitions (bool): If True, keep the acquisitions in the plate.
558
+
559
+ Returns:
560
+ NgioPlateMeta: The derived plate metadata.
524
561
  """
525
562
  columns = self.plate.columns
526
563
  rows = self.plate.rows
@@ -530,17 +567,18 @@ class NgioPlateMeta(HCSAttrs):
530
567
  else:
531
568
  acquisitions = None
532
569
 
533
- if version is None:
534
- version = self.plate.version # type: ignore (version is NgffVersions or None)
570
+ if ngff_version is None:
571
+ ngff_version = self.version
535
572
 
536
573
  return NgioPlateMeta(
537
- plate=Plate(
574
+ plate=PlateWithVersion(
538
575
  rows=rows,
539
576
  columns=columns,
540
577
  acquisitions=acquisitions,
541
578
  wells=[],
542
579
  field_count=self.plate.field_count,
543
- version=version,
544
580
  name=name,
545
- )
581
+ version=ngff_version,
582
+ ),
583
+ version=ngff_version,
546
584
  )
@@ -13,11 +13,11 @@ import numpy as np
13
13
  from pydantic import BaseModel
14
14
 
15
15
  from ngio.ome_zarr_meta.ngio_specs._axes import (
16
+ AxesHandler,
16
17
  DefaultSpaceUnit,
17
18
  DefaultTimeUnit,
18
19
  SpaceUnits,
19
20
  TimeUnits,
20
- build_canonical_axes_handler,
21
21
  )
22
22
  from ngio.ome_zarr_meta.ngio_specs._channels import ChannelsMeta
23
23
  from ngio.ome_zarr_meta.ngio_specs._dataset import Dataset
@@ -25,7 +25,7 @@ from ngio.ome_zarr_meta.ngio_specs._pixel_size import PixelSize
25
25
  from ngio.utils import NgioValidationError, NgioValueError
26
26
 
27
27
  T = TypeVar("T")
28
- NgffVersions = Literal["0.4"]
28
+ NgffVersions = Literal["0.4", "0.5"]
29
29
  DefaultNgffVersion: Literal["0.4"] = "0.4"
30
30
 
31
31
 
@@ -41,6 +41,13 @@ class ImageLabelSource(BaseModel):
41
41
  return cls(version=version, source={"image": "../../"})
42
42
 
43
43
 
44
+ class NgioLabelsGroupMeta(BaseModel):
45
+ """Metadata model for the /labels group in OME-NGFF."""
46
+
47
+ version: NgffVersions
48
+ labels: list[str]
49
+
50
+
44
51
  class AbstractNgioImageMeta:
45
52
  """Base class for ImageMeta and LabelMeta."""
46
53
 
@@ -66,42 +73,23 @@ class AbstractNgioImageMeta:
66
73
  @classmethod
67
74
  def default_init(
68
75
  cls,
69
- levels: int | Sequence[str],
70
- axes_names: Sequence[str],
71
- pixel_size: PixelSize,
72
- scaling_factors: Sequence[float] | None = None,
76
+ levels: Sequence[str],
77
+ axes_handler: AxesHandler,
78
+ scales: Sequence[tuple[float, ...]],
79
+ translations: Sequence[tuple[float, ...] | None],
73
80
  name: str | None = None,
74
81
  version: NgffVersions = DefaultNgffVersion,
75
82
  ):
76
83
  """Initialize the ImageMeta object."""
77
- axes_handler = build_canonical_axes_handler(
78
- axes_names,
79
- space_units=pixel_size.space_unit,
80
- time_units=pixel_size.time_unit,
81
- )
82
-
83
- px_size_dict = pixel_size.as_dict()
84
- scale = [px_size_dict.get(name, 1.0) for name in axes_handler.axes_names]
85
-
86
- if scaling_factors is None:
87
- _default = {"x": 2.0, "y": 2.0}
88
- scaling_factors = [
89
- _default.get(name, 1.0) for name in axes_handler.axes_names
90
- ]
91
-
92
- if isinstance(levels, int):
93
- levels = [str(i) for i in range(levels)]
94
-
95
84
  datasets = []
96
- for level in levels:
85
+ for level, scale, translation in zip(levels, scales, translations, strict=True):
97
86
  dataset = Dataset(
98
87
  path=level,
99
88
  axes_handler=axes_handler,
100
89
  scale=scale,
101
- translation=None,
90
+ translation=translation,
102
91
  )
103
92
  datasets.append(dataset)
104
- scale = [s * f for s, f in zip(scale, scaling_factors, strict=True)]
105
93
 
106
94
  return cls(
107
95
  version=version,
@@ -141,11 +129,58 @@ class AbstractNgioImageMeta:
141
129
  datasets=new_datasets,
142
130
  )
143
131
 
132
+ def rename_axes(self, axes_names: Sequence[str]):
133
+ """Rename axes in the metadata.
134
+
135
+ Args:
136
+ axes_names(Sequence[str]): The new axes names in the order of the current
137
+ axes.
138
+ """
139
+ new_axes_handler = self.axes_handler.rename_axes(axes_names=axes_names)
140
+ new_datasets = []
141
+ for dataset in self.datasets:
142
+ new_dataset = Dataset(
143
+ path=dataset.path,
144
+ axes_handler=new_axes_handler,
145
+ scale=dataset.scale,
146
+ translation=dataset.translation,
147
+ )
148
+ new_datasets.append(new_dataset)
149
+
150
+ return type(self)(
151
+ version=self.version,
152
+ name=self.name,
153
+ datasets=new_datasets,
154
+ )
155
+
156
+ def rename_image(self, name: str):
157
+ """Rename the image in the metadata.
158
+
159
+ Args:
160
+ name(str): The new name of the image.
161
+ """
162
+ return type(self)(
163
+ version=self.version,
164
+ name=name,
165
+ datasets=self.datasets,
166
+ )
167
+
144
168
  @property
145
169
  def version(self) -> NgffVersions:
146
170
  """Version of the OME-NFF metadata used to build the object."""
147
171
  return self._version # type: ignore (version is a Literal type)
148
172
 
173
+ @property
174
+ def zarr_format(self) -> Literal[2, 3]:
175
+ """Zarr version used to store the data."""
176
+ match self.version:
177
+ case "0.4":
178
+ return 2
179
+ case "0.5":
180
+ return 3
181
+ case _:
182
+ raise NgioValueError(f"Unsupported NGFF version: {self.version}")
183
+
149
184
  @property
150
185
  def name(self) -> str | None:
151
186
  """Name of the image."""
@@ -326,50 +361,15 @@ class AbstractNgioImageMeta:
326
361
  )
327
362
  return dataset, lr_dataset
328
363
 
329
- def scaling_factor(self, path: str | None = None) -> list[float]:
330
- """Get the scaling factors from a dataset to its lower resolution."""
331
- if self.levels == 1:
332
- return [1.0] * len(self.axes_handler.axes_names)
333
- dataset, lr_dataset = self._get_closest_datasets(path=path)
334
-
335
- scaling_factors = []
336
- for ax_name in self.axes_handler.axes_names:
337
- s_d = dataset.get_scale(ax_name)
338
- s_lr_d = lr_dataset.get_scale(ax_name)
339
- scaling_factors.append(s_lr_d / s_d)
340
- return scaling_factors
341
-
342
- def yx_scaling(self, path: str | None = None) -> tuple[float, float]:
343
- """Get the scaling factor from a dataset to its lower resolution."""
364
+ def scaling_factor(self, path: str | None = None) -> tuple[float, ...]:
365
+ """Get the scaling factors to downscale to the next lower resolution dataset."""
344
366
  if self.levels == 1:
345
- return 1.0, 1.0
367
+ return (1.0,) * len(self.axes_handler.axes_names)
346
368
  dataset, lr_dataset = self._get_closest_datasets(path=path)
347
-
348
- if lr_dataset is None:
349
- raise NgioValueError(
350
- "No lower resolution dataset found. "
351
- "This is the lowest resolution dataset."
352
- )
353
-
354
- s_d = dataset.get_scale("y")
355
- s_lr_d = lr_dataset.get_scale("y")
356
- scale_y = s_lr_d / s_d
357
-
358
- s_d = dataset.get_scale("x")
359
- s_lr_d = lr_dataset.get_scale("x")
360
- scale_x = s_lr_d / s_d
361
-
362
- return scale_y, scale_x
363
-
364
- def z_scaling(self, path: str | None = None) -> float:
365
- """Get the scaling factor from a dataset to its lower resolution."""
366
- if self.levels == 1:
367
- return 1.0
368
- dataset, lr_dataset = self._get_closest_datasets(path=path)
369
-
370
- s_d = dataset.get_scale("z", default=1.0)
371
- s_lr_d = lr_dataset.get_scale("z", default=1.0)
372
- return s_lr_d / s_d
369
+ scale = dataset.scale
370
+ lr_scale = lr_dataset.scale
371
+ scaling_factors = [s / s_lr for s_lr, s in zip(scale, lr_scale, strict=True)]
372
+ return tuple(scaling_factors)
373
373
 
374
374
 
375
375
  class NgioLabelMeta(AbstractNgioImageMeta):