ngio 0.3.0a0__py3-none-any.whl → 0.3.1__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 (36) hide show
  1. ngio/common/_array_pipe.py +50 -27
  2. ngio/common/_table_ops.py +1 -1
  3. ngio/hcs/__init__.py +1 -1
  4. ngio/hcs/{plate.py → _plate.py} +27 -12
  5. ngio/images/__init__.py +3 -3
  6. ngio/images/{image.py → _image.py} +26 -21
  7. ngio/images/{label.py → _label.py} +6 -4
  8. ngio/images/{masked_image.py → _masked_image.py} +2 -2
  9. ngio/images/{ome_zarr_container.py → _ome_zarr_container.py} +59 -24
  10. ngio/ome_zarr_meta/_meta_handlers.py +16 -8
  11. ngio/ome_zarr_meta/ngio_specs/_axes.py +4 -7
  12. ngio/ome_zarr_meta/ngio_specs/_channels.py +41 -29
  13. ngio/tables/__init__.py +7 -2
  14. ngio/tables/{abstract_table.py → _abstract_table.py} +5 -4
  15. ngio/tables/{tables_container.py → _tables_container.py} +42 -29
  16. ngio/tables/backends/__init__.py +6 -4
  17. ngio/tables/backends/_abstract_backend.py +1 -1
  18. ngio/tables/backends/{_anndata_v1.py → _anndata.py} +1 -1
  19. ngio/tables/backends/{_csv_v1.py → _csv.py} +2 -2
  20. ngio/tables/backends/{_json_v1.py → _json.py} +1 -1
  21. ngio/tables/backends/{_parquet_v1.py → _parquet.py} +2 -2
  22. ngio/tables/backends/_table_backends.py +41 -17
  23. ngio/tables/v1/__init__.py +12 -3
  24. ngio/tables/v1/_condition_table.py +8 -4
  25. ngio/tables/v1/_feature_table.py +11 -13
  26. ngio/tables/v1/_generic_table.py +14 -3
  27. ngio/tables/v1/_roi_table.py +11 -7
  28. ngio/utils/_fractal_fsspec_store.py +1 -1
  29. {ngio-0.3.0a0.dist-info → ngio-0.3.1.dist-info}/METADATA +69 -35
  30. ngio-0.3.1.dist-info/RECORD +61 -0
  31. ngio-0.3.0a0.dist-info/RECORD +0 -61
  32. /ngio/images/{abstract_image.py → _abstract_image.py} +0 -0
  33. /ngio/images/{create.py → _create.py} +0 -0
  34. /ngio/tables/backends/{_non_zarr_backends_v1.py → _non_zarr_backends.py} +0 -0
  35. {ngio-0.3.0a0.dist-info → ngio-0.3.1.dist-info}/WHEEL +0 -0
  36. {ngio-0.3.0a0.dist-info → ngio-0.3.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,10 +1,11 @@
1
1
  from collections.abc import Collection, Iterable
2
2
  from typing import Literal
3
3
 
4
- import dask
5
- import dask.delayed
4
+ import dask.array as da
6
5
  import numpy as np
7
6
  import zarr
7
+ from dask.array import Array as DaskArray
8
+ from dask.delayed import Delayed, delayed
8
9
 
9
10
  from ngio.common._axes_transforms import transform_dask_array, transform_numpy_array
10
11
  from ngio.common._common_types import ArrayLike
@@ -55,26 +56,26 @@ def _numpy_get_pipe(
55
56
  slices: SliceTransform,
56
57
  transformations: tuple[AxesTransformation, ...],
57
58
  ) -> np.ndarray:
58
- array = numpy_get_slice(array, slices)
59
- return transform_numpy_array(array, transformations)
59
+ _array = numpy_get_slice(array, slices)
60
+ return transform_numpy_array(_array, transformations)
60
61
 
61
62
 
62
63
  def _delayed_numpy_get_pipe(
63
64
  array: zarr.Array,
64
65
  slices: SliceTransform,
65
66
  transformations: tuple[AxesTransformation, ...],
66
- ) -> dask.delayed:
67
- array = dask.delayed(numpy_get_slice)(array, slices)
68
- return dask.delayed(transform_numpy_array)(array, transformations)
67
+ ) -> Delayed:
68
+ _array = delayed(numpy_get_slice)(array, slices)
69
+ return delayed(transform_numpy_array)(_array, transformations)
69
70
 
70
71
 
71
72
  def _dask_get_pipe(
72
73
  array: zarr.Array,
73
74
  slices: SliceTransform,
74
75
  transformations: tuple[AxesTransformation, ...],
75
- ) -> dask.array:
76
- array = dask_get_slice(array, slices)
77
- return transform_dask_array(array, transformations)
76
+ ) -> DaskArray:
77
+ _array = dask_get_slice(array, slices)
78
+ return transform_dask_array(_array, transformations)
78
79
 
79
80
 
80
81
  def _numpy_set_pipe(
@@ -89,22 +90,22 @@ def _numpy_set_pipe(
89
90
 
90
91
  def _dask_set_pipe(
91
92
  array: zarr.Array,
92
- patch: np.ndarray,
93
+ patch: DaskArray,
93
94
  slices: SliceTransform,
94
95
  transformations: tuple[AxesTransformation, ...],
95
96
  ) -> None:
96
- patch = transform_dask_array(patch, transformations)
97
- dask_set_slice(array, patch, slices)
97
+ _patch = transform_dask_array(patch, transformations)
98
+ dask_set_slice(array, _patch, slices)
98
99
 
99
100
 
100
101
  def _delayed_numpy_set_pipe(
101
102
  array: zarr.Array,
102
- patch: np.ndarray,
103
+ patch: np.ndarray | Delayed,
103
104
  slices: SliceTransform,
104
105
  transformations: tuple[AxesTransformation, ...],
105
- ) -> dask.delayed:
106
- patch = dask.delayed(transform_numpy_array)(patch, transformations)
107
- return dask.delayed(numpy_set_slice)(array, patch, slices)
106
+ ) -> Delayed:
107
+ _patch = delayed(transform_numpy_array)(patch, transformations)
108
+ return delayed(numpy_set_slice)(array, _patch, slices)
108
109
 
109
110
 
110
111
  def get_pipe(
@@ -144,7 +145,7 @@ def set_pipe(
144
145
  slices, transformations = _compute_to_disk_transforms(
145
146
  dimensions=dimensions, axes_order=axes_order, **slice_kwargs
146
147
  )
147
- if isinstance(patch, dask.array.Array):
148
+ if isinstance(patch, DaskArray):
148
149
  _dask_set_pipe(
149
150
  array=array, patch=patch, slices=slices, transformations=transformations
150
151
  )
@@ -152,7 +153,7 @@ def set_pipe(
152
153
  _numpy_set_pipe(
153
154
  array=array, patch=patch, slices=slices, transformations=transformations
154
155
  )
155
- elif isinstance(patch, dask.delayed.Delayed):
156
+ elif isinstance(patch, Delayed):
156
157
  _delayed_numpy_set_pipe(
157
158
  array=array, patch=patch, slices=slices, transformations=transformations
158
159
  )
@@ -193,12 +194,15 @@ def _mask_pipe_common(
193
194
  **slice_kwargs,
194
195
  )
195
196
 
196
- if isinstance(array_patch, np.ndarray):
197
+ if isinstance(array_patch, np.ndarray) and isinstance(label_patch, np.ndarray):
197
198
  label_patch = np.broadcast_to(label_patch, array_patch.shape)
198
- elif isinstance(array_patch, dask.array.Array):
199
- label_patch = dask.array.broadcast_to(label_patch, array_patch.shape)
199
+ elif isinstance(array_patch, DaskArray) and isinstance(label_patch, DaskArray):
200
+ label_patch = da.broadcast_to(label_patch, array_patch.shape)
200
201
  else:
201
- raise NgioValueError(f"Mode {mode} not yet supported for masked array.")
202
+ raise NgioValueError(
203
+ "Incompatible types for array and label: "
204
+ f"{type(array_patch)} and {type(label_patch)}"
205
+ )
202
206
 
203
207
  mask = label_patch == label
204
208
  return array_patch, mask
@@ -225,7 +229,14 @@ def get_masked_pipe(
225
229
  mode=mode,
226
230
  **slice_kwargs,
227
231
  )
228
- array_patch[~mask] = 0
232
+ if isinstance(array_patch, np.ndarray):
233
+ array_patch[~mask] = 0
234
+ elif isinstance(array_patch, DaskArray):
235
+ array_patch = da.where(mask, array_patch, 0)
236
+ else:
237
+ raise NgioValueError(
238
+ "Mode not yet supported for masked array. Expected a numpy or dask array."
239
+ )
229
240
  return array_patch
230
241
 
231
242
 
@@ -240,7 +251,7 @@ def set_masked_pipe(
240
251
  axes_order: Collection[str] | None = None,
241
252
  **slice_kwargs: slice | int | Iterable[int],
242
253
  ):
243
- if isinstance(patch, dask.array.Array):
254
+ if isinstance(patch, DaskArray):
244
255
  mode = "dask"
245
256
  elif isinstance(patch, np.ndarray):
246
257
  mode = "numpy"
@@ -259,7 +270,19 @@ def set_masked_pipe(
259
270
  mode=mode,
260
271
  **slice_kwargs,
261
272
  )
262
- patch = np.where(mask, patch, array_patch)
273
+ if isinstance(patch, np.ndarray):
274
+ assert isinstance(array_patch, np.ndarray)
275
+ _patch = np.where(mask, patch, array_patch)
276
+ elif isinstance(patch, DaskArray):
277
+ _patch = da.where(mask, patch, array_patch)
278
+ else:
279
+ raise NgioValueError(
280
+ "Mode not yet supported for masked array. Expected a numpy or dask array."
281
+ )
263
282
  set_pipe(
264
- array, patch, dimensions=dimensions_array, axes_order=axes_order, **slice_kwargs
283
+ array,
284
+ _patch,
285
+ dimensions=dimensions_array,
286
+ axes_order=axes_order,
287
+ **slice_kwargs,
265
288
  )
ngio/common/_table_ops.py CHANGED
@@ -9,7 +9,7 @@ from typing import Literal
9
9
  import pandas as pd
10
10
  import polars as pl
11
11
 
12
- from ngio.images.ome_zarr_container import OmeZarrContainer
12
+ from ngio.images._ome_zarr_container import OmeZarrContainer
13
13
  from ngio.tables import Table, TableType
14
14
 
15
15
 
ngio/hcs/__init__.py CHANGED
@@ -1,6 +1,6 @@
1
1
  """OME-Zarr HCS objects models."""
2
2
 
3
- from ngio.hcs.plate import (
3
+ from ngio.hcs._plate import (
4
4
  OmeZarrPlate,
5
5
  OmeZarrWell,
6
6
  create_empty_plate,
@@ -32,14 +32,13 @@ from ngio.tables import (
32
32
  MaskingRoiTable,
33
33
  RoiTable,
34
34
  Table,
35
- TableBackendProtocol,
35
+ TableBackend,
36
36
  TablesContainer,
37
37
  TableType,
38
38
  TypedTable,
39
39
  )
40
40
  from ngio.utils import (
41
41
  AccessModeLiteral,
42
- NgioValidationError,
43
42
  NgioValueError,
44
43
  StoreOrGroup,
45
44
  ZarrGroupHandler,
@@ -805,22 +804,38 @@ class OmeZarrPlate:
805
804
  parallel_safe=parallel_safe,
806
805
  )
807
806
 
808
- @property
809
- def tables_container(self) -> TablesContainer:
807
+ def _get_tables_container(self) -> TablesContainer | None:
810
808
  """Return the tables container."""
811
809
  if self._tables_container is None:
812
- self._tables_container = _default_table_container(self._group_handler)
813
- if self._tables_container is None:
814
- raise NgioValidationError("No tables found in the image.")
810
+ _tables_container = _default_table_container(self._group_handler)
811
+ if _tables_container is None:
812
+ return None
813
+ self._tables_container = _tables_container
815
814
  return self._tables_container
816
815
 
817
- def list_tables(self, filter_types: str | None = None) -> list[str]:
816
+ @property
817
+ def tables_container(self) -> TablesContainer:
818
+ """Return the tables container."""
819
+ _table_container = self._get_tables_container()
820
+ if _table_container is None:
821
+ raise NgioValueError(
822
+ "No tables container found. Please add a tables container to the plate."
823
+ )
824
+ return _table_container
825
+
826
+ def list_tables(self, filter_types: TypedTable | str | None = None) -> list[str]:
818
827
  """List all tables in the image."""
819
828
  return self.tables_container.list(filter_types=filter_types)
820
829
 
821
830
  def list_roi_tables(self) -> list[str]:
822
831
  """List all ROI tables in the image."""
823
- return self.tables_container.list_roi_tables()
832
+ masking_roi = self.tables_container.list(
833
+ filter_types="masking_roi_table",
834
+ )
835
+ roi = self.tables_container.list(
836
+ filter_types="roi_table",
837
+ )
838
+ return masking_roi + roi
824
839
 
825
840
  def get_roi_table(self, name: str) -> RoiTable:
826
841
  """Get a ROI table from the image.
@@ -909,14 +924,14 @@ class OmeZarrPlate:
909
924
  self,
910
925
  name: str,
911
926
  table_cls: type[TableType],
912
- backend: str | TableBackendProtocol | None = None,
927
+ backend: TableBackend | None = None,
913
928
  ) -> TableType:
914
929
  """Get a table from the image as a specific type.
915
930
 
916
931
  Args:
917
932
  name (str): The name of the table.
918
933
  table_cls (type[TableType]): The type of the table.
919
- backend (str | TableBackendProtocol | None): The backend to use. If None,
934
+ backend (TableBackend | None): The backend to use. If None,
920
935
  the default backend is used.
921
936
  """
922
937
  return self.tables_container.get_as(
@@ -929,7 +944,7 @@ class OmeZarrPlate:
929
944
  self,
930
945
  name: str,
931
946
  table: Table,
932
- backend: str | TableBackendProtocol = "anndata_v1",
947
+ backend: TableBackend = "anndata",
933
948
  overwrite: bool = False,
934
949
  ) -> None:
935
950
  """Add a table to the image."""
ngio/images/__init__.py CHANGED
@@ -1,8 +1,8 @@
1
1
  """OME-Zarr object models."""
2
2
 
3
- from ngio.images.image import Image, ImagesContainer
4
- from ngio.images.label import Label, LabelsContainer
5
- from ngio.images.ome_zarr_container import (
3
+ from ngio.images._image import Image, ImagesContainer
4
+ from ngio.images._label import Label, LabelsContainer
5
+ from ngio.images._ome_zarr_container import (
6
6
  OmeZarrContainer,
7
7
  create_empty_ome_zarr,
8
8
  create_ome_zarr_from_array,
@@ -3,11 +3,11 @@
3
3
  from collections.abc import Collection
4
4
  from typing import Literal
5
5
 
6
- from dask import array as da
6
+ import dask.array as da
7
7
 
8
8
  from ngio.common import Dimensions
9
- from ngio.images.abstract_image import AbstractImage, consolidate_image
10
- from ngio.images.create import create_empty_image_container
9
+ from ngio.images._abstract_image import AbstractImage, consolidate_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,
@@ -152,31 +152,33 @@ class ImagesContainer:
152
152
 
153
153
  def set_channel_meta(
154
154
  self,
155
- labels: Collection[str] | int | None = None,
156
- wavelength_id: Collection[str] | None = None,
157
- start: Collection[float] | None = None,
158
- end: Collection[float] | None = None,
155
+ labels: Collection[str | None] | int | None = None,
156
+ wavelength_id: Collection[str | None] | None = None,
157
+ start: Collection[float | None] | None = None,
158
+ end: Collection[float | None] | None = None,
159
159
  percentiles: tuple[float, float] | None = None,
160
- colors: Collection[str] | None = None,
161
- active: Collection[bool] | None = None,
160
+ colors: Collection[str | None] | None = None,
161
+ active: Collection[bool | None] | None = None,
162
162
  **omero_kwargs: dict,
163
163
  ) -> None:
164
164
  """Create a ChannelsMeta object with the default unit.
165
165
 
166
166
  Args:
167
- labels(Collection[str] | int): The list of channels names in the image.
168
- If an integer is provided, the channels will be named "channel_i".
169
- wavelength_id(Collection[str] | None): The wavelength ID of the channel.
167
+ labels(Collection[str | None] | int): The list of channels names
168
+ in the image. If an integer is provided, the channels will
169
+ be named "channel_i".
170
+ wavelength_id(Collection[str | None]): The wavelength ID of the channel.
170
171
  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
+ start(Collection[float | None]): The start value for each channel.
172
173
  If None, the start value will be computed from the image.
173
- end(Collection[float] | None): The end value for each channel.
174
+ end(Collection[float | None]): The end value for each channel.
174
175
  If None, the end value will be computed from the image.
175
- percentiles(tuple[float, float] | None): The start and end percentiles
176
- for each channel. If None, the percentiles will not be computed.
177
- colors(Collection[str, NgioColors] | None): The list of colors for the
176
+ percentiles(tuple[float, float] | None): The start and end
177
+ percentiles for each channel. If None, the percentiles will
178
+ not be computed.
179
+ colors(Collection[str | None]): The list of colors for the
178
180
  channels. If None, the colors will be random.
179
- active (Collection[bool] | None):active(bool): Whether the channel should
181
+ active (Collection[bool | None]): Whether the channel should
180
182
  be shown by default.
181
183
  omero_kwargs(dict): Extra fields to store in the omero attributes.
182
184
  """
@@ -376,9 +378,12 @@ def compute_image_percentile(
376
378
  starts, ends = [], []
377
379
  for c in range(image.num_channels):
378
380
  if image.num_channels == 1:
379
- data = image.get_array(mode="dask").ravel()
381
+ data = image.get_array(mode="dask")
380
382
  else:
381
- data = image.get_array(c=c, mode="dask").ravel()
383
+ data = image.get_array(c=c, mode="dask")
384
+
385
+ assert isinstance(data, da.Array), "Data must be a Dask array."
386
+ data = da.ravel(data)
382
387
  # remove all the zeros
383
388
  mask = data > 1e-16
384
389
  data = data[mask]
@@ -391,7 +396,7 @@ def compute_image_percentile(
391
396
  # compute the percentiles
392
397
  _s_perc, _e_perc = da.percentile(
393
398
  data, [start_percentile, end_percentile], method="nearest"
394
- ).compute()
399
+ ).compute() # type: ignore
395
400
 
396
401
  starts.append(float(_s_perc))
397
402
  ends.append(float(_e_perc))
@@ -3,10 +3,12 @@
3
3
  from collections.abc import Collection
4
4
  from typing import Literal
5
5
 
6
+ import dask.array as da
7
+
6
8
  from ngio.common import compute_masking_roi
7
- from ngio.images.abstract_image import AbstractImage, consolidate_image
8
- from ngio.images.create import create_empty_label_container
9
- from ngio.images.image import Image
9
+ from ngio.images._abstract_image import AbstractImage, consolidate_image
10
+ from ngio.images._create import create_empty_label_container
11
+ from ngio.images._image import Image
10
12
  from ngio.ome_zarr_meta import (
11
13
  LabelMetaHandler,
12
14
  NgioLabelMeta,
@@ -309,6 +311,6 @@ def build_masking_roi_table(label: Label) -> MaskingRoiTable:
309
311
  raise NgioValueError("Time series labels are not supported.")
310
312
 
311
313
  array = label.get_array(axes_order=["z", "y", "x"], mode="dask")
312
-
314
+ assert isinstance(array, da.Array), "Array must be a Dask array."
313
315
  rois = compute_masking_roi(array, label.pixel_size)
314
316
  return MaskingRoiTable(rois, reference_label=label.meta.name)
@@ -4,8 +4,8 @@ from collections.abc import Collection, Iterable
4
4
  from typing import Literal
5
5
 
6
6
  from ngio.common import ArrayLike, get_masked_pipe, roi_to_slice_kwargs, set_masked_pipe
7
- from ngio.images.image import Image
8
- from ngio.images.label import Label
7
+ from ngio.images._image import Image
8
+ from ngio.images._label import Label
9
9
  from ngio.ome_zarr_meta import ImageMetaHandler, LabelMetaHandler
10
10
  from ngio.tables import MaskingRoiTable
11
11
  from ngio.utils import (
@@ -5,10 +5,10 @@ from collections.abc import Collection
5
5
 
6
6
  import numpy as np
7
7
 
8
- from ngio.images.create import create_empty_image_container
9
- from ngio.images.image import Image, ImagesContainer
10
- from ngio.images.label import Label, LabelsContainer
11
- from ngio.images.masked_image import MaskedImage, MaskedLabel
8
+ from ngio.images._create import create_empty_image_container
9
+ from ngio.images._image import Image, ImagesContainer
10
+ from ngio.images._label import Label, LabelsContainer
11
+ from ngio.images._masked_image import MaskedImage, MaskedLabel
12
12
  from ngio.ome_zarr_meta import (
13
13
  NgioImageMeta,
14
14
  PixelSize,
@@ -28,7 +28,7 @@ from ngio.tables import (
28
28
  MaskingRoiTable,
29
29
  RoiTable,
30
30
  Table,
31
- TableBackendProtocol,
31
+ TableBackend,
32
32
  TablesContainer,
33
33
  TableType,
34
34
  TypedTable,
@@ -68,7 +68,7 @@ class OmeZarrContainer:
68
68
  group_handler: ZarrGroupHandler,
69
69
  table_container: TablesContainer | None = None,
70
70
  label_container: LabelsContainer | None = None,
71
- validate_arrays: bool = True,
71
+ validate_paths: bool = False,
72
72
  ) -> None:
73
73
  """Initialize the OmeZarrContainer."""
74
74
  self._group_handler = group_handler
@@ -77,6 +77,10 @@ class OmeZarrContainer:
77
77
  self._labels_container = label_container
78
78
  self._tables_container = table_container
79
79
 
80
+ if validate_paths:
81
+ for level_path in self._images_container.levels_paths:
82
+ self.get_image(path=level_path)
83
+
80
84
  def __repr__(self) -> str:
81
85
  """Return a string representation of the image."""
82
86
  num_labels = len(self.list_labels())
@@ -99,24 +103,40 @@ class OmeZarrContainer:
99
103
  """Return the image container."""
100
104
  return self._images_container
101
105
 
102
- @property
103
- def labels_container(self) -> LabelsContainer:
106
+ def _get_labels_container(self) -> LabelsContainer | None:
104
107
  """Return the labels container."""
105
108
  if self._labels_container is None:
106
- self._labels_container = _default_label_container(self._group_handler)
107
- if self._labels_container is None:
108
- raise NgioValidationError("No labels found in the image.")
109
+ _labels_container = _default_label_container(self._group_handler)
110
+ if _labels_container is None:
111
+ return None
112
+ self._labels_container = _labels_container
109
113
  return self._labels_container
110
114
 
111
115
  @property
112
- def tables_container(self) -> TablesContainer:
116
+ def labels_container(self) -> LabelsContainer:
117
+ """Return the labels container."""
118
+ _labels_container = self._get_labels_container()
119
+ if _labels_container is None:
120
+ raise NgioValidationError("No labels found in the image.")
121
+ return _labels_container
122
+
123
+ def _get_tables_container(self) -> TablesContainer | None:
113
124
  """Return the tables container."""
114
125
  if self._tables_container is None:
115
- self._tables_container = _default_table_container(self._group_handler)
116
- if self._tables_container is None:
117
- raise NgioValidationError("No tables found in the image.")
126
+ _tables_container = _default_table_container(self._group_handler)
127
+ if _tables_container is None:
128
+ return None
129
+ self._tables_container = _tables_container
118
130
  return self._tables_container
119
131
 
132
+ @property
133
+ def tables_container(self) -> TablesContainer:
134
+ """Return the tables container."""
135
+ _tables_container = self._get_tables_container()
136
+ if _tables_container is None:
137
+ raise NgioValidationError("No tables found in the image.")
138
+ return _tables_container
139
+
120
140
  @property
121
141
  def image_meta(self) -> NgioImageMeta:
122
142
  """Return the image metadata."""
@@ -332,7 +352,7 @@ class OmeZarrContainer:
332
352
 
333
353
  new_ome_zarr = OmeZarrContainer(
334
354
  group_handler=handler,
335
- validate_arrays=False,
355
+ validate_paths=False,
336
356
  )
337
357
 
338
358
  if copy_labels:
@@ -346,13 +366,25 @@ class OmeZarrContainer:
346
366
  )
347
367
  return new_ome_zarr
348
368
 
349
- def list_tables(self, filter_types: str | None = None) -> list[str]:
369
+ def list_tables(self, filter_types: TypedTable | str | None = None) -> list[str]:
350
370
  """List all tables in the image."""
351
- return self.tables_container.list(filter_types=filter_types)
371
+ table_container = self._get_tables_container()
372
+ if table_container is None:
373
+ return []
374
+
375
+ return table_container.list(
376
+ filter_types=filter_types,
377
+ )
352
378
 
353
379
  def list_roi_tables(self) -> list[str]:
354
380
  """List all ROI tables in the image."""
355
- return self.tables_container.list_roi_tables()
381
+ masking_roi = self.tables_container.list(
382
+ filter_types="masking_roi_table",
383
+ )
384
+ roi = self.tables_container.list(
385
+ filter_types="roi_table",
386
+ )
387
+ return masking_roi + roi
356
388
 
357
389
  def get_roi_table(self, name: str) -> RoiTable:
358
390
  """Get a ROI table from the image.
@@ -441,14 +473,14 @@ class OmeZarrContainer:
441
473
  self,
442
474
  name: str,
443
475
  table_cls: type[TableType],
444
- backend: str | TableBackendProtocol | None = None,
476
+ backend: TableBackend | None = None,
445
477
  ) -> TableType:
446
478
  """Get a table from the image as a specific type.
447
479
 
448
480
  Args:
449
481
  name (str): The name of the table.
450
482
  table_cls (type[TableType]): The type of the table.
451
- backend (str | TableBackendProtocol | None): The backend to use. If None,
483
+ backend (TableBackend | None): The backend to use. If None,
452
484
  the default backend is used.
453
485
  """
454
486
  return self.tables_container.get_as(
@@ -469,7 +501,7 @@ class OmeZarrContainer:
469
501
  self,
470
502
  name: str,
471
503
  table: Table,
472
- backend: str | TableBackendProtocol = "anndata_v1",
504
+ backend: TableBackend = "anndata",
473
505
  overwrite: bool = False,
474
506
  ) -> None:
475
507
  """Add a table to the image."""
@@ -479,7 +511,10 @@ class OmeZarrContainer:
479
511
 
480
512
  def list_labels(self) -> list[str]:
481
513
  """List all labels in the image."""
482
- return self.labels_container.list()
514
+ label_container = self._get_labels_container()
515
+ if label_container is None:
516
+ return []
517
+ return label_container.list()
483
518
 
484
519
  def get_label(
485
520
  self,
@@ -597,7 +632,7 @@ def open_ome_zarr_container(
597
632
  handler = ZarrGroupHandler(store=store, cache=cache, mode=mode)
598
633
  return OmeZarrContainer(
599
634
  group_handler=handler,
600
- validate_arrays=validate_arrays,
635
+ validate_paths=validate_arrays,
601
636
  )
602
637
 
603
638
 
@@ -187,10 +187,6 @@ class GenericMetaHandler(
187
187
 
188
188
  raise NgioValueError(f"Could not load metadata: {meta_or_error}")
189
189
 
190
- def safe_load_meta(self) -> _image_meta | ConverterError:
191
- """Load the metadata from the store."""
192
- return self._load_meta(return_error=True)
193
-
194
190
  def _write_meta(self, meta) -> None:
195
191
  """Write the metadata to the store."""
196
192
  _meta = self._meta_exporter(metadata=meta)
@@ -217,6 +213,10 @@ class ImageMetaHandler(
217
213
  return meta
218
214
  raise NgioValueError(f"Could not load metadata: {meta}")
219
215
 
216
+ def safe_load_meta(self) -> NgioImageMeta | ConverterError:
217
+ """Load the metadata from the store."""
218
+ return self._load_meta(return_error=True)
219
+
220
220
 
221
221
  class LabelMetaHandler(
222
222
  GenericMetaHandler[NgioLabelMeta, LabelMetaImporter, LabelMetaExporter]
@@ -230,6 +230,10 @@ class LabelMetaHandler(
230
230
  return meta
231
231
  raise NgioValueError(f"Could not load metadata: {meta}")
232
232
 
233
+ def safe_load_meta(self) -> NgioLabelMeta | ConverterError:
234
+ """Load the metadata from the store."""
235
+ return self._load_meta(return_error=True)
236
+
233
237
 
234
238
  ###########################################################################
235
239
  #
@@ -267,10 +271,6 @@ class GenericHCSMetaHandler(Generic[_hcs_meta, _hcs_meta_importer, _hcs_meta_exp
267
271
 
268
272
  raise NgioValueError(f"Could not load metadata: {meta_or_error}")
269
273
 
270
- def safe_load_meta(self) -> _hcs_meta | ConverterError:
271
- """Load the metadata from the store."""
272
- return self._load_meta(return_error=True)
273
-
274
274
  def _write_meta(self, meta) -> None:
275
275
  _meta = self._meta_exporter(metadata=meta)
276
276
  self._group_handler.write_attrs(_meta)
@@ -295,6 +295,10 @@ class WellMetaHandler(
295
295
  return meta
296
296
  raise NgioValueError(f"Could not load metadata: {meta}")
297
297
 
298
+ def safe_load_meta(self) -> NgioWellMeta | ConverterError:
299
+ """Load the metadata from the store."""
300
+ return self._load_meta(return_error=True)
301
+
298
302
 
299
303
  class PlateMetaHandler(
300
304
  GenericHCSMetaHandler[NgioPlateMeta, PlateMetaImporter, PlateMetaExporter]
@@ -308,6 +312,10 @@ class PlateMetaHandler(
308
312
  return meta
309
313
  raise NgioValueError(f"Could not load metadata: {meta}")
310
314
 
315
+ def safe_load_meta(self) -> NgioPlateMeta | ConverterError:
316
+ """Load the metadata from the store."""
317
+ return self._load_meta(return_error=True)
318
+
311
319
 
312
320
  ###########################################################################
313
321
  #