ngio 0.2.0a2__py3-none-any.whl → 0.5.0b4__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 (106) hide show
  1. ngio/__init__.py +40 -12
  2. ngio/common/__init__.py +16 -32
  3. ngio/common/_dimensions.py +270 -48
  4. ngio/common/_masking_roi.py +153 -0
  5. ngio/common/_pyramid.py +267 -73
  6. ngio/common/_roi.py +290 -66
  7. ngio/common/_synt_images_utils.py +101 -0
  8. ngio/common/_zoom.py +54 -22
  9. ngio/experimental/__init__.py +5 -0
  10. ngio/experimental/iterators/__init__.py +15 -0
  11. ngio/experimental/iterators/_abstract_iterator.py +390 -0
  12. ngio/experimental/iterators/_feature.py +189 -0
  13. ngio/experimental/iterators/_image_processing.py +130 -0
  14. ngio/experimental/iterators/_mappers.py +48 -0
  15. ngio/experimental/iterators/_rois_utils.py +126 -0
  16. ngio/experimental/iterators/_segmentation.py +235 -0
  17. ngio/hcs/__init__.py +17 -58
  18. ngio/hcs/_plate.py +1354 -0
  19. ngio/images/__init__.py +30 -9
  20. ngio/images/_abstract_image.py +968 -0
  21. ngio/images/_create_synt_container.py +132 -0
  22. ngio/images/_create_utils.py +423 -0
  23. ngio/images/_image.py +926 -0
  24. ngio/images/_label.py +417 -0
  25. ngio/images/_masked_image.py +531 -0
  26. ngio/images/_ome_zarr_container.py +1235 -0
  27. ngio/images/_table_ops.py +471 -0
  28. ngio/io_pipes/__init__.py +75 -0
  29. ngio/io_pipes/_io_pipes.py +361 -0
  30. ngio/io_pipes/_io_pipes_masked.py +488 -0
  31. ngio/io_pipes/_io_pipes_roi.py +146 -0
  32. ngio/io_pipes/_io_pipes_types.py +56 -0
  33. ngio/io_pipes/_match_shape.py +377 -0
  34. ngio/io_pipes/_ops_axes.py +344 -0
  35. ngio/io_pipes/_ops_slices.py +411 -0
  36. ngio/io_pipes/_ops_slices_utils.py +199 -0
  37. ngio/io_pipes/_ops_transforms.py +104 -0
  38. ngio/io_pipes/_zoom_transform.py +180 -0
  39. ngio/ome_zarr_meta/__init__.py +39 -15
  40. ngio/ome_zarr_meta/_meta_handlers.py +490 -96
  41. ngio/ome_zarr_meta/ngio_specs/__init__.py +24 -10
  42. ngio/ome_zarr_meta/ngio_specs/_axes.py +268 -234
  43. ngio/ome_zarr_meta/ngio_specs/_channels.py +125 -41
  44. ngio/ome_zarr_meta/ngio_specs/_dataset.py +42 -87
  45. ngio/ome_zarr_meta/ngio_specs/_ngio_hcs.py +536 -2
  46. ngio/ome_zarr_meta/ngio_specs/_ngio_image.py +202 -198
  47. ngio/ome_zarr_meta/ngio_specs/_pixel_size.py +72 -34
  48. ngio/ome_zarr_meta/v04/__init__.py +21 -5
  49. ngio/ome_zarr_meta/v04/_custom_models.py +18 -0
  50. ngio/ome_zarr_meta/v04/{_v04_spec_utils.py → _v04_spec.py} +151 -90
  51. ngio/ome_zarr_meta/v05/__init__.py +27 -0
  52. ngio/ome_zarr_meta/v05/_custom_models.py +18 -0
  53. ngio/ome_zarr_meta/v05/_v05_spec.py +511 -0
  54. ngio/resources/20200812-CardiomyocyteDifferentiation14-Cycle1_B03/mask.png +0 -0
  55. ngio/resources/20200812-CardiomyocyteDifferentiation14-Cycle1_B03/nuclei.png +0 -0
  56. ngio/resources/20200812-CardiomyocyteDifferentiation14-Cycle1_B03/raw.jpg +0 -0
  57. ngio/resources/__init__.py +55 -0
  58. ngio/resources/resource_model.py +36 -0
  59. ngio/tables/__init__.py +20 -4
  60. ngio/tables/_abstract_table.py +270 -0
  61. ngio/tables/_tables_container.py +449 -0
  62. ngio/tables/backends/__init__.py +50 -1
  63. ngio/tables/backends/_abstract_backend.py +200 -31
  64. ngio/tables/backends/_anndata.py +139 -0
  65. ngio/tables/backends/_anndata_utils.py +10 -114
  66. ngio/tables/backends/_csv.py +19 -0
  67. ngio/tables/backends/_json.py +92 -0
  68. ngio/tables/backends/_parquet.py +19 -0
  69. ngio/tables/backends/_py_arrow_backends.py +222 -0
  70. ngio/tables/backends/_table_backends.py +162 -38
  71. ngio/tables/backends/_utils.py +608 -0
  72. ngio/tables/v1/__init__.py +19 -4
  73. ngio/tables/v1/_condition_table.py +71 -0
  74. ngio/tables/v1/_feature_table.py +79 -115
  75. ngio/tables/v1/_generic_table.py +21 -90
  76. ngio/tables/v1/_roi_table.py +486 -137
  77. ngio/transforms/__init__.py +5 -0
  78. ngio/transforms/_zoom.py +19 -0
  79. ngio/utils/__init__.py +16 -14
  80. ngio/utils/_cache.py +48 -0
  81. ngio/utils/_datasets.py +121 -13
  82. ngio/utils/_fractal_fsspec_store.py +42 -0
  83. ngio/utils/_zarr_utils.py +374 -218
  84. ngio-0.5.0b4.dist-info/METADATA +147 -0
  85. ngio-0.5.0b4.dist-info/RECORD +88 -0
  86. {ngio-0.2.0a2.dist-info → ngio-0.5.0b4.dist-info}/WHEEL +1 -1
  87. ngio/common/_array_pipe.py +0 -160
  88. ngio/common/_axes_transforms.py +0 -63
  89. ngio/common/_common_types.py +0 -5
  90. ngio/common/_slicer.py +0 -97
  91. ngio/images/abstract_image.py +0 -240
  92. ngio/images/create.py +0 -251
  93. ngio/images/image.py +0 -389
  94. ngio/images/label.py +0 -236
  95. ngio/images/omezarr_container.py +0 -535
  96. ngio/ome_zarr_meta/_generic_handlers.py +0 -320
  97. ngio/ome_zarr_meta/v04/_meta_handlers.py +0 -54
  98. ngio/tables/_validators.py +0 -192
  99. ngio/tables/backends/_anndata_v1.py +0 -75
  100. ngio/tables/backends/_json_v1.py +0 -56
  101. ngio/tables/tables_container.py +0 -300
  102. ngio/tables/v1/_masking_roi_table.py +0 -175
  103. ngio/utils/_logger.py +0 -29
  104. ngio-0.2.0a2.dist-info/METADATA +0 -95
  105. ngio-0.2.0a2.dist-info/RECORD +0 -53
  106. {ngio-0.2.0a2.dist-info → ngio-0.5.0b4.dist-info}/licenses/LICENSE +0 -0
ngio/hcs/_plate.py ADDED
@@ -0,0 +1,1354 @@
1
+ """A module for handling the Plate Sequence in an OME-Zarr file."""
2
+
3
+ import asyncio
4
+ import warnings
5
+ from collections.abc import Sequence
6
+ from typing import Literal
7
+
8
+ from ngio.images import (
9
+ OmeZarrContainer,
10
+ concatenate_image_tables,
11
+ concatenate_image_tables_as,
12
+ concatenate_image_tables_as_async,
13
+ concatenate_image_tables_async,
14
+ list_image_tables,
15
+ list_image_tables_async,
16
+ )
17
+ from ngio.ome_zarr_meta import (
18
+ DefaultNgffVersion,
19
+ ImageInWellPath,
20
+ NgffVersions,
21
+ NgioPlateMeta,
22
+ NgioWellMeta,
23
+ PlateMetaHandler,
24
+ WellMetaHandler,
25
+ path_in_well_validation,
26
+ update_ngio_plate_meta,
27
+ update_ngio_well_meta,
28
+ )
29
+ from ngio.tables import (
30
+ ConditionTable,
31
+ DefaultTableBackend,
32
+ FeatureTable,
33
+ GenericRoiTable,
34
+ MaskingRoiTable,
35
+ RoiTable,
36
+ Table,
37
+ TableBackend,
38
+ TablesContainer,
39
+ TableType,
40
+ TypedTable,
41
+ )
42
+ from ngio.utils import (
43
+ AccessModeLiteral,
44
+ NgioCache,
45
+ NgioError,
46
+ NgioValueError,
47
+ StoreOrGroup,
48
+ ZarrGroupHandler,
49
+ )
50
+
51
+
52
+ def _try_get_table_container(
53
+ handler: ZarrGroupHandler, create_mode: bool = True
54
+ ) -> TablesContainer | None:
55
+ """Return a default table container."""
56
+ try:
57
+ table_handler = handler.get_handler("tables", create_mode=create_mode)
58
+ return TablesContainer(table_handler)
59
+ except NgioError:
60
+ return None
61
+
62
+
63
+ # Mock lock class that does nothing
64
+ class MockLock:
65
+ """A mock lock class that does nothing."""
66
+
67
+ def __enter__(self):
68
+ """Enter the lock."""
69
+ pass
70
+
71
+ def __exit__(self, exc_type, exc_value, traceback):
72
+ """Exit the lock."""
73
+ pass
74
+
75
+
76
+ class OmeZarrWell:
77
+ """A class to handle the Well Sequence in an OME-Zarr file."""
78
+
79
+ def __init__(self, group_handler: ZarrGroupHandler) -> None:
80
+ """Initialize the LabelGroupHandler.
81
+
82
+ Args:
83
+ group_handler: The Zarr group handler that contains the Well.
84
+ """
85
+ self._group_handler = group_handler
86
+ self._meta_handler = WellMetaHandler(group_handler)
87
+
88
+ def __repr__(self) -> str:
89
+ """Return a string representation of the well."""
90
+ return f"Well(#images: {len(self.paths())})"
91
+
92
+ @property
93
+ def meta_handler(self):
94
+ """Return the metadata handler."""
95
+ return self._meta_handler
96
+
97
+ @property
98
+ def meta(self):
99
+ """Return the metadata."""
100
+ return self._meta_handler.get_meta()
101
+
102
+ @property
103
+ def acquisition_ids(self) -> list[int]:
104
+ """Return the acquisitions ids in the well."""
105
+ return self.meta.acquisition_ids
106
+
107
+ def paths(self, acquisition: int | None = None) -> list[str]:
108
+ """Return the images paths in the well.
109
+
110
+ If acquisition is None, return all images paths in the well.
111
+ Else, return the images paths in the well for the given acquisition.
112
+
113
+ Args:
114
+ acquisition (int | None): The acquisition id to filter the images.
115
+ """
116
+ return self.meta.paths(acquisition)
117
+
118
+ def get_image_store(self, image_path: str) -> StoreOrGroup:
119
+ """Get the image store from the well.
120
+
121
+ Args:
122
+ image_path (str): The path of the image.
123
+ """
124
+ return self._group_handler.get_group(image_path, create_mode=True)
125
+
126
+ def get_image_acquisition_id(self, image_path: str) -> int | None:
127
+ """Get the acquisition id of an image in the well.
128
+
129
+ Args:
130
+ image_path (str): The path of the image.
131
+
132
+ Returns:
133
+ int | None: The acquisition id of the image.
134
+ """
135
+ return self.meta.get_image_acquisition_id(image_path=image_path)
136
+
137
+ def get_image(self, image_path: str) -> OmeZarrContainer:
138
+ """Get an image from the well.
139
+
140
+ Args:
141
+ image_path (str): The path of the image.
142
+
143
+ Returns:
144
+ OmeZarrContainer: The image.
145
+ """
146
+ handler = self._group_handler.get_handler(image_path)
147
+ return OmeZarrContainer(handler)
148
+
149
+ def _add_image(
150
+ self,
151
+ image_path: str,
152
+ acquisition_id: int | None = None,
153
+ strict: bool = True,
154
+ atomic: bool = False,
155
+ ) -> StoreOrGroup:
156
+ """Add an image to an ome-zarr well."""
157
+ image_path = path_in_well_validation(path=image_path)
158
+
159
+ if atomic:
160
+ well_lock = self._group_handler.lock
161
+ else:
162
+ well_lock = MockLock()
163
+
164
+ with well_lock:
165
+ meta = self.meta.add_image(
166
+ path=image_path, acquisition=acquisition_id, strict=strict
167
+ )
168
+ self.meta_handler.update_meta(meta)
169
+ self.meta_handler._group_handler.clean_cache()
170
+
171
+ return self._group_handler.get_group(image_path, create_mode=True)
172
+
173
+ def atomic_add_image(
174
+ self,
175
+ image_path: str,
176
+ acquisition_id: int | None = None,
177
+ strict: bool = True,
178
+ ) -> StoreOrGroup:
179
+ """Parallel safe version of add_image."""
180
+ return self._add_image(
181
+ image_path=image_path,
182
+ acquisition_id=acquisition_id,
183
+ atomic=True,
184
+ strict=strict,
185
+ )
186
+
187
+ def add_image(
188
+ self,
189
+ image_path: str,
190
+ acquisition_id: int | None = None,
191
+ strict: bool = True,
192
+ ) -> StoreOrGroup:
193
+ """Add an image to an ome-zarr well.
194
+
195
+ Args:
196
+ image_path (str): The path of the image.
197
+ acquisition_id (int | None): The acquisition id to filter the images.
198
+ strict (bool): Whether to check if the acquisition id is already exists
199
+ in the well. Defaults to True. If False this might lead to
200
+ acquisition in a well that does not exist at the plate level.
201
+ """
202
+ return self._add_image(
203
+ image_path=image_path,
204
+ acquisition_id=acquisition_id,
205
+ atomic=False,
206
+ strict=strict,
207
+ )
208
+
209
+
210
+ def _build_extras(paths: Sequence[str]) -> list[dict[str, str]]:
211
+ """Build the extras for the images.
212
+
213
+ Args:
214
+ paths (Sequence[str]): The paths of the images.
215
+
216
+ Returns:
217
+ list[dict[str, str]]: The extras for the images.
218
+ """
219
+ extras = []
220
+ for path in paths:
221
+ row, column, path_in_well = path.split("/")
222
+ extras.append(
223
+ {
224
+ "row": row,
225
+ "column": column,
226
+ "path_in_well": path_in_well,
227
+ }
228
+ )
229
+ return extras
230
+
231
+
232
+ class OmeZarrPlate:
233
+ """A class to handle the Plate Sequence in an OME-Zarr file."""
234
+
235
+ def __init__(
236
+ self,
237
+ group_handler: ZarrGroupHandler,
238
+ table_container: TablesContainer | None = None,
239
+ ) -> None:
240
+ """Initialize the LabelGroupHandler.
241
+
242
+ Args:
243
+ group_handler: The Zarr group handler that contains the Plate.
244
+ table_container: The tables container that contains plate level tables.
245
+ """
246
+ self._group_handler = group_handler
247
+ self._meta_handler = PlateMetaHandler(group_handler)
248
+ self._tables_container = table_container
249
+ self._wells_cache: NgioCache[OmeZarrWell] = NgioCache(
250
+ use_cache=self._group_handler.use_cache
251
+ )
252
+ self._images_cache: NgioCache[OmeZarrContainer] = NgioCache(
253
+ use_cache=self._group_handler.use_cache
254
+ )
255
+
256
+ def __repr__(self) -> str:
257
+ """Return a string representation of the plate."""
258
+ return f"Plate([rows x columns] ({len(self.rows)} x {len(self.columns)}))"
259
+
260
+ @property
261
+ def meta_handler(self):
262
+ """Return the metadata handler."""
263
+ return self._meta_handler
264
+
265
+ @property
266
+ def meta(self):
267
+ """Return the metadata."""
268
+ return self._meta_handler.get_meta()
269
+
270
+ @property
271
+ def columns(self) -> list[str]:
272
+ """Return the number of columns in the plate."""
273
+ return self.meta.columns
274
+
275
+ @property
276
+ def rows(self) -> list[str]:
277
+ """Return the number of rows in the plate."""
278
+ return self.meta.rows
279
+
280
+ @property
281
+ def acquisitions_names(self) -> list[str | None]:
282
+ """Return the acquisitions in the plate."""
283
+ return self.meta.acquisitions_names
284
+
285
+ @property
286
+ def acquisition_ids(self) -> list[int]:
287
+ """Return the acquisitions ids in the plate."""
288
+ return self.meta.acquisition_ids
289
+
290
+ def _well_path(self, row: str, column: int | str) -> str:
291
+ """Return the well path in the plate."""
292
+ return self.meta.get_well_path(row=row, column=column)
293
+
294
+ def _image_path(self, row: str, column: int | str, path: str) -> str:
295
+ """Return the image path in the plate."""
296
+ well = self.get_well(row, column)
297
+ if path not in well.paths():
298
+ raise ValueError(f"Image {path} does not exist in well {row}{column}")
299
+ return f"{self._well_path(row, column)}/{path}"
300
+
301
+ def wells_paths(self) -> list[str]:
302
+ """Return the wells paths in the plate."""
303
+ return self.meta.wells_paths
304
+
305
+ async def images_paths_async(self, acquisition: int | None = None) -> list[str]:
306
+ """Return the images paths in the plate asynchronously.
307
+
308
+ If acquisition is None, return all images paths in the plate.
309
+ Else, return the images paths in the plate for the given acquisition.
310
+
311
+ Args:
312
+ acquisition (int | None): The acquisition id to filter the images.
313
+ """
314
+ wells = await self.get_wells_async()
315
+ paths = []
316
+ for well_path, well in wells.items():
317
+ for img_path in well.paths(acquisition):
318
+ paths.append(f"{well_path}/{img_path}")
319
+ return paths
320
+
321
+ def images_paths(self, acquisition: int | None = None) -> list[str]:
322
+ """Return the images paths in the plate.
323
+
324
+ If acquisition is None, return all images paths in the plate.
325
+ Else, return the images paths in the plate for the given acquisition.
326
+
327
+ Args:
328
+ acquisition (int | None): The acquisition id to filter the images.
329
+ """
330
+ wells = self.get_wells()
331
+ images = []
332
+ for well_path, well in wells.items():
333
+ for img_path in well.paths(acquisition):
334
+ images.append(f"{well_path}/{img_path}")
335
+ return images
336
+
337
+ def well_images_paths(
338
+ self, row: str, column: int | str, acquisition: int | None = None
339
+ ) -> list[str]:
340
+ """Return the images paths in a well.
341
+
342
+ If acquisition is None, return all images paths in the well.
343
+ Else, return the images paths in the well for the given acquisition.
344
+
345
+ Args:
346
+ row (str): The row of the well.
347
+ column (int | str): The column of the well.
348
+ acquisition (int | None): The acquisition id to filter the images.
349
+ """
350
+ images = []
351
+ well = self.get_well(row=row, column=column)
352
+ for path in well.paths(acquisition):
353
+ images.append(self._image_path(row=row, column=column, path=path))
354
+ return images
355
+
356
+ def get_image_acquisition_id(
357
+ self, row: str, column: int | str, image_path: str
358
+ ) -> int | None:
359
+ """Get the acquisition id of an image in a well.
360
+
361
+ Args:
362
+ row (str): The row of the well.
363
+ column (int | str): The column of the well.
364
+ image_path (str): The path of the image.
365
+
366
+ Returns:
367
+ int | None: The acquisition id of the image.
368
+ """
369
+ well = self.get_well(row=row, column=column)
370
+ return well.get_image_acquisition_id(image_path=image_path)
371
+
372
+ def _get_well(self, well_path: str) -> OmeZarrWell:
373
+ """Get a well from the plate by its path.
374
+
375
+ Args:
376
+ well_path (str): The path of the well.
377
+
378
+ Returns:
379
+ OmeZarrWell: The well.
380
+
381
+ """
382
+ cached_well = self._wells_cache.get(well_path)
383
+ if cached_well is not None:
384
+ return cached_well
385
+
386
+ group_handler = self._group_handler.get_handler(well_path)
387
+ self._wells_cache.set(well_path, OmeZarrWell(group_handler))
388
+ return OmeZarrWell(group_handler)
389
+
390
+ def get_well(self, row: str, column: int | str) -> OmeZarrWell:
391
+ """Get a well from the plate.
392
+
393
+ Args:
394
+ row (str): The row of the well.
395
+ column (int | str): The column of the well.
396
+
397
+ Returns:
398
+ OmeZarrWell: The well.
399
+ """
400
+ well_path = self._well_path(row=row, column=column)
401
+ return self._get_well(well_path=well_path)
402
+
403
+ async def get_wells_async(self) -> dict[str, OmeZarrWell]:
404
+ """Get all wells in the plate asynchronously.
405
+
406
+ This method processes wells in parallel for improved performance
407
+ when working with a large number of wells.
408
+
409
+ Returns:
410
+ dict[str, OmeZarrWell]: A dictionary of wells, where the key is the well
411
+ path and the value is the well object.
412
+ """
413
+ wells, tasks = {}, []
414
+ for well_path in self.wells_paths():
415
+ task = asyncio.to_thread(
416
+ lambda well_path: (well_path, self._get_well(well_path)), well_path
417
+ )
418
+ tasks.append(task)
419
+
420
+ results = await asyncio.gather(*tasks)
421
+ for well_path, well in results:
422
+ wells[well_path] = well
423
+
424
+ return wells
425
+
426
+ def get_wells(self) -> dict[str, OmeZarrWell]:
427
+ """Get all wells in the plate.
428
+
429
+ Returns:
430
+ dict[str, OmeZarrWell]: A dictionary of wells, where the key is the well
431
+ path and the value is the well object.
432
+ """
433
+ wells = {}
434
+ for well_path in self.wells_paths():
435
+ wells[well_path] = self._get_well(well_path)
436
+ return wells
437
+
438
+ def _get_image(self, image_path: str) -> OmeZarrContainer:
439
+ """Get an image from the plate by its path.
440
+
441
+ Args:
442
+ image_path (str): The path of the image.
443
+ """
444
+ cached_image = self._images_cache.get(image_path)
445
+ if cached_image is not None:
446
+ return cached_image
447
+ img_group_handler = self._group_handler.get_handler(image_path)
448
+ image = OmeZarrContainer(img_group_handler)
449
+ self._images_cache.set(image_path, image)
450
+ return image
451
+
452
+ async def get_images_async(
453
+ self, acquisition: int | None = None
454
+ ) -> dict[str, OmeZarrContainer]:
455
+ """Get all images in the plate asynchronously.
456
+
457
+ This method processes images in parallel for improved performance
458
+ when working with a large number of images.
459
+
460
+ Args:
461
+ acquisition: The acquisition id to filter the images.
462
+
463
+ Returns:
464
+ dict[str, OmeZarrContainer]: A dictionary of images, where the key is the
465
+ image path and the value is the image object.
466
+ """
467
+ paths = await self.images_paths_async(acquisition=acquisition)
468
+
469
+ images, tasks = {}, []
470
+ for image_path in paths:
471
+ task = asyncio.to_thread(
472
+ lambda image_path: (image_path, self._get_image(image_path)), image_path
473
+ )
474
+ tasks.append(task)
475
+
476
+ results = await asyncio.gather(*tasks)
477
+
478
+ for image_path, image in results:
479
+ images[image_path] = image
480
+ return images
481
+
482
+ def get_images(self, acquisition: int | None = None) -> dict[str, OmeZarrContainer]:
483
+ """Get all images in the plate.
484
+
485
+ Args:
486
+ acquisition: The acquisition id to filter the images.
487
+ """
488
+ paths = self.images_paths(acquisition=acquisition)
489
+ images = {}
490
+ for image_path in paths:
491
+ images[image_path] = self._get_image(image_path)
492
+
493
+ return images
494
+
495
+ def get_image(
496
+ self, row: str, column: int | str, image_path: str
497
+ ) -> OmeZarrContainer:
498
+ """Get an image from the plate.
499
+
500
+ Args:
501
+ row (str): The row of the well.
502
+ column (int | str): The column of the well.
503
+ image_path (str): The path of the image.
504
+
505
+ Returns:
506
+ OmeZarrContainer: The image.
507
+ """
508
+ image_path = self._image_path(row=row, column=column, path=image_path)
509
+ return self._get_image(image_path)
510
+
511
+ def get_image_store(
512
+ self, row: str, column: int | str, image_path: str
513
+ ) -> StoreOrGroup:
514
+ """Get the image store from the plate.
515
+
516
+ Args:
517
+ row (str): The row of the well.
518
+ column (int | str): The column of the well.
519
+ image_path (str): The path of the image.
520
+ """
521
+ well = self.get_well(row=row, column=column)
522
+ return well.get_image_store(image_path=image_path)
523
+
524
+ def get_well_images(
525
+ self, row: str, column: str | int, acquisition: int | None = None
526
+ ) -> dict[str, OmeZarrContainer]:
527
+ """Get all images in a well.
528
+
529
+ Args:
530
+ row: The row of the well.
531
+ column: The column of the well.
532
+ acquisition: The acquisition id to filter the images.
533
+ """
534
+ images = {}
535
+ for image_paths in self.well_images_paths(
536
+ row=row, column=column, acquisition=acquisition
537
+ ):
538
+ group_handler = self._group_handler.get_handler(image_paths)
539
+ images[image_paths] = OmeZarrContainer(group_handler)
540
+ return images
541
+
542
+ def _add_image(
543
+ self,
544
+ row: str,
545
+ column: int | str,
546
+ image_path: str | None = None,
547
+ acquisition_id: int | None = None,
548
+ acquisition_name: str | None = None,
549
+ atomic: bool = False,
550
+ ) -> str:
551
+ """Add an image to an ome-zarr plate."""
552
+ if image_path is not None:
553
+ image_path = path_in_well_validation(path=image_path)
554
+
555
+ if atomic:
556
+ plate_lock = self._group_handler.lock
557
+ else:
558
+ plate_lock = MockLock()
559
+
560
+ with plate_lock:
561
+ meta = self.meta
562
+ meta = meta.add_well(row=row, column=column)
563
+ if acquisition_id is not None:
564
+ meta = meta.add_acquisition(
565
+ acquisition_id=acquisition_id, acquisition_name=acquisition_name
566
+ )
567
+ self.meta_handler.update_meta(meta)
568
+ self.meta_handler._group_handler.clean_cache()
569
+
570
+ well_path = self.meta.get_well_path(row=row, column=column)
571
+ group_handler = self._group_handler.get_handler(well_path)
572
+
573
+ if atomic:
574
+ well_lock = group_handler.lock
575
+ else:
576
+ well_lock = MockLock()
577
+
578
+ with well_lock:
579
+ attrs = group_handler.load_attrs()
580
+ if len(attrs) == 0:
581
+ # Initialize the well metadata
582
+ # if the group is empty
583
+ well_meta = NgioWellMeta.default_init()
584
+ version = self.meta.plate.version
585
+ version = version if version is not None else "0.4"
586
+ update_ngio_well_meta(group_handler, well_meta)
587
+ meta_handler = WellMetaHandler(group_handler=group_handler)
588
+ else:
589
+ meta_handler = WellMetaHandler(group_handler=group_handler)
590
+ well_meta = meta_handler.get_meta()
591
+
592
+ group_handler = self._group_handler.get_handler(well_path)
593
+
594
+ if image_path is not None:
595
+ well_meta = well_meta.add_image(
596
+ path=image_path, acquisition=acquisition_id, strict=False
597
+ )
598
+ meta_handler.update_meta(well_meta)
599
+ meta_handler._group_handler.clean_cache()
600
+
601
+ if image_path is not None:
602
+ return f"{well_path}/{image_path}"
603
+ return well_path
604
+
605
+ def atomic_add_image(
606
+ self,
607
+ row: str,
608
+ column: int | str,
609
+ image_path: str,
610
+ acquisition_id: int | None = None,
611
+ acquisition_name: str | None = None,
612
+ ) -> str:
613
+ """Parallel safe version of add_image."""
614
+ if image_path is None:
615
+ raise ValueError(
616
+ "Image path cannot be None for atomic add_image. "
617
+ "If your intent is to add a well, use add_well instead."
618
+ )
619
+ path = self._add_image(
620
+ row=row,
621
+ column=column,
622
+ image_path=image_path,
623
+ acquisition_id=acquisition_id,
624
+ acquisition_name=acquisition_name,
625
+ atomic=True,
626
+ )
627
+ return path
628
+
629
+ def add_image(
630
+ self,
631
+ row: str,
632
+ column: int | str,
633
+ image_path: str,
634
+ acquisition_id: int | None = None,
635
+ acquisition_name: str | None = None,
636
+ ) -> str:
637
+ """Add an image to an ome-zarr plate."""
638
+ if image_path is None:
639
+ raise ValueError(
640
+ "Image path cannot be None for atomic add_image. "
641
+ "If your intent is to add a well, use add_well instead."
642
+ )
643
+ path = self._add_image(
644
+ row=row,
645
+ column=column,
646
+ image_path=image_path,
647
+ acquisition_id=acquisition_id,
648
+ acquisition_name=acquisition_name,
649
+ atomic=False,
650
+ )
651
+ return path
652
+
653
+ def add_well(
654
+ self,
655
+ row: str,
656
+ column: int | str,
657
+ ) -> OmeZarrWell:
658
+ """Add a well to an ome-zarr plate."""
659
+ _ = self._add_image(
660
+ row=row,
661
+ column=column,
662
+ image_path=None,
663
+ acquisition_id=None,
664
+ acquisition_name=None,
665
+ atomic=False,
666
+ )
667
+ return self.get_well(row=row, column=column)
668
+
669
+ def add_column(
670
+ self,
671
+ column: int | str,
672
+ ) -> "OmeZarrPlate":
673
+ """Add a column to an ome-zarr plate."""
674
+ meta, _ = self.meta.add_column(column)
675
+ self.meta_handler.update_meta(meta)
676
+ self.meta_handler._group_handler.clean_cache()
677
+ return self
678
+
679
+ def add_row(
680
+ self,
681
+ row: str,
682
+ ) -> "OmeZarrPlate":
683
+ """Add a row to an ome-zarr plate."""
684
+ meta, _ = self.meta.add_row(row)
685
+ self.meta_handler.update_meta(meta)
686
+ self.meta_handler._group_handler.clean_cache()
687
+ return self
688
+
689
+ def add_acquisition(
690
+ self,
691
+ acquisition_id: int,
692
+ acquisition_name: str,
693
+ ) -> "OmeZarrPlate":
694
+ """Add an acquisition to an ome-zarr plate.
695
+
696
+ Be aware that this is not a parallel safe operation.
697
+
698
+ Args:
699
+ acquisition_id (int): The acquisition id.
700
+ acquisition_name (str): The acquisition name.
701
+ """
702
+ meta = self.meta.add_acquisition(
703
+ acquisition_id=acquisition_id, acquisition_name=acquisition_name
704
+ )
705
+ self.meta_handler.update_meta(meta)
706
+ self.meta_handler._group_handler.clean_cache()
707
+ return self
708
+
709
+ def _remove_well(
710
+ self,
711
+ row: str,
712
+ column: int | str,
713
+ atomic: bool = False,
714
+ ):
715
+ """Remove a well from an ome-zarr plate."""
716
+ if atomic:
717
+ plate_lock = self._group_handler.lock
718
+ else:
719
+ plate_lock = MockLock()
720
+
721
+ with plate_lock:
722
+ meta = self.meta
723
+ meta = meta.remove_well(row, column)
724
+ self.meta_handler.update_meta(meta)
725
+ self.meta_handler._group_handler.clean_cache()
726
+
727
+ def _remove_image(
728
+ self,
729
+ row: str,
730
+ column: int | str,
731
+ image_path: str,
732
+ atomic: bool = False,
733
+ ):
734
+ """Remove an image from an ome-zarr plate."""
735
+ well = self.get_well(row, column)
736
+
737
+ if atomic:
738
+ well_lock = well.meta_handler._group_handler.lock
739
+ else:
740
+ well_lock = MockLock()
741
+
742
+ with well_lock:
743
+ well_meta = well.meta
744
+ well_meta = well_meta.remove_image(path=image_path)
745
+ well.meta_handler.update_meta(well_meta)
746
+ well.meta_handler._group_handler.clean_cache()
747
+ if len(well_meta.paths()) == 0:
748
+ self._remove_well(row, column, atomic=atomic)
749
+
750
+ def atomic_remove_image(
751
+ self,
752
+ row: str,
753
+ column: int | str,
754
+ image_path: str,
755
+ ):
756
+ """Parallel safe version of remove_image."""
757
+ return self._remove_image(
758
+ row=row,
759
+ column=column,
760
+ image_path=image_path,
761
+ atomic=True,
762
+ )
763
+
764
+ def remove_image(
765
+ self,
766
+ row: str,
767
+ column: int | str,
768
+ image_path: str,
769
+ ):
770
+ """Remove an image from an ome-zarr plate."""
771
+ return self._remove_image(
772
+ row=row,
773
+ column=column,
774
+ image_path=image_path,
775
+ atomic=False,
776
+ )
777
+
778
+ def derive_plate(
779
+ self,
780
+ store: StoreOrGroup,
781
+ plate_name: str | None = None,
782
+ version: NgffVersions | None = None,
783
+ ngff_version: NgffVersions = DefaultNgffVersion,
784
+ keep_acquisitions: bool = False,
785
+ cache: bool = False,
786
+ overwrite: bool = False,
787
+ ) -> "OmeZarrPlate":
788
+ """Derive a new OME-Zarr plate from an existing one.
789
+
790
+ Args:
791
+ store (StoreOrGroup): The Zarr store or group that stores the plate.
792
+ plate_name (str | None): The name of the new plate.
793
+ version (NgffVersion | None): Deprecated. Please use 'ngff_version' instead.
794
+ ngff_version (NgffVersion): The NGFF version to use for the new plate.
795
+ keep_acquisitions (bool): Whether to keep the acquisitions in the new plate.
796
+ cache (bool): Whether to use a cache for the zarr group metadata.
797
+ overwrite (bool): Whether to overwrite the existing plate.
798
+ """
799
+ return derive_ome_zarr_plate(
800
+ ome_zarr_plate=self,
801
+ store=store,
802
+ plate_name=plate_name,
803
+ ngff_version=ngff_version,
804
+ version=version,
805
+ keep_acquisitions=keep_acquisitions,
806
+ cache=cache,
807
+ overwrite=overwrite,
808
+ )
809
+
810
+ def _get_tables_container(self, create_mode: bool = True) -> TablesContainer | None:
811
+ """Return the tables container."""
812
+ if self._tables_container is not None:
813
+ return self._tables_container
814
+ _tables_container = _try_get_table_container(
815
+ self._group_handler, create_mode=create_mode
816
+ )
817
+ self._tables_container = _tables_container
818
+ return self._tables_container
819
+
820
+ @property
821
+ def tables_container(self) -> TablesContainer:
822
+ """Return the tables container."""
823
+ _table_container = self._get_tables_container()
824
+ if _table_container is None:
825
+ raise NgioValueError(
826
+ "No tables container found. Please add a tables container to the plate."
827
+ )
828
+ return _table_container
829
+
830
+ def list_tables(self, filter_types: TypedTable | str | None = None) -> list[str]:
831
+ """List all tables in the image."""
832
+ _tables_container = self._get_tables_container(create_mode=False)
833
+ if _tables_container is None:
834
+ return []
835
+ return self.tables_container.list(filter_types=filter_types)
836
+
837
+ def list_roi_tables(self) -> list[str]:
838
+ """List all ROI tables in the image."""
839
+ roi = self.tables_container.list(
840
+ filter_types="roi_table",
841
+ )
842
+ masking_roi = self.tables_container.list(
843
+ filter_types="masking_roi_table",
844
+ )
845
+ return roi + masking_roi
846
+
847
+ def get_roi_table(self, name: str) -> RoiTable:
848
+ """Get a ROI table from the image.
849
+
850
+ Args:
851
+ name (str): The name of the table.
852
+ """
853
+ table = self.tables_container.get(name=name, strict=True)
854
+ if not isinstance(table, RoiTable):
855
+ raise NgioValueError(f"Table {name} is not a ROI table. Got {type(table)}")
856
+ return table
857
+
858
+ def get_masking_roi_table(self, name: str) -> MaskingRoiTable:
859
+ """Get a masking ROI table from the image.
860
+
861
+ Args:
862
+ name (str): The name of the table.
863
+ """
864
+ table = self.tables_container.get(name=name, strict=True)
865
+ if not isinstance(table, MaskingRoiTable):
866
+ raise NgioValueError(
867
+ f"Table {name} is not a masking ROI table. Got {type(table)}"
868
+ )
869
+ return table
870
+
871
+ def get_feature_table(self, name: str) -> FeatureTable:
872
+ """Get a feature table from the image.
873
+
874
+ Args:
875
+ name (str): The name of the table.
876
+ """
877
+ table = self.tables_container.get(name=name, strict=True)
878
+ if not isinstance(table, FeatureTable):
879
+ raise NgioValueError(
880
+ f"Table {name} is not a feature table. Got {type(table)}"
881
+ )
882
+ return table
883
+
884
+ def get_generic_roi_table(self, name: str) -> GenericRoiTable:
885
+ """Get a generic ROI table from the image.
886
+
887
+ Args:
888
+ name (str): The name of the table.
889
+ """
890
+ table = self.tables_container.get(name=name, strict=True)
891
+ if not isinstance(table, GenericRoiTable):
892
+ raise NgioValueError(
893
+ f"Table {name} is not a generic ROI table. Got {type(table)}"
894
+ )
895
+ return table
896
+
897
+ def get_condition_table(self, name: str) -> ConditionTable:
898
+ """Get a condition table from the image.
899
+
900
+ Args:
901
+ name (str): The name of the table.
902
+ """
903
+ table = self.tables_container.get(name=name, strict=True)
904
+ if not isinstance(table, ConditionTable):
905
+ raise NgioValueError(
906
+ f"Table {name} is not a condition table. Got {type(table)}"
907
+ )
908
+ return table
909
+
910
+ def get_table(self, name: str, check_type: TypedTable | None = None) -> Table:
911
+ """Get a table from the image.
912
+
913
+ Args:
914
+ name (str): The name of the table.
915
+ check_type (TypedTable | None): Deprecated. Please use
916
+ 'get_table_as' instead, or one of the type specific
917
+ get_*table() methods.
918
+
919
+ """
920
+ if check_type is not None:
921
+ warnings.warn(
922
+ "The 'check_type' argument is deprecated, and will be removed in "
923
+ "ngio=0.3. Use 'get_table_as' instead or one of the "
924
+ "type specific get_*table() methods.",
925
+ DeprecationWarning,
926
+ stacklevel=2,
927
+ )
928
+ return self.tables_container.get(name=name, strict=False)
929
+
930
+ def get_table_as(
931
+ self,
932
+ name: str,
933
+ table_cls: type[TableType],
934
+ backend: TableBackend | None = None,
935
+ ) -> TableType:
936
+ """Get a table from the image as a specific type.
937
+
938
+ Args:
939
+ name (str): The name of the table.
940
+ table_cls (type[TableType]): The type of the table.
941
+ backend (TableBackend | None): The backend to use. If None,
942
+ the default backend is used.
943
+ """
944
+ return self.tables_container.get_as(
945
+ name=name,
946
+ table_cls=table_cls,
947
+ backend=backend,
948
+ )
949
+
950
+ def add_table(
951
+ self,
952
+ name: str,
953
+ table: Table,
954
+ backend: TableBackend = DefaultTableBackend,
955
+ overwrite: bool = False,
956
+ ) -> None:
957
+ """Add a table to the image."""
958
+ self.tables_container.add(
959
+ name=name, table=table, backend=backend, overwrite=overwrite
960
+ )
961
+
962
+ def delete_table(self, name: str, missing_ok: bool = False) -> None:
963
+ """Delete a table from the group.
964
+
965
+ Args:
966
+ name (str): The name of the table to delete.
967
+ missing_ok (bool): If True, do not raise an error if the table does not
968
+ exist.
969
+
970
+ """
971
+ table_container = self._get_tables_container(create_mode=False)
972
+ if table_container is None and missing_ok:
973
+ return
974
+ if table_container is None:
975
+ raise NgioValueError(
976
+ f"No tables found in the image, cannot delete {name}. "
977
+ "Set missing_ok=True to ignore this error."
978
+ )
979
+ table_container.delete(name=name, missing_ok=missing_ok)
980
+
981
+ def list_image_tables(
982
+ self,
983
+ acquisition: int | None = None,
984
+ filter_types: str | None = None,
985
+ mode: Literal["common", "all"] = "common",
986
+ ) -> list[str]:
987
+ """List all image tables in the image.
988
+
989
+ Args:
990
+ acquisition (int | None): The acquisition id to filter the images.
991
+ filter_types (str | None): The type of tables to filter. If None,
992
+ return all tables. Defaults to None.
993
+ mode (Literal["common", "all"]): The mode to use for listing the tables.
994
+ If 'common', return only common tables between all images.
995
+ If 'all', return all tables. Defaults to 'common'.
996
+ """
997
+ images = tuple(self.get_images(acquisition=acquisition).values())
998
+ return list_image_tables(
999
+ images=images,
1000
+ filter_types=filter_types,
1001
+ mode=mode,
1002
+ )
1003
+
1004
+ async def list_image_tables_async(
1005
+ self,
1006
+ acquisition: int | None = None,
1007
+ filter_types: str | None = None,
1008
+ mode: Literal["common", "all"] = "common",
1009
+ ) -> list[str]:
1010
+ """List all image tables in the image asynchronously.
1011
+
1012
+ Args:
1013
+ acquisition (int | None): The acquisition id to filter the images.
1014
+ filter_types (str | None): The type of tables to filter. If None,
1015
+ return all tables. Defaults to None.
1016
+ mode (Literal["common", "all"]): The mode to use for listing the tables.
1017
+ If 'common', return only common tables between all images.
1018
+ If 'all', return all tables. Defaults to 'common'.
1019
+ """
1020
+ images = await self.get_images_async(acquisition=acquisition)
1021
+ images = tuple(images.values())
1022
+ return await list_image_tables_async(
1023
+ images=images,
1024
+ filter_types=filter_types,
1025
+ mode=mode,
1026
+ )
1027
+
1028
+ def concatenate_image_tables(
1029
+ self,
1030
+ name: str,
1031
+ acquisition: int | None = None,
1032
+ strict: bool = True,
1033
+ index_key: str | None = None,
1034
+ mode: Literal["eager", "lazy"] = "eager",
1035
+ ) -> Table:
1036
+ """Concatenate tables from all images in the plate.
1037
+
1038
+ Args:
1039
+ name: The name of the table to concatenate.
1040
+ index_key: The key to use for the index of the concatenated table.
1041
+ acquisition: The acquisition id to filter the images.
1042
+ strict: If True, raise an error if the table is not found in the image.
1043
+ index_key: If a string is provided, a new index column will be created
1044
+ new_index_pattern = {row}_{column}_{path_in_well}_{label}
1045
+ mode: The mode to use for concatenation. Can be 'eager' or 'lazy'.
1046
+ if 'eager', the table will be loaded into memory.
1047
+ if 'lazy', the table will be loaded as a lazy frame.
1048
+ """
1049
+ images = self.get_images(acquisition=acquisition)
1050
+ extras = _build_extras(tuple(images.keys()))
1051
+ return concatenate_image_tables(
1052
+ images=tuple(images.values()),
1053
+ extras=extras,
1054
+ name=name,
1055
+ index_key=index_key,
1056
+ strict=strict,
1057
+ mode=mode,
1058
+ )
1059
+
1060
+ def concatenate_image_tables_as(
1061
+ self,
1062
+ name: str,
1063
+ table_cls: type[TableType],
1064
+ acquisition: int | None = None,
1065
+ index_key: str | None = None,
1066
+ strict: bool = True,
1067
+ mode: Literal["eager", "lazy"] = "eager",
1068
+ ) -> TableType:
1069
+ """Concatenate tables from all images in the plate as a specific type.
1070
+
1071
+ Args:
1072
+ name: The name of the table to concatenate.
1073
+ table_cls: The type of the table to concatenate.
1074
+ index_key: The key to use for the index of the concatenated table.
1075
+ acquisition: The acquisition id to filter the images.
1076
+ index_key: If a string is provided, a new index column will be created
1077
+ new_index_pattern = {row}_{column}_{path_in_well}_{label}
1078
+ strict: If True, raise an error if the table is not found in the image.
1079
+ mode: The mode to use for concatenation. Can be 'eager' or 'lazy'.
1080
+ if 'eager', the table will be loaded into memory.
1081
+ if 'lazy', the table will be loaded as a lazy frame.
1082
+ """
1083
+ images = self.get_images(acquisition=acquisition)
1084
+ extras = _build_extras(tuple(images.keys()))
1085
+ return concatenate_image_tables_as(
1086
+ images=tuple(images.values()),
1087
+ extras=extras,
1088
+ name=name,
1089
+ table_cls=table_cls,
1090
+ index_key=index_key,
1091
+ strict=strict,
1092
+ mode=mode,
1093
+ )
1094
+
1095
+ async def concatenate_image_tables_async(
1096
+ self,
1097
+ name: str,
1098
+ acquisition: int | None = None,
1099
+ index_key: str | None = None,
1100
+ strict: bool = True,
1101
+ mode: Literal["eager", "lazy"] = "eager",
1102
+ ) -> Table:
1103
+ """Concatenate tables from all images in the plate asynchronously.
1104
+
1105
+ Args:
1106
+ name: The name of the table to concatenate.
1107
+ index_key: The key to use for the index of the concatenated table.
1108
+ acquisition: The acquisition id to filter the images.
1109
+ index_key: If a string is provided, a new index column will be created
1110
+ new_index_pattern = {row}_{column}_{path_in_well}_{label}
1111
+ strict: If True, raise an error if the table is not found in the image.
1112
+ mode: The mode to use for concatenation. Can be 'eager' or 'lazy'.
1113
+ if 'eager', the table will be loaded into memory.
1114
+ if 'lazy', the table will be loaded as a lazy frame.
1115
+ """
1116
+ images = await self.get_images_async(acquisition=acquisition)
1117
+ extras = _build_extras(tuple(images.keys()))
1118
+ return await concatenate_image_tables_async(
1119
+ images=tuple(images.values()),
1120
+ extras=extras,
1121
+ name=name,
1122
+ index_key=index_key,
1123
+ strict=strict,
1124
+ mode=mode,
1125
+ )
1126
+
1127
+ async def concatenate_image_tables_as_async(
1128
+ self,
1129
+ name: str,
1130
+ table_cls: type[TableType],
1131
+ acquisition: int | None = None,
1132
+ index_key: str | None = None,
1133
+ strict: bool = True,
1134
+ mode: Literal["eager", "lazy"] = "eager",
1135
+ ) -> TableType:
1136
+ """Concatenate tables from all images in the plate as a specific type.
1137
+
1138
+ Args:
1139
+ name: The name of the table to concatenate.
1140
+ table_cls: The type of the table to concatenate.
1141
+ index_key: The key to use for the index of the concatenated table.
1142
+ acquisition: The acquisition id to filter the images.
1143
+ index_key: If a string is provided, a new index column will be created
1144
+ new_index_pattern = {row}_{column}_{path_in_well}_{label}
1145
+ strict: If True, raise an error if the table is not found in the image.
1146
+ mode: The mode to use for concatenation. Can be 'eager' or 'lazy'.
1147
+ if 'eager', the table will be loaded into memory.
1148
+ if 'lazy', the table will be loaded as a lazy frame.
1149
+ """
1150
+ images = await self.get_images_async(acquisition=acquisition)
1151
+ extras = _build_extras(tuple(images.keys()))
1152
+ return await concatenate_image_tables_as_async(
1153
+ images=tuple(images.values()),
1154
+ extras=extras,
1155
+ name=name,
1156
+ table_cls=table_cls,
1157
+ index_key=index_key,
1158
+ strict=strict,
1159
+ mode=mode,
1160
+ )
1161
+
1162
+
1163
+ def open_ome_zarr_plate(
1164
+ store: StoreOrGroup,
1165
+ cache: bool = False,
1166
+ mode: AccessModeLiteral = "r+",
1167
+ ) -> OmeZarrPlate:
1168
+ """Open an OME-Zarr plate.
1169
+
1170
+ Args:
1171
+ store (StoreOrGroup): The Zarr store or group that stores the plate.
1172
+ cache (bool): Whether to use a cache for the zarr group metadata.
1173
+ mode (AccessModeLiteral): The
1174
+ access mode for the image. Defaults to "r+".
1175
+ """
1176
+ group_handler = ZarrGroupHandler(store=store, cache=cache, mode=mode)
1177
+ return OmeZarrPlate(group_handler)
1178
+
1179
+
1180
+ def _create_empty_plate_from_meta(
1181
+ store: StoreOrGroup,
1182
+ meta: NgioPlateMeta,
1183
+ overwrite: bool = False,
1184
+ ) -> ZarrGroupHandler:
1185
+ """Create an empty OME-Zarr plate from metadata."""
1186
+ mode = "w" if overwrite else "w-"
1187
+ group_handler = ZarrGroupHandler(store=store, cache=True, mode=mode)
1188
+ update_ngio_plate_meta(group_handler, meta)
1189
+ return group_handler
1190
+
1191
+
1192
+ def create_empty_plate(
1193
+ store: StoreOrGroup,
1194
+ name: str,
1195
+ images: list[ImageInWellPath] | None = None,
1196
+ version: NgffVersions | None = None,
1197
+ ngff_version: NgffVersions = DefaultNgffVersion,
1198
+ cache: bool = False,
1199
+ overwrite: bool = False,
1200
+ ) -> OmeZarrPlate:
1201
+ """Initialize and create an empty OME-Zarr plate.
1202
+
1203
+ Args:
1204
+ store (StoreOrGroup): The Zarr store or group that stores the plate.
1205
+ name (str): The name of the plate.
1206
+ images (list[ImageInWellPath] | None): A list of images to add to the plate.
1207
+ If None, no images are added. Defaults to None.
1208
+ version (NgffVersion | None): Deprecated. Please use 'ngff_version' instead.
1209
+ ngff_version (NgffVersion): The NGFF version to use for the new plate.
1210
+ cache (bool): Whether to use a cache for the zarr group metadata.
1211
+ overwrite (bool): Whether to overwrite the existing plate.
1212
+ """
1213
+ if version is not None:
1214
+ warnings.warn(
1215
+ "The 'version' argument is deprecated, and will be removed in ngio=0.3. "
1216
+ "Please use 'ngff_version' instead.",
1217
+ DeprecationWarning,
1218
+ stacklevel=2,
1219
+ )
1220
+ ngff_version = version
1221
+ plate_meta = NgioPlateMeta.default_init(
1222
+ name=name,
1223
+ ngff_version=ngff_version,
1224
+ )
1225
+ group_handler = _create_empty_plate_from_meta(
1226
+ store=store,
1227
+ meta=plate_meta,
1228
+ overwrite=overwrite,
1229
+ )
1230
+
1231
+ if images is not None:
1232
+ plate = OmeZarrPlate(group_handler)
1233
+ for image in images:
1234
+ plate.add_image(
1235
+ row=image.row,
1236
+ column=image.column,
1237
+ image_path=image.path,
1238
+ acquisition_id=image.acquisition_id,
1239
+ acquisition_name=image.acquisition_name,
1240
+ )
1241
+ return open_ome_zarr_plate(
1242
+ store=store,
1243
+ cache=cache,
1244
+ mode="r+",
1245
+ )
1246
+
1247
+
1248
+ def derive_ome_zarr_plate(
1249
+ ome_zarr_plate: OmeZarrPlate,
1250
+ store: StoreOrGroup,
1251
+ plate_name: str | None = None,
1252
+ version: NgffVersions | None = None,
1253
+ ngff_version: NgffVersions = DefaultNgffVersion,
1254
+ keep_acquisitions: bool = False,
1255
+ cache: bool = False,
1256
+ overwrite: bool = False,
1257
+ ) -> OmeZarrPlate:
1258
+ """Derive a new OME-Zarr plate from an existing one.
1259
+
1260
+ Args:
1261
+ ome_zarr_plate (OmeZarrPlate): The existing OME-Zarr plate.
1262
+ store (StoreOrGroup): The Zarr store or group that stores the plate.
1263
+ plate_name (str | None): The name of the new plate.
1264
+ version (NgffVersion | None): Deprecated. Please use 'ngff_version' instead.
1265
+ ngff_version (NgffVersion): The NGFF version to use for the new plate.
1266
+ keep_acquisitions (bool): Whether to keep the acquisitions in the new plate.
1267
+ cache (bool): Whether to use a cache for the zarr group metadata.
1268
+ overwrite (bool): Whether to overwrite the existing plate.
1269
+ """
1270
+ if version is not None:
1271
+ warnings.warn(
1272
+ "The 'version' argument is deprecated, and will be removed in ngio=0.3. "
1273
+ "Please use 'ngff_version' instead.",
1274
+ DeprecationWarning,
1275
+ stacklevel=2,
1276
+ )
1277
+ ngff_version = version
1278
+
1279
+ if plate_name is None:
1280
+ plate_name = ome_zarr_plate.meta.plate.name
1281
+
1282
+ new_meta = ome_zarr_plate.meta.derive(
1283
+ name=plate_name,
1284
+ ngff_version=ngff_version,
1285
+ keep_acquisitions=keep_acquisitions,
1286
+ )
1287
+ _ = _create_empty_plate_from_meta(
1288
+ store=store,
1289
+ meta=new_meta,
1290
+ overwrite=overwrite,
1291
+ )
1292
+ return open_ome_zarr_plate(
1293
+ store=store,
1294
+ cache=cache,
1295
+ mode="r+",
1296
+ )
1297
+
1298
+
1299
+ def open_ome_zarr_well(
1300
+ store: StoreOrGroup,
1301
+ cache: bool = False,
1302
+ mode: AccessModeLiteral = "r+",
1303
+ ) -> OmeZarrWell:
1304
+ """Open an OME-Zarr well.
1305
+
1306
+ Args:
1307
+ store (StoreOrGroup): The Zarr store or group that stores the plate.
1308
+ cache (bool): Whether to use a cache for the zarr group metadata.
1309
+ mode (AccessModeLiteral): The access mode for the image. Defaults to "r+".
1310
+ """
1311
+ group_handler = ZarrGroupHandler(
1312
+ store=store,
1313
+ cache=cache,
1314
+ mode=mode,
1315
+ )
1316
+ return OmeZarrWell(group_handler)
1317
+
1318
+
1319
+ def create_empty_well(
1320
+ store: StoreOrGroup,
1321
+ version: NgffVersions | None = None,
1322
+ ngff_version: NgffVersions = DefaultNgffVersion,
1323
+ cache: bool = False,
1324
+ overwrite: bool = False,
1325
+ ) -> OmeZarrWell:
1326
+ """Create an empty OME-Zarr well.
1327
+
1328
+ Args:
1329
+ store (StoreOrGroup): The Zarr store or group that stores the well.
1330
+ version (NgffVersion | None): Deprecated. Please use 'ngff_version' instead.
1331
+ ngff_version (NgffVersion): The version of the new well.
1332
+ cache (bool): Whether to use a cache for the zarr group metadata.
1333
+ overwrite (bool): Whether to overwrite the existing well.
1334
+ """
1335
+ if version is not None:
1336
+ warnings.warn(
1337
+ "The 'version' argument is deprecated, and will be removed in ngio=0.3. "
1338
+ "Please use 'ngff_version' instead.",
1339
+ DeprecationWarning,
1340
+ stacklevel=2,
1341
+ )
1342
+ ngff_version = version
1343
+ group_handler = ZarrGroupHandler(
1344
+ store=store, cache=True, mode="w" if overwrite else "w-"
1345
+ )
1346
+ update_ngio_well_meta(
1347
+ group_handler, NgioWellMeta.default_init(ngff_version=ngff_version)
1348
+ )
1349
+
1350
+ return open_ome_zarr_well(
1351
+ store=store,
1352
+ cache=cache,
1353
+ mode="r+",
1354
+ )