ngio 0.1.5__py3-none-any.whl → 0.2.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 (87) hide show
  1. ngio/__init__.py +33 -5
  2. ngio/common/__init__.py +54 -0
  3. ngio/common/_array_pipe.py +265 -0
  4. ngio/common/_axes_transforms.py +64 -0
  5. ngio/common/_common_types.py +5 -0
  6. ngio/common/_dimensions.py +120 -0
  7. ngio/common/_masking_roi.py +158 -0
  8. ngio/common/_pyramid.py +228 -0
  9. ngio/common/_roi.py +165 -0
  10. ngio/common/_slicer.py +96 -0
  11. ngio/{pipes/_zoom_utils.py → common/_zoom.py} +51 -83
  12. ngio/hcs/__init__.py +5 -0
  13. ngio/hcs/plate.py +448 -0
  14. ngio/images/__init__.py +23 -0
  15. ngio/images/abstract_image.py +349 -0
  16. ngio/images/create.py +270 -0
  17. ngio/images/image.py +453 -0
  18. ngio/images/label.py +285 -0
  19. ngio/images/masked_image.py +273 -0
  20. ngio/images/ome_zarr_container.py +738 -0
  21. ngio/ome_zarr_meta/__init__.py +47 -0
  22. ngio/ome_zarr_meta/_meta_handlers.py +791 -0
  23. ngio/ome_zarr_meta/ngio_specs/__init__.py +71 -0
  24. ngio/ome_zarr_meta/ngio_specs/_axes.py +481 -0
  25. ngio/ome_zarr_meta/ngio_specs/_channels.py +389 -0
  26. ngio/ome_zarr_meta/ngio_specs/_dataset.py +134 -0
  27. ngio/ome_zarr_meta/ngio_specs/_ngio_hcs.py +377 -0
  28. ngio/ome_zarr_meta/ngio_specs/_ngio_image.py +489 -0
  29. ngio/ome_zarr_meta/ngio_specs/_pixel_size.py +116 -0
  30. ngio/ome_zarr_meta/v04/__init__.py +23 -0
  31. ngio/ome_zarr_meta/v04/_v04_spec_utils.py +485 -0
  32. ngio/tables/__init__.py +24 -6
  33. ngio/tables/_validators.py +190 -0
  34. ngio/tables/backends/__init__.py +8 -0
  35. ngio/tables/backends/_abstract_backend.py +71 -0
  36. ngio/tables/backends/_anndata_utils.py +198 -0
  37. ngio/tables/backends/_anndata_v1.py +76 -0
  38. ngio/tables/backends/_json_v1.py +56 -0
  39. ngio/tables/backends/_table_backends.py +102 -0
  40. ngio/tables/tables_container.py +310 -0
  41. ngio/tables/v1/__init__.py +5 -5
  42. ngio/tables/v1/_feature_table.py +182 -0
  43. ngio/tables/v1/_generic_table.py +160 -179
  44. ngio/tables/v1/_roi_table.py +366 -0
  45. ngio/utils/__init__.py +26 -10
  46. ngio/utils/_datasets.py +53 -0
  47. ngio/utils/_errors.py +10 -4
  48. ngio/utils/_fractal_fsspec_store.py +13 -0
  49. ngio/utils/_logger.py +3 -1
  50. ngio/utils/_zarr_utils.py +401 -0
  51. {ngio-0.1.5.dist-info → ngio-0.2.0.dist-info}/METADATA +31 -43
  52. ngio-0.2.0.dist-info/RECORD +54 -0
  53. ngio/core/__init__.py +0 -7
  54. ngio/core/dimensions.py +0 -122
  55. ngio/core/image_handler.py +0 -228
  56. ngio/core/image_like_handler.py +0 -549
  57. ngio/core/label_handler.py +0 -410
  58. ngio/core/ngff_image.py +0 -387
  59. ngio/core/roi.py +0 -92
  60. ngio/core/utils.py +0 -287
  61. ngio/io/__init__.py +0 -19
  62. ngio/io/_zarr.py +0 -88
  63. ngio/io/_zarr_array_utils.py +0 -0
  64. ngio/io/_zarr_group_utils.py +0 -61
  65. ngio/iterators/__init__.py +0 -1
  66. ngio/ngff_meta/__init__.py +0 -27
  67. ngio/ngff_meta/fractal_image_meta.py +0 -1267
  68. ngio/ngff_meta/meta_handler.py +0 -92
  69. ngio/ngff_meta/utils.py +0 -235
  70. ngio/ngff_meta/v04/__init__.py +0 -6
  71. ngio/ngff_meta/v04/specs.py +0 -158
  72. ngio/ngff_meta/v04/zarr_utils.py +0 -376
  73. ngio/pipes/__init__.py +0 -7
  74. ngio/pipes/_slicer_transforms.py +0 -176
  75. ngio/pipes/_transforms.py +0 -33
  76. ngio/pipes/data_pipe.py +0 -52
  77. ngio/tables/_ad_reader.py +0 -80
  78. ngio/tables/_utils.py +0 -301
  79. ngio/tables/tables_group.py +0 -252
  80. ngio/tables/v1/feature_tables.py +0 -182
  81. ngio/tables/v1/masking_roi_tables.py +0 -243
  82. ngio/tables/v1/roi_tables.py +0 -285
  83. ngio/utils/_common_types.py +0 -5
  84. ngio/utils/_pydantic_utils.py +0 -52
  85. ngio-0.1.5.dist-info/RECORD +0 -44
  86. {ngio-0.1.5.dist-info → ngio-0.2.0.dist-info}/WHEEL +0 -0
  87. {ngio-0.1.5.dist-info → ngio-0.2.0.dist-info}/licenses/LICENSE +0 -0
@@ -3,8 +3,50 @@ from typing import Literal
3
3
 
4
4
  import dask.array as da
5
5
  import numpy as np
6
- import zarr
7
- from scipy.ndimage import zoom
6
+ from scipy.ndimage import zoom as scipy_zoom
7
+
8
+ from ngio.utils import NgioValueError
9
+
10
+
11
+ def _stacked_zoom(x, zoom_y, zoom_x, order=1, mode="grid-constant", grid_mode=True):
12
+ *rest, yshape, xshape = x.shape
13
+ x = x.reshape(-1, yshape, xshape)
14
+ scale_xy = (zoom_y, zoom_x)
15
+ x_out = np.stack(
16
+ [
17
+ scipy_zoom(x[i], scale_xy, order=order, mode=mode, grid_mode=True)
18
+ for i in range(x.shape[0])
19
+ ]
20
+ )
21
+ return x_out.reshape(*rest, *x_out.shape[1:])
22
+
23
+
24
+ def fast_zoom(x, zoom, order=1, mode="grid-constant", grid_mode=True, auto_stack=True):
25
+ """Fast zoom function.
26
+
27
+ Scipy zoom function that can handle singleton dimensions
28
+ but the performance degrades with the number of dimensions.
29
+
30
+ This function has two small optimizations:
31
+ - it removes singleton dimensions before calling zoom
32
+ - if it detects that the zoom is only on the last two dimensions
33
+ it stacks the first dimensions to call zoom only on the last two.
34
+ """
35
+ mask = np.isclose(x.shape, 1)
36
+ zoom = np.array(zoom)
37
+ singletons = tuple(np.where(mask)[0])
38
+ xs = np.squeeze(x, axis=singletons)
39
+ new_zoom = zoom[~mask]
40
+
41
+ *zoom_rest, zoom_y, zoom_x = new_zoom
42
+ if auto_stack and np.allclose(zoom_rest, 1):
43
+ xs = _stacked_zoom(
44
+ xs, zoom_y, zoom_x, order=order, mode=mode, grid_mode=grid_mode
45
+ )
46
+ else:
47
+ xs = scipy_zoom(xs, new_zoom, order=order, mode=mode, grid_mode=grid_mode)
48
+ x = np.expand_dims(xs, axis=singletons)
49
+ return x
8
50
 
9
51
 
10
52
  def _zoom_inputs_check(
@@ -13,15 +55,15 @@ def _zoom_inputs_check(
13
55
  target_shape: tuple[int, ...] | None = None,
14
56
  ) -> tuple[np.ndarray, tuple[int, ...]]:
15
57
  if scale is None and target_shape is None:
16
- raise ValueError("Either scale or target_shape must be provided")
58
+ raise NgioValueError("Either scale or target_shape must be provided")
17
59
 
18
60
  if scale is not None and target_shape is not None:
19
- raise ValueError("Only one of scale or target_shape must be provided")
61
+ raise NgioValueError("Only one of scale or target_shape must be provided")
20
62
 
21
63
  if scale is None:
22
64
  assert target_shape is not None, "Target shape must be provided"
23
65
  if len(target_shape) != source_array.ndim:
24
- raise ValueError(
66
+ raise NgioValueError(
25
67
  "Target shape must have the "
26
68
  "same number of dimensions as "
27
69
  "the source array"
@@ -35,7 +77,7 @@ def _zoom_inputs_check(
35
77
  return _scale, _target_shape
36
78
 
37
79
 
38
- def _dask_zoom(
80
+ def dask_zoom(
39
81
  source_array: da.Array,
40
82
  scale: tuple[int, ...] | None = None,
41
83
  target_shape: tuple[int, ...] | None = None,
@@ -73,7 +115,7 @@ def _dask_zoom(
73
115
  block_output_shape = tuple(np.ceil(better_source_chunks * _scale).astype(int))
74
116
 
75
117
  zoom_wrapper = partial(
76
- zoom, zoom=_scale, order=order, mode="grid-constant", grid_mode=True
118
+ fast_zoom, zoom=_scale, order=order, mode="grid-constant", grid_mode=True
77
119
  )
78
120
 
79
121
  out_array = da.map_blocks(
@@ -86,7 +128,7 @@ def _dask_zoom(
86
128
  return out_array
87
129
 
88
130
 
89
- def _numpy_zoom(
131
+ def numpy_zoom(
90
132
  source_array: np.ndarray,
91
133
  scale: tuple[int, ...] | None = None,
92
134
  target_shape: tuple[int, ...] | None = None,
@@ -109,82 +151,8 @@ def _numpy_zoom(
109
151
  source_array=source_array, scale=scale, target_shape=target_shape
110
152
  )
111
153
 
112
- out_array = zoom(
154
+ out_array = fast_zoom(
113
155
  source_array, zoom=_scale, order=order, mode="grid-constant", grid_mode=True
114
156
  )
115
157
  assert isinstance(out_array, np.ndarray)
116
158
  return out_array
117
-
118
-
119
- def on_disk_zoom(
120
- source: zarr.Array,
121
- target: zarr.Array,
122
- order: Literal[0, 1, 2] = 1,
123
- mode: Literal["dask", "numpy"] = "dask",
124
- ) -> None:
125
- """Apply a zoom operation from a source zarr array to a target zarr array.
126
-
127
- Args:
128
- source (zarr.Array): The source array to zoom.
129
- target (zarr.Array): The target array to save the zoomed result to.
130
- order (Literal[0, 1, 2]): The order of interpolation. Defaults to 1.
131
- mode (Literal["dask", "numpy"]): The mode to use. Defaults to "dask".
132
- """
133
- if not isinstance(source, zarr.Array):
134
- raise ValueError("source must be a zarr array")
135
-
136
- if not isinstance(target, zarr.Array):
137
- raise ValueError("target must be a zarr array")
138
-
139
- if source.dtype != target.dtype:
140
- raise ValueError("source and target must have the same dtype")
141
-
142
- assert mode in ["dask", "numpy"], "mode must be either 'dask' or 'numpy'"
143
-
144
- if mode == "numpy":
145
- target[...] = _numpy_zoom(source[...], target_shape=target.shape, order=order)
146
- return None
147
-
148
- source_array = da.from_zarr(source)
149
- target_array = _dask_zoom(source_array, target_shape=target.shape, order=order)
150
-
151
- target_array = target_array.rechunk(target.chunks)
152
- target_array.to_zarr(target)
153
-
154
-
155
- def on_disk_coarsen(
156
- source: zarr.Array,
157
- target: zarr.Array,
158
- aggregation_function: np.ufunc,
159
- ) -> None:
160
- """Apply a coarsening operation from a source zarr array to a target zarr array.
161
-
162
- Args:
163
- source (zarr.Array): The source array to coarsen.
164
- target (zarr.Array): The target array to save the coarsened result to.
165
- aggregation_function (np.ufunc): The aggregation function to use.
166
- """
167
- source_array = da.from_zarr(source)
168
-
169
- _scale, _target_shape = _zoom_inputs_check(
170
- source_array=source_array, scale=None, target_shape=target.shape
171
- )
172
-
173
- assert (
174
- _target_shape == target.shape
175
- ), "Target shape must match the target array shape"
176
- coarsening_setup = {}
177
- for i, s in enumerate(_scale):
178
- factor = 1 / s
179
- if factor.is_integer():
180
- coarsening_setup[i] = int(factor)
181
- else:
182
- raise ValueError(
183
- "Coarsening factor must be an integer, got " f"{factor} on axis {i}"
184
- )
185
-
186
- out_target = da.coarsen(
187
- aggregation_function, source_array, coarsening_setup, trim_excess=True
188
- )
189
- out_target = out_target.rechunk(target.chunks)
190
- out_target.to_zarr(target)
ngio/hcs/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ """OME-Zarr HCS objects models."""
2
+
3
+ from ngio.hcs.plate import OmeZarrPlate, create_empty_plate, open_ome_zarr_plate
4
+
5
+ __all__ = ["OmeZarrPlate", "create_empty_plate", "open_ome_zarr_plate"]
ngio/hcs/plate.py ADDED
@@ -0,0 +1,448 @@
1
+ """A module for handling the Plate Collection in an OME-Zarr file."""
2
+
3
+ from ngio.images import OmeZarrContainer
4
+ from ngio.ome_zarr_meta import (
5
+ ImageInWellPath,
6
+ NgioPlateMeta,
7
+ NgioWellMeta,
8
+ find_plate_meta_handler,
9
+ find_well_meta_handler,
10
+ get_plate_meta_handler,
11
+ get_well_meta_handler,
12
+ )
13
+ from ngio.utils import (
14
+ AccessModeLiteral,
15
+ StoreOrGroup,
16
+ ZarrGroupHandler,
17
+ )
18
+
19
+
20
+ # Mock lock class that does nothing
21
+ class MockLock:
22
+ """A mock lock class that does nothing."""
23
+
24
+ def __enter__(self):
25
+ """Enter the lock."""
26
+ pass
27
+
28
+ def __exit__(self, exc_type, exc_value, traceback):
29
+ """Exit the lock."""
30
+ pass
31
+
32
+
33
+ class OmeZarrWell:
34
+ """A class to handle the Well Collection in an OME-Zarr file."""
35
+
36
+ def __init__(self, group_handler: ZarrGroupHandler) -> None:
37
+ """Initialize the LabelGroupHandler.
38
+
39
+ Args:
40
+ group_handler: The Zarr group handler that contains the Well.
41
+ """
42
+ self._group_handler = group_handler
43
+ self._meta_handler = find_well_meta_handler(group_handler)
44
+
45
+ @property
46
+ def meta_handler(self):
47
+ """Return the metadata handler."""
48
+ return self._meta_handler
49
+
50
+ @property
51
+ def meta(self):
52
+ """Return the metadata."""
53
+ return self._meta_handler.meta
54
+
55
+ def paths(self, acquisition: int | None = None) -> list[str]:
56
+ """Return the images paths in the well.
57
+
58
+ If acquisition is None, return all images paths in the well.
59
+ Else, return the images paths in the well for the given acquisition.
60
+
61
+ Args:
62
+ acquisition (int | None): The acquisition id to filter the images.
63
+ """
64
+ return self.meta.paths(acquisition)
65
+
66
+
67
+ class OmeZarrPlate:
68
+ """A class to handle the Plate Collection in an OME-Zarr file."""
69
+
70
+ def __init__(self, group_handler: ZarrGroupHandler) -> None:
71
+ """Initialize the LabelGroupHandler.
72
+
73
+ Args:
74
+ group_handler: The Zarr group handler that contains the Plate.
75
+ """
76
+ self._group_handler = group_handler
77
+ self._meta_handler = find_plate_meta_handler(group_handler)
78
+
79
+ def __repr__(self) -> str:
80
+ """Return a string representation of the plate."""
81
+ return f"Plate([rows x columns] ({len(self.rows)} x {len(self.columns)})"
82
+
83
+ @property
84
+ def meta_handler(self):
85
+ """Return the metadata handler."""
86
+ return self._meta_handler
87
+
88
+ @property
89
+ def meta(self):
90
+ """Return the metadata."""
91
+ return self._meta_handler.meta
92
+
93
+ @property
94
+ def columns(self) -> list[str]:
95
+ """Return the number of columns in the plate."""
96
+ return self.meta.columns
97
+
98
+ @property
99
+ def rows(self) -> list[str]:
100
+ """Return the number of rows in the plate."""
101
+ return self.meta.rows
102
+
103
+ @property
104
+ def acquisitions_names(self) -> list[str | None]:
105
+ """Return the acquisitions in the plate."""
106
+ return self.meta.acquisitions_names
107
+
108
+ @property
109
+ def acquisitions_ids(self) -> list[int]:
110
+ """Return the acquisitions ids in the plate."""
111
+ return self.meta.acquisitions_ids
112
+
113
+ def _well_path(self, row: str, column: int | str) -> str:
114
+ """Return the well path in the plate."""
115
+ return self.meta.get_well_path(row=row, column=column)
116
+
117
+ def _image_path(self, row: str, column: int | str, path: str) -> str:
118
+ """Return the image path in the plate."""
119
+ well = self.get_well(row, column)
120
+ if path not in well.paths():
121
+ raise ValueError(f"Image {path} does not exist in well {row}{column}")
122
+ return f"{self._well_path(row, column)}/{path}"
123
+
124
+ def wells_paths(self) -> list[str]:
125
+ """Return the wells paths in the plate."""
126
+ return self.meta.wells_paths
127
+
128
+ def images_paths(self, acquisition: int | None = None) -> list[str]:
129
+ """Return the images paths in the plate.
130
+
131
+ If acquisition is None, return all images paths in the plate.
132
+ Else, return the images paths in the plate for the given acquisition.
133
+
134
+ Args:
135
+ acquisition (int | None): The acquisition id to filter the images.
136
+ """
137
+ images = []
138
+ for well_path, wells in self.get_wells().items():
139
+ for img_path in wells.paths(acquisition):
140
+ images.append(f"{well_path}/{img_path}")
141
+ return images
142
+
143
+ def well_images_paths(
144
+ self, row: str, column: int | str, acquisition: int | None = None
145
+ ) -> list[str]:
146
+ """Return the images paths in a well.
147
+
148
+ If acquisition is None, return all images paths in the well.
149
+ Else, return the images paths in the well for the given acquisition.
150
+
151
+ Args:
152
+ row (str): The row of the well.
153
+ column (int | str): The column of the well.
154
+ acquisition (int | None): The acquisition id to filter the images.
155
+ """
156
+ images = []
157
+ well = self.get_well(row=row, column=column)
158
+ for path in well.paths(acquisition):
159
+ images.append(self._image_path(row=row, column=column, path=path))
160
+ return images
161
+
162
+ def get_well(self, row: str, column: int | str) -> OmeZarrWell:
163
+ """Get a well from the plate.
164
+
165
+ Args:
166
+ row (str): The row of the well.
167
+ column (int | str): The column of the well.
168
+
169
+ Returns:
170
+ OmeZarrWell: The well.
171
+ """
172
+ well_path = self._well_path(row=row, column=column)
173
+ group_handler = self._group_handler.derive_handler(well_path)
174
+ return OmeZarrWell(group_handler)
175
+
176
+ def get_wells(self) -> dict[str, OmeZarrWell]:
177
+ """Get all wells in the plate.
178
+
179
+ Returns:
180
+ dict[str, OmeZarrWell]: A dictionary of wells, where the key is the well
181
+ path and the value is the well object.
182
+ """
183
+ wells = {}
184
+ for well_path in self.wells_paths():
185
+ group_handler = self._group_handler.derive_handler(well_path)
186
+ well = OmeZarrWell(group_handler)
187
+ wells[well_path] = well
188
+ return wells
189
+
190
+ def get_images(self, acquisition: int | None = None) -> dict[str, OmeZarrContainer]:
191
+ """Get all images in the plate.
192
+
193
+ Args:
194
+ acquisition: The acquisition id to filter the images.
195
+ """
196
+ images = {}
197
+ for image_path in self.images_paths(acquisition):
198
+ img_group_handler = self._group_handler.derive_handler(image_path)
199
+ images[image_path] = OmeZarrContainer(img_group_handler)
200
+ return images
201
+
202
+ def get_image(
203
+ self, row: str, column: int | str, image_path: str
204
+ ) -> OmeZarrContainer:
205
+ """Get an image from the plate.
206
+
207
+ Args:
208
+ row (str): The row of the well.
209
+ column (int | str): The column of the well.
210
+ image_path (str): The path of the image.
211
+
212
+ Returns:
213
+ OmeZarrContainer: The image.
214
+ """
215
+ image_path = self._image_path(row=row, column=column, path=image_path)
216
+ group_handler = self._group_handler.derive_handler(image_path)
217
+ return OmeZarrContainer(group_handler)
218
+
219
+ def get_well_images(
220
+ self, row: str, column: str | int, acquisition: int | None = None
221
+ ) -> dict[str, OmeZarrContainer]:
222
+ """Get all images in a well.
223
+
224
+ Args:
225
+ row: The row of the well.
226
+ column: The column of the well.
227
+ acquisition: The acquisition id to filter the images.
228
+ """
229
+ images = {}
230
+ for image_paths in self.well_images_paths(
231
+ row=row, column=column, acquisition=acquisition
232
+ ):
233
+ group_handler = self._group_handler.derive_handler(image_paths)
234
+ images[image_paths] = OmeZarrContainer(group_handler)
235
+ return images
236
+
237
+ def _add_image(
238
+ self,
239
+ row: str,
240
+ column: int | str,
241
+ image_path: str,
242
+ acquisition_id: int | None = None,
243
+ acquisition_name: str | None = None,
244
+ atomic: bool = False,
245
+ ) -> StoreOrGroup:
246
+ """Add an image to an ome-zarr plate."""
247
+ if atomic:
248
+ plate_lock = self._group_handler.lock
249
+ else:
250
+ plate_lock = MockLock()
251
+
252
+ with plate_lock:
253
+ meta = self.meta
254
+ meta = meta.add_well(row, column, acquisition_id, acquisition_name)
255
+ self.meta_handler.write_meta(meta)
256
+ self.meta_handler._group_handler.clean_cache()
257
+
258
+ well_path = self.meta.get_well_path(row=row, column=column)
259
+ group_handler = self._group_handler.derive_handler(well_path)
260
+
261
+ if atomic:
262
+ well_lock = group_handler.lock
263
+ else:
264
+ well_lock = MockLock()
265
+
266
+ with well_lock:
267
+ attrs = group_handler.load_attrs()
268
+ if len(attrs) == 0:
269
+ # Initialize the well metadata
270
+ # if the group is empty
271
+ well_meta = NgioWellMeta.default_init()
272
+ meta_handler = get_well_meta_handler(group_handler, version="0.4")
273
+ else:
274
+ meta_handler = find_well_meta_handler(group_handler)
275
+ well_meta = meta_handler.meta
276
+
277
+ group_handler = self._group_handler.derive_handler(well_path)
278
+
279
+ well_meta = well_meta.add_image(path=image_path, acquisition=acquisition_id)
280
+ meta_handler.write_meta(well_meta)
281
+ meta_handler._group_handler.clean_cache()
282
+
283
+ return group_handler.get_group(image_path, create_mode=True)
284
+
285
+ def atomic_add_image(
286
+ self,
287
+ row: str,
288
+ column: int | str,
289
+ image_path: str,
290
+ acquisition_id: int | None = None,
291
+ acquisition_name: str | None = None,
292
+ ) -> StoreOrGroup:
293
+ """Parallel safe version of add_image."""
294
+ return self._add_image(
295
+ row=row,
296
+ column=column,
297
+ image_path=image_path,
298
+ acquisition_id=acquisition_id,
299
+ acquisition_name=acquisition_name,
300
+ atomic=True,
301
+ )
302
+
303
+ def add_image(
304
+ self,
305
+ row: str,
306
+ column: int | str,
307
+ image_path: str,
308
+ acquisition_id: int | None = None,
309
+ acquisition_name: str | None = None,
310
+ ) -> StoreOrGroup:
311
+ """Add an image to an ome-zarr plate."""
312
+ return self._add_image(
313
+ row=row,
314
+ column=column,
315
+ image_path=image_path,
316
+ acquisition_id=acquisition_id,
317
+ acquisition_name=acquisition_name,
318
+ atomic=False,
319
+ )
320
+
321
+ def _remove_well(
322
+ self,
323
+ row: str,
324
+ column: int | str,
325
+ atomic: bool = False,
326
+ ):
327
+ """Remove a well from an ome-zarr plate."""
328
+ if atomic:
329
+ plate_lock = self._group_handler.lock
330
+ else:
331
+ plate_lock = MockLock()
332
+
333
+ with plate_lock:
334
+ meta = self.meta
335
+ meta = meta.remove_well(row, column)
336
+ self.meta_handler.write_meta(meta)
337
+ self.meta_handler._group_handler.clean_cache()
338
+
339
+ def _remove_image(
340
+ self,
341
+ row: str,
342
+ column: int | str,
343
+ image_path: str,
344
+ atomic: bool = False,
345
+ ):
346
+ """Remove an image from an ome-zarr plate."""
347
+ well = self.get_well(row, column)
348
+
349
+ if atomic:
350
+ well_lock = well.meta_handler._group_handler.lock
351
+ else:
352
+ well_lock = MockLock()
353
+
354
+ with well_lock:
355
+ well_meta = well.meta
356
+ well_meta = well_meta.remove_image(path=image_path)
357
+ well.meta_handler.write_meta(well_meta)
358
+ well.meta_handler._group_handler.clean_cache()
359
+ if len(well_meta.paths()) == 0:
360
+ self._remove_well(row, column, atomic=atomic)
361
+
362
+ def atomic_remove_image(
363
+ self,
364
+ row: str,
365
+ column: int | str,
366
+ image_path: str,
367
+ ):
368
+ """Parallel safe version of remove_image."""
369
+ return self._remove_image(
370
+ row=row,
371
+ column=column,
372
+ image_path=image_path,
373
+ atomic=True,
374
+ )
375
+
376
+ def remove_image(
377
+ self,
378
+ row: str,
379
+ column: int | str,
380
+ image_path: str,
381
+ ):
382
+ """Remove an image from an ome-zarr plate."""
383
+ return self._remove_image(
384
+ row=row,
385
+ column=column,
386
+ image_path=image_path,
387
+ atomic=False,
388
+ )
389
+
390
+
391
+ def open_ome_zarr_plate(
392
+ store: StoreOrGroup,
393
+ cache: bool = False,
394
+ mode: AccessModeLiteral = "r+",
395
+ parallel_safe: bool = True,
396
+ ) -> OmeZarrPlate:
397
+ """Open an OME-Zarr plate.
398
+
399
+ Args:
400
+ store (StoreOrGroup): The Zarr store or group that stores the plate.
401
+ cache (bool): Whether to use a cache for the zarr group metadata.
402
+ mode (AccessModeLiteral): The
403
+ access mode for the image. Defaults to "r+".
404
+ parallel_safe (bool): Whether the group handler is parallel safe.
405
+ """
406
+ group_handler = ZarrGroupHandler(
407
+ store=store, cache=cache, mode=mode, parallel_safe=parallel_safe
408
+ )
409
+ return OmeZarrPlate(group_handler)
410
+
411
+
412
+ def create_empty_plate(
413
+ store: StoreOrGroup,
414
+ name: str,
415
+ images: list[ImageInWellPath] | None = None,
416
+ version: str = "0.4",
417
+ cache: bool = False,
418
+ overwrite: bool = False,
419
+ parallel_safe: bool = True,
420
+ ) -> OmeZarrPlate:
421
+ """Initialize and create an empty OME-Zarr plate."""
422
+ mode = "w" if overwrite else "w-"
423
+ group_handler = ZarrGroupHandler(
424
+ store=store, cache=True, mode=mode, parallel_safe=False
425
+ )
426
+ meta_handler = get_plate_meta_handler(group_handler, version=version)
427
+ plate_meta = NgioPlateMeta.default_init(
428
+ name=name,
429
+ version=version,
430
+ )
431
+ meta_handler.write_meta(plate_meta)
432
+
433
+ if images is not None:
434
+ plate = OmeZarrPlate(group_handler)
435
+ for image in images:
436
+ plate.add_image(
437
+ row=image.row,
438
+ column=image.column,
439
+ image_path=image.path,
440
+ acquisition_id=image.acquisition_id,
441
+ acquisition_name=image.acquisition_name,
442
+ )
443
+ return open_ome_zarr_plate(
444
+ store=store,
445
+ cache=cache,
446
+ mode="r+",
447
+ parallel_safe=parallel_safe,
448
+ )
@@ -0,0 +1,23 @@
1
+ """OME-Zarr object models."""
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 (
6
+ OmeZarrContainer,
7
+ create_empty_ome_zarr,
8
+ create_ome_zarr_from_array,
9
+ open_image,
10
+ open_ome_zarr_container,
11
+ )
12
+
13
+ __all__ = [
14
+ "Image",
15
+ "ImagesContainer",
16
+ "Label",
17
+ "LabelsContainer",
18
+ "OmeZarrContainer",
19
+ "create_empty_ome_zarr",
20
+ "create_ome_zarr_from_array",
21
+ "open_image",
22
+ "open_ome_zarr_container",
23
+ ]