ngio 0.5.0a1__py3-none-any.whl → 0.5.0a2__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.
ngio/common/_pyramid.py CHANGED
@@ -41,9 +41,19 @@ def _on_disk_dask_zoom(
41
41
  source_array = da.from_zarr(source)
42
42
  target_array = dask_zoom(source_array, target_shape=target.shape, order=order)
43
43
 
44
- target_array = target_array.rechunk(target.chunks) # type: ignore
45
- target_array.compute_chunk_sizes()
44
+ # This is a potential fix for Dask 2025.11
45
+ # import dask.config
46
+ # chunk_size_bytes = np.prod(target.chunks) * target_array.dtype.itemsize
47
+ # current_chunk_size = dask.config.get("array.chunk-size")
48
+ # Increase the chunk size to avoid dask potentially creating
49
+ # corrupted chunks when writing chunks that are not multiple of the
50
+ # target chunk size
51
+ # dask.config.set({"array.chunk-size": f"{chunk_size_bytes}B"})
52
+ target_array = target_array.rechunk(target.chunks)
53
+ target_array = target_array.compute_chunk_sizes()
46
54
  target_array.to_zarr(target)
55
+ # Restore previous chunk size
56
+ # dask.config.set({"array.chunk-size": current_chunk_size})
47
57
 
48
58
 
49
59
  def _on_disk_coarsen(
ngio/hcs/_plate.py CHANGED
@@ -40,6 +40,7 @@ from ngio.tables import (
40
40
  )
41
41
  from ngio.utils import (
42
42
  AccessModeLiteral,
43
+ NgioCache,
43
44
  NgioValueError,
44
45
  StoreOrGroup,
45
46
  ZarrGroupHandler,
@@ -239,6 +240,12 @@ class OmeZarrPlate:
239
240
  self._group_handler = group_handler
240
241
  self._meta_handler = find_plate_meta_handler(group_handler)
241
242
  self._tables_container = table_container
243
+ self._wells_cache: NgioCache[OmeZarrWell] = NgioCache(
244
+ use_cache=self._group_handler.use_cache
245
+ )
246
+ self._images_cache: NgioCache[OmeZarrContainer] = NgioCache(
247
+ use_cache=self._group_handler.use_cache
248
+ )
242
249
 
243
250
  def __repr__(self) -> str:
244
251
  """Return a string representation of the plate."""
@@ -356,6 +363,24 @@ class OmeZarrPlate:
356
363
  well = self.get_well(row=row, column=column)
357
364
  return well.get_image_acquisition_id(image_path=image_path)
358
365
 
366
+ def _get_well(self, well_path: str) -> OmeZarrWell:
367
+ """Get a well from the plate by its path.
368
+
369
+ Args:
370
+ well_path (str): The path of the well.
371
+
372
+ Returns:
373
+ OmeZarrWell: The well.
374
+
375
+ """
376
+ cached_well = self._wells_cache.get(well_path)
377
+ if cached_well is not None:
378
+ return cached_well
379
+
380
+ group_handler = self._group_handler.derive_handler(well_path)
381
+ self._wells_cache.set(well_path, OmeZarrWell(group_handler))
382
+ return OmeZarrWell(group_handler)
383
+
359
384
  def get_well(self, row: str, column: int | str) -> OmeZarrWell:
360
385
  """Get a well from the plate.
361
386
 
@@ -367,8 +392,7 @@ class OmeZarrPlate:
367
392
  OmeZarrWell: The well.
368
393
  """
369
394
  well_path = self._well_path(row=row, column=column)
370
- group_handler = self._group_handler.derive_handler(well_path)
371
- return OmeZarrWell(group_handler)
395
+ return self._get_well(well_path=well_path)
372
396
 
373
397
  async def get_wells_async(self) -> dict[str, OmeZarrWell]:
374
398
  """Get all wells in the plate asynchronously.
@@ -380,26 +404,17 @@ class OmeZarrPlate:
380
404
  dict[str, OmeZarrWell]: A dictionary of wells, where the key is the well
381
405
  path and the value is the well object.
382
406
  """
383
- wells = self._group_handler.get_from_cache("wells")
384
- if wells is not None:
385
- assert isinstance(wells, dict)
386
- return wells
387
-
388
- def process_well(well_path):
389
- group_handler = self._group_handler.derive_handler(well_path)
390
- well = OmeZarrWell(group_handler)
391
- return well_path, well
392
-
393
407
  wells, tasks = {}, []
394
408
  for well_path in self.wells_paths():
395
- task = asyncio.to_thread(process_well, well_path)
409
+ task = asyncio.to_thread(
410
+ lambda well_path: (well_path, self._get_well(well_path)), well_path
411
+ )
396
412
  tasks.append(task)
397
413
 
398
414
  results = await asyncio.gather(*tasks)
399
415
  for well_path, well in results:
400
416
  wells[well_path] = well
401
417
 
402
- self._group_handler.add_to_cache("wells", wells)
403
418
  return wells
404
419
 
405
420
  def get_wells(self) -> dict[str, OmeZarrWell]:
@@ -409,24 +424,25 @@ class OmeZarrPlate:
409
424
  dict[str, OmeZarrWell]: A dictionary of wells, where the key is the well
410
425
  path and the value is the well object.
411
426
  """
412
- wells = self._group_handler.get_from_cache("wells")
413
- if wells is not None:
414
- assert isinstance(wells, dict)
415
- return wells
416
-
417
- def process_well(well_path):
418
- group_handler = self._group_handler.derive_handler(well_path)
419
- well = OmeZarrWell(group_handler)
420
- return well_path, well
421
-
422
427
  wells = {}
423
428
  for well_path in self.wells_paths():
424
- _, well = process_well(well_path)
425
- wells[well_path] = well
426
-
427
- self._group_handler.add_to_cache("wells", wells)
429
+ wells[well_path] = self._get_well(well_path)
428
430
  return wells
429
431
 
432
+ def _get_image(self, image_path: str) -> OmeZarrContainer:
433
+ """Get an image from the plate by its path.
434
+
435
+ Args:
436
+ image_path (str): The path of the image.
437
+ """
438
+ cached_image = self._images_cache.get(image_path)
439
+ if cached_image is not None:
440
+ return cached_image
441
+ img_group_handler = self._group_handler.derive_handler(image_path)
442
+ image = OmeZarrContainer(img_group_handler)
443
+ self._images_cache.set(image_path, image)
444
+ return image
445
+
430
446
  async def get_images_async(
431
447
  self, acquisition: int | None = None
432
448
  ) -> dict[str, OmeZarrContainer]:
@@ -442,30 +458,19 @@ class OmeZarrPlate:
442
458
  dict[str, OmeZarrContainer]: A dictionary of images, where the key is the
443
459
  image path and the value is the image object.
444
460
  """
445
- images = self._group_handler.get_from_cache("images")
446
- if images is not None:
447
- assert isinstance(images, dict)
448
- return images
449
-
450
461
  paths = await self.images_paths_async(acquisition=acquisition)
451
462
 
452
- def process_image(image_path):
453
- """Process a single image and return the image path and image object."""
454
- img_group_handler = self._group_handler.derive_handler(image_path)
455
- image = OmeZarrContainer(img_group_handler)
456
- return image_path, image
457
-
458
463
  images, tasks = {}, []
459
464
  for image_path in paths:
460
- task = asyncio.to_thread(process_image, image_path)
465
+ task = asyncio.to_thread(
466
+ lambda image_path: (image_path, self._get_image(image_path)), image_path
467
+ )
461
468
  tasks.append(task)
462
469
 
463
470
  results = await asyncio.gather(*tasks)
464
471
 
465
472
  for image_path, image in results:
466
473
  images[image_path] = image
467
-
468
- self._group_handler.add_to_cache("images", images)
469
474
  return images
470
475
 
471
476
  def get_images(self, acquisition: int | None = None) -> dict[str, OmeZarrContainer]:
@@ -474,24 +479,11 @@ class OmeZarrPlate:
474
479
  Args:
475
480
  acquisition: The acquisition id to filter the images.
476
481
  """
477
- images = self._group_handler.get_from_cache("images")
478
- if images is not None:
479
- assert isinstance(images, dict)
480
- return images
481
482
  paths = self.images_paths(acquisition=acquisition)
482
-
483
- def process_image(image_path):
484
- """Process a single image and return the image path and image object."""
485
- img_group_handler = self._group_handler.derive_handler(image_path)
486
- image = OmeZarrContainer(img_group_handler)
487
- return image_path, image
488
-
489
483
  images = {}
490
484
  for image_path in paths:
491
- _, image = process_image(image_path)
492
- images[image_path] = image
485
+ images[image_path] = self._get_image(image_path)
493
486
 
494
- self._group_handler.add_to_cache("images", images)
495
487
  return images
496
488
 
497
489
  def get_image(
@@ -508,8 +500,7 @@ class OmeZarrPlate:
508
500
  OmeZarrContainer: The image.
509
501
  """
510
502
  image_path = self._image_path(row=row, column=column, path=image_path)
511
- group_handler = self._group_handler.derive_handler(image_path)
512
- return OmeZarrContainer(group_handler)
503
+ return self._get_image(image_path)
513
504
 
514
505
  def get_image_store(
515
506
  self, row: str, column: int | str, image_path: str
@@ -785,7 +776,6 @@ class OmeZarrPlate:
785
776
  keep_acquisitions: bool = False,
786
777
  cache: bool = False,
787
778
  overwrite: bool = False,
788
- parallel_safe: bool = True,
789
779
  ) -> "OmeZarrPlate":
790
780
  """Derive a new OME-Zarr plate from an existing one.
791
781
 
@@ -796,7 +786,6 @@ class OmeZarrPlate:
796
786
  keep_acquisitions (bool): Whether to keep the acquisitions in the new plate.
797
787
  cache (bool): Whether to use a cache for the zarr group metadata.
798
788
  overwrite (bool): Whether to overwrite the existing plate.
799
- parallel_safe (bool): Whether the group handler is parallel safe.
800
789
  """
801
790
  return derive_ome_zarr_plate(
802
791
  ome_zarr_plate=self,
@@ -806,7 +795,6 @@ class OmeZarrPlate:
806
795
  keep_acquisitions=keep_acquisitions,
807
796
  cache=cache,
808
797
  overwrite=overwrite,
809
- parallel_safe=parallel_safe,
810
798
  )
811
799
 
812
800
  def _get_tables_container(self) -> TablesContainer | None:
@@ -1143,7 +1131,6 @@ def open_ome_zarr_plate(
1143
1131
  store: StoreOrGroup,
1144
1132
  cache: bool = False,
1145
1133
  mode: AccessModeLiteral = "r+",
1146
- parallel_safe: bool = True,
1147
1134
  ) -> OmeZarrPlate:
1148
1135
  """Open an OME-Zarr plate.
1149
1136
 
@@ -1152,11 +1139,8 @@ def open_ome_zarr_plate(
1152
1139
  cache (bool): Whether to use a cache for the zarr group metadata.
1153
1140
  mode (AccessModeLiteral): The
1154
1141
  access mode for the image. Defaults to "r+".
1155
- parallel_safe (bool): Whether the group handler is parallel safe.
1156
1142
  """
1157
- group_handler = ZarrGroupHandler(
1158
- store=store, cache=cache, mode=mode, parallel_safe=parallel_safe
1159
- )
1143
+ group_handler = ZarrGroupHandler(store=store, cache=cache, mode=mode)
1160
1144
  return OmeZarrPlate(group_handler)
1161
1145
 
1162
1146
 
@@ -1168,9 +1152,7 @@ def _create_empty_plate_from_meta(
1168
1152
  ) -> ZarrGroupHandler:
1169
1153
  """Create an empty OME-Zarr plate from metadata."""
1170
1154
  mode = "w" if overwrite else "w-"
1171
- group_handler = ZarrGroupHandler(
1172
- store=store, cache=True, mode=mode, parallel_safe=False
1173
- )
1155
+ group_handler = ZarrGroupHandler(store=store, cache=True, mode=mode)
1174
1156
  meta_handler = get_plate_meta_handler(group_handler, version=version)
1175
1157
  meta_handler.write_meta(meta)
1176
1158
  return group_handler
@@ -1183,9 +1165,18 @@ def create_empty_plate(
1183
1165
  version: NgffVersions = "0.4",
1184
1166
  cache: bool = False,
1185
1167
  overwrite: bool = False,
1186
- parallel_safe: bool = True,
1187
1168
  ) -> OmeZarrPlate:
1188
- """Initialize and create an empty OME-Zarr plate."""
1169
+ """Initialize and create an empty OME-Zarr plate.
1170
+
1171
+ Args:
1172
+ store (StoreOrGroup): The Zarr store or group that stores the plate.
1173
+ name (str): The name of the plate.
1174
+ images (list[ImageInWellPath] | None): A list of images to add to the plate.
1175
+ If None, no images are added. Defaults to None.
1176
+ version (NgffVersion): The version of the new plate.
1177
+ cache (bool): Whether to use a cache for the zarr group metadata.
1178
+ overwrite (bool): Whether to overwrite the existing plate.
1179
+ """
1189
1180
  plate_meta = NgioPlateMeta.default_init(
1190
1181
  name=name,
1191
1182
  version=version,
@@ -1211,7 +1202,6 @@ def create_empty_plate(
1211
1202
  store=store,
1212
1203
  cache=cache,
1213
1204
  mode="r+",
1214
- parallel_safe=parallel_safe,
1215
1205
  )
1216
1206
 
1217
1207
 
@@ -1223,7 +1213,6 @@ def derive_ome_zarr_plate(
1223
1213
  keep_acquisitions: bool = False,
1224
1214
  cache: bool = False,
1225
1215
  overwrite: bool = False,
1226
- parallel_safe: bool = True,
1227
1216
  ) -> OmeZarrPlate:
1228
1217
  """Derive a new OME-Zarr plate from an existing one.
1229
1218
 
@@ -1235,7 +1224,6 @@ def derive_ome_zarr_plate(
1235
1224
  keep_acquisitions (bool): Whether to keep the acquisitions in the new plate.
1236
1225
  cache (bool): Whether to use a cache for the zarr group metadata.
1237
1226
  overwrite (bool): Whether to overwrite the existing plate.
1238
- parallel_safe (bool): Whether the group handler is parallel safe.
1239
1227
  """
1240
1228
  if plate_name is None:
1241
1229
  plate_name = ome_zarr_plate.meta.plate.name
@@ -1255,7 +1243,6 @@ def derive_ome_zarr_plate(
1255
1243
  store=store,
1256
1244
  cache=cache,
1257
1245
  mode="r+",
1258
- parallel_safe=parallel_safe,
1259
1246
  )
1260
1247
 
1261
1248
 
@@ -1263,7 +1250,6 @@ def open_ome_zarr_well(
1263
1250
  store: StoreOrGroup,
1264
1251
  cache: bool = False,
1265
1252
  mode: AccessModeLiteral = "r+",
1266
- parallel_safe: bool = True,
1267
1253
  ) -> OmeZarrWell:
1268
1254
  """Open an OME-Zarr well.
1269
1255
 
@@ -1271,10 +1257,11 @@ def open_ome_zarr_well(
1271
1257
  store (StoreOrGroup): The Zarr store or group that stores the plate.
1272
1258
  cache (bool): Whether to use a cache for the zarr group metadata.
1273
1259
  mode (AccessModeLiteral): The access mode for the image. Defaults to "r+".
1274
- parallel_safe (bool): Whether the group handler is parallel safe.
1275
1260
  """
1276
1261
  group_handler = ZarrGroupHandler(
1277
- store=store, cache=cache, mode=mode, parallel_safe=parallel_safe
1262
+ store=store,
1263
+ cache=cache,
1264
+ mode=mode,
1278
1265
  )
1279
1266
  return OmeZarrWell(group_handler)
1280
1267
 
@@ -1284,7 +1271,6 @@ def create_empty_well(
1284
1271
  version: NgffVersions = "0.4",
1285
1272
  cache: bool = False,
1286
1273
  overwrite: bool = False,
1287
- parallel_safe: bool = True,
1288
1274
  ) -> OmeZarrWell:
1289
1275
  """Create an empty OME-Zarr well.
1290
1276
 
@@ -1293,10 +1279,9 @@ def create_empty_well(
1293
1279
  version (NgffVersion): The version of the new well.
1294
1280
  cache (bool): Whether to use a cache for the zarr group metadata.
1295
1281
  overwrite (bool): Whether to overwrite the existing well.
1296
- parallel_safe (bool): Whether the group handler is parallel safe.
1297
1282
  """
1298
1283
  group_handler = ZarrGroupHandler(
1299
- store=store, cache=True, mode="w" if overwrite else "w-", parallel_safe=False
1284
+ store=store, cache=True, mode="w" if overwrite else "w-"
1300
1285
  )
1301
1286
  meta_handler = get_well_meta_handler(group_handler, version=version)
1302
1287
  meta = NgioWellMeta.default_init()
@@ -1306,5 +1291,4 @@ def create_empty_well(
1306
1291
  store=store,
1307
1292
  cache=cache,
1308
1293
  mode="r+",
1309
- parallel_safe=parallel_safe,
1310
1294
  )
ngio/images/_create.py CHANGED
@@ -40,7 +40,7 @@ def _init_generic_meta(
40
40
  space_unit: SpaceUnits | str | None = DefaultSpaceUnit,
41
41
  time_unit: TimeUnits | str | None = DefaultTimeUnit,
42
42
  name: str | None = None,
43
- version: NgffVersions = DefaultNgffVersion,
43
+ ngff_version: NgffVersions = DefaultNgffVersion,
44
44
  ) -> tuple[_image_or_label_meta, list[float]]:
45
45
  """Initialize the metadata for an image or label."""
46
46
  scaling_factors = []
@@ -75,7 +75,7 @@ def _init_generic_meta(
75
75
  axes_names=axes_names,
76
76
  pixel_size=pixel_sizes,
77
77
  scaling_factors=scaling_factors,
78
- version=version,
78
+ version=ngff_version,
79
79
  )
80
80
  return meta, scaling_factors
81
81
 
@@ -98,7 +98,7 @@ def create_empty_label_container(
98
98
  dimension_separator: Literal[".", "/"] = "/",
99
99
  compressors: CompressorLike = "auto",
100
100
  overwrite: bool = False,
101
- version: NgffVersions = DefaultNgffVersion,
101
+ ngff_version: NgffVersions = DefaultNgffVersion,
102
102
  ) -> ZarrGroupHandler:
103
103
  """Create an empty label with the given shape and metadata.
104
104
 
@@ -130,7 +130,7 @@ def create_empty_label_container(
130
130
  dtype (str, optional): The data type of the image. Defaults to "uint16".
131
131
  overwrite (bool, optional): Whether to overwrite an existing image.
132
132
  Defaults to True.
133
- version (str, optional): The version of the OME-Zarr specification.
133
+ ngff_version (str, optional): The version of the OME-Zarr specification.
134
134
  Defaults to DefaultVersion.
135
135
 
136
136
  """
@@ -155,14 +155,16 @@ def create_empty_label_container(
155
155
  time_unit=time_unit,
156
156
  axes_names=axes_names,
157
157
  name=name,
158
- version=version,
158
+ ngff_version=ngff_version,
159
159
  )
160
160
 
161
161
  mode = "w" if overwrite else "w-"
162
162
  group_handler = ZarrGroupHandler(
163
163
  store=store, mode=mode, cache=False, zarr_format=meta.zarr_format
164
164
  )
165
- image_handler = get_label_meta_handler(version=version, group_handler=group_handler)
165
+ image_handler = get_label_meta_handler(
166
+ version=ngff_version, group_handler=group_handler
167
+ )
166
168
  image_handler.write_meta(meta)
167
169
 
168
170
  init_empty_pyramid(
@@ -199,7 +201,7 @@ def create_empty_image_container(
199
201
  dimension_separator: Literal[".", "/"] = "/",
200
202
  compressors: CompressorLike = "auto",
201
203
  overwrite: bool = False,
202
- version: NgffVersions = DefaultNgffVersion,
204
+ ngff_version: NgffVersions = DefaultNgffVersion,
203
205
  ) -> ZarrGroupHandler:
204
206
  """Create an empty OME-Zarr image with the given shape and metadata.
205
207
 
@@ -231,7 +233,7 @@ def create_empty_image_container(
231
233
  compressors (CompressorLike): The compressors to use. Defaults to "auto".
232
234
  overwrite (bool, optional): Whether to overwrite an existing image.
233
235
  Defaults to True.
234
- version (str, optional): The version of the OME-Zarr specification.
236
+ ngff_version (str, optional): The version of the OME-Zarr specification.
235
237
  Defaults to DefaultVersion.
236
238
 
237
239
  """
@@ -256,13 +258,15 @@ def create_empty_image_container(
256
258
  time_unit=time_unit,
257
259
  axes_names=axes_names,
258
260
  name=name,
259
- version=version,
261
+ ngff_version=ngff_version,
260
262
  )
261
263
  mode = "w" if overwrite else "w-"
262
264
  group_handler = ZarrGroupHandler(
263
265
  store=store, mode=mode, cache=False, zarr_format=meta.zarr_format
264
266
  )
265
- image_handler = get_image_meta_handler(version=version, group_handler=group_handler)
267
+ image_handler = get_image_meta_handler(
268
+ version=ngff_version, group_handler=group_handler
269
+ )
266
270
  image_handler.write_meta(meta)
267
271
 
268
272
  init_empty_pyramid(
@@ -40,7 +40,7 @@ def create_synthetic_ome_zarr(
40
40
  dimension_separator: Literal[".", "/"] = "/",
41
41
  compressors: CompressorLike = "auto",
42
42
  overwrite: bool = False,
43
- version: NgffVersions = DefaultNgffVersion,
43
+ ngff_version: NgffVersions = DefaultNgffVersion,
44
44
  ) -> OmeZarrContainer:
45
45
  """Create an empty OME-Zarr image with the given shape and metadata.
46
46
 
@@ -72,7 +72,7 @@ def create_synthetic_ome_zarr(
72
72
  compressors (CompressorLike): The compressors to use. Defaults to "auto".
73
73
  overwrite (bool, optional): Whether to overwrite an existing image.
74
74
  Defaults to True.
75
- version (NgffVersion, optional): The version of the OME-Zarr specification.
75
+ ngff_version (NgffVersion, optional): The version of the OME-Zarr specification.
76
76
  Defaults to DefaultNgffVersion.
77
77
  """
78
78
  if isinstance(reference_sample, str):
@@ -105,7 +105,7 @@ def create_synthetic_ome_zarr(
105
105
  overwrite=overwrite,
106
106
  dimension_separator=dimension_separator,
107
107
  compressors=compressors,
108
- version=version,
108
+ ngff_version=ngff_version,
109
109
  )
110
110
 
111
111
  image = ome_zarr.get_image()
ngio/images/_image.py CHANGED
@@ -823,7 +823,7 @@ def derive_image_container(
823
823
  dimension_separator=dimension_separator,
824
824
  compressors=compressors,
825
825
  overwrite=overwrite,
826
- version=ngff_version,
826
+ ngff_version=ngff_version,
827
827
  )
828
828
  image_container = ImagesContainer(handler)
829
829
 
@@ -885,7 +885,7 @@ def _parse_str_or_model(
885
885
  )
886
886
  elif channel_selection.mode == "wavelength_id":
887
887
  return image.get_channel_idx(
888
- channel_label=str(channel_selection.identifier)
888
+ wavelength_id=str(channel_selection.identifier)
889
889
  )
890
890
  elif channel_selection.mode == "index":
891
891
  return int(channel_selection.identifier)
ngio/images/_label.py CHANGED
@@ -329,7 +329,7 @@ def derive_label(
329
329
  dimension_separator=dimension_separator,
330
330
  compressors=compressors,
331
331
  overwrite=overwrite,
332
- version=ref_meta.version,
332
+ ngff_version=ref_meta.version,
333
333
  name=name,
334
334
  )
335
335
  return None
@@ -845,7 +845,7 @@ def create_empty_ome_zarr(
845
845
  channel_colors: Sequence[str] | None = None,
846
846
  channel_active: Sequence[bool] | None = None,
847
847
  overwrite: bool = False,
848
- version: NgffVersions = DefaultNgffVersion,
848
+ ngff_version: NgffVersions = DefaultNgffVersion,
849
849
  ) -> OmeZarrContainer:
850
850
  """Create an empty OME-Zarr image with the given shape and metadata.
851
851
 
@@ -885,7 +885,7 @@ def create_empty_ome_zarr(
885
885
  active. Defaults to None.
886
886
  overwrite (bool, optional): Whether to overwrite an existing image.
887
887
  Defaults to True.
888
- version (NgffVersion, optional): The version of the OME-Zarr specification.
888
+ ngff_version (NgffVersion, optional): The version of the OME-Zarr specification.
889
889
  Defaults to DefaultNgffVersion.
890
890
  """
891
891
  handler = create_empty_image_container(
@@ -906,7 +906,7 @@ def create_empty_ome_zarr(
906
906
  dimension_separator=dimension_separator,
907
907
  compressors=compressors,
908
908
  overwrite=overwrite,
909
- version=version,
909
+ ngff_version=ngff_version,
910
910
  )
911
911
 
912
912
  ome_zarr = OmeZarrContainer(group_handler=handler)
@@ -942,7 +942,7 @@ def create_ome_zarr_from_array(
942
942
  dimension_separator: Literal[".", "/"] = "/",
943
943
  compressors: CompressorLike = "auto",
944
944
  overwrite: bool = False,
945
- version: NgffVersions = DefaultNgffVersion,
945
+ ngff_version: NgffVersions = DefaultNgffVersion,
946
946
  ) -> OmeZarrContainer:
947
947
  """Create an OME-Zarr image from a numpy array.
948
948
 
@@ -983,7 +983,7 @@ def create_ome_zarr_from_array(
983
983
  compressors (CompressorLike): The compressors to use. Defaults to "auto".
984
984
  overwrite (bool, optional): Whether to overwrite an existing image.
985
985
  Defaults to True.
986
- version (str, optional): The version of the OME-Zarr specification.
986
+ ngff_version (str, optional): The version of the OME-Zarr specification.
987
987
  Defaults to DefaultNgffVersion.
988
988
  """
989
989
  handler = create_empty_image_container(
@@ -1004,7 +1004,7 @@ def create_ome_zarr_from_array(
1004
1004
  overwrite=overwrite,
1005
1005
  dimension_separator=dimension_separator,
1006
1006
  compressors=compressors,
1007
- version=version,
1007
+ ngff_version=ngff_version,
1008
1008
  )
1009
1009
 
1010
1010
  ome_zarr = OmeZarrContainer(group_handler=handler)
@@ -359,13 +359,10 @@ ImplementedTables().add_implementation(ConditionTableV1)
359
359
  def open_tables_container(
360
360
  store: StoreOrGroup,
361
361
  cache: bool = False,
362
- mode: AccessModeLiteral = "a",
363
- parallel_safe: bool = False,
362
+ mode: AccessModeLiteral = "r+",
364
363
  ) -> TablesContainer:
365
364
  """Open a table handler from a Zarr store."""
366
- handler = ZarrGroupHandler(
367
- store=store, cache=cache, mode=mode, parallel_safe=parallel_safe
368
- )
365
+ handler = ZarrGroupHandler(store=store, cache=cache, mode=mode)
369
366
  return TablesContainer(handler)
370
367
 
371
368
 
@@ -373,12 +370,13 @@ def open_table(
373
370
  store: StoreOrGroup,
374
371
  backend: TableBackend | None = None,
375
372
  cache: bool = False,
376
- mode: AccessModeLiteral = "a",
377
- parallel_safe: bool = False,
373
+ mode: AccessModeLiteral = "r+",
378
374
  ) -> Table:
379
375
  """Open a table from a Zarr store."""
380
376
  handler = ZarrGroupHandler(
381
- store=store, cache=cache, mode=mode, parallel_safe=parallel_safe
377
+ store=store,
378
+ cache=cache,
379
+ mode=mode,
382
380
  )
383
381
  meta = _get_meta(handler)
384
382
  return ImplementedTables().get_table(
@@ -391,12 +389,13 @@ def open_table_as(
391
389
  table_cls: type[TableType],
392
390
  backend: TableBackend | None = None,
393
391
  cache: bool = False,
394
- mode: AccessModeLiteral = "a",
395
- parallel_safe: bool = False,
392
+ mode: AccessModeLiteral = "r+",
396
393
  ) -> TableType:
397
394
  """Open a table from a Zarr store as a specific type."""
398
395
  handler = ZarrGroupHandler(
399
- store=store, cache=cache, mode=mode, parallel_safe=parallel_safe
396
+ store=store,
397
+ cache=cache,
398
+ mode=mode,
400
399
  )
401
400
  return table_cls.from_handler(
402
401
  handler=handler,
@@ -410,12 +409,20 @@ def write_table(
410
409
  backend: TableBackend = DefaultTableBackend,
411
410
  cache: bool = False,
412
411
  mode: AccessModeLiteral = "a",
413
- parallel_safe: bool = False,
414
412
  ) -> None:
415
- """Write a table to a Zarr store."""
416
- handler = ZarrGroupHandler(
417
- store=store, cache=cache, mode=mode, parallel_safe=parallel_safe
418
- )
413
+ """Write a table to a Zarr store.
414
+
415
+ A table will be created at the given store location.
416
+
417
+ Args:
418
+ store (StoreOrGroup): The Zarr store or group to write the table to.
419
+ table (Table): The table to write.
420
+ backend (TableBackend): The backend to use for writing the table.
421
+ cache (bool): Whether to use caching for the Zarr group handler.
422
+ mode (AccessModeLiteral): The access mode to use for the Zarr group handler.
423
+
424
+ """
425
+ handler = ZarrGroupHandler(store=store, cache=cache, mode=mode)
419
426
  table.set_backend(
420
427
  handler=handler,
421
428
  backend=backend,
@@ -86,10 +86,10 @@ def _dataframe_to_rois(
86
86
  ) -> dict[str, Roi]:
87
87
  """Convert a DataFrame to a WorldCooROI object."""
88
88
  # Validate the columns of the DataFrame
89
- _required_columns = set(dataframe.columns).intersection(set(required_columns))
90
- if len(_required_columns) != len(required_columns):
89
+ _missing_columns = set(required_columns).difference(set(dataframe.columns))
90
+ if len(_missing_columns) != 0:
91
91
  raise NgioTableValidationError(
92
- f"Could not find required columns: {_required_columns} in the table."
92
+ f"Could not find required columns: {_missing_columns} in the table."
93
93
  )
94
94
 
95
95
  extra_columns = set(dataframe.columns).difference(
ngio/utils/__init__.py CHANGED
@@ -18,6 +18,7 @@ from ngio.utils._fractal_fsspec_store import fractal_fsspec_store
18
18
  from ngio.utils._logger import ngio_logger, ngio_warn, set_logger_level
19
19
  from ngio.utils._zarr_utils import (
20
20
  AccessModeLiteral,
21
+ NgioCache,
21
22
  StoreOrGroup,
22
23
  ZarrGroupHandler,
23
24
  open_group_wrapper,
@@ -28,6 +29,7 @@ set_logger_level(os.getenv("NGIO_LOGGER_LEVEL", "WARNING"))
28
29
  __all__ = [
29
30
  # Zarr
30
31
  "AccessModeLiteral",
32
+ "NgioCache",
31
33
  # Errors
32
34
  "NgioFileExistsError",
33
35
  "NgioFileNotFoundError",
ngio/utils/_cache.py ADDED
@@ -0,0 +1,48 @@
1
+ from typing import Generic, TypeVar
2
+
3
+ T = TypeVar("T")
4
+
5
+
6
+ class NgioCache(Generic[T]):
7
+ """A simple cache for NGIO objects."""
8
+
9
+ def __init__(self, use_cache: bool = True):
10
+ self._cache: dict[str, T] = {}
11
+ self._use_cache = use_cache
12
+
13
+ def _cache_sanity_check(self) -> None:
14
+ if len(self._cache) > 0:
15
+ raise RuntimeError(
16
+ "Cache is disabled, but cache contains items. "
17
+ "This indicates a logic error."
18
+ )
19
+
20
+ @property
21
+ def use_cache(self) -> bool:
22
+ return self._use_cache
23
+
24
+ @property
25
+ def cache(self) -> dict[str, T]:
26
+ return self._cache
27
+
28
+ @property
29
+ def is_empty(self) -> bool:
30
+ return len(self._cache) == 0
31
+
32
+ def get(self, key: str, default: T | None = None) -> T | None:
33
+ if not self._use_cache:
34
+ self._cache_sanity_check()
35
+ return default
36
+ return self._cache.get(key, default)
37
+
38
+ def set(self, key: str, value: T, overwrite: bool = True) -> None:
39
+ if not self._use_cache:
40
+ self._cache_sanity_check()
41
+ return
42
+ self._cache[key] = value
43
+
44
+ def clear(self) -> None:
45
+ if not self._use_cache:
46
+ self._cache_sanity_check()
47
+ return
48
+ self._cache.clear()
ngio/utils/_zarr_utils.py CHANGED
@@ -1,5 +1,6 @@
1
1
  """Common utilities for working with Zarr groups in consistent ways."""
2
2
 
3
+ import warnings
3
4
  from pathlib import Path
4
5
  from typing import Literal
5
6
 
@@ -7,12 +8,16 @@ import fsspec
7
8
  import zarr
8
9
  from filelock import BaseFileLock, FileLock
9
10
  from zarr.abc.store import Store
10
- from zarr.core.array import CompressorLike
11
11
  from zarr.errors import ContainsGroupError
12
12
  from zarr.storage import FsspecStore, LocalStore, MemoryStore
13
13
 
14
- from ngio.utils import NgioFileExistsError, NgioFileNotFoundError, NgioValueError
15
- from ngio.utils._errors import NgioError
14
+ from ngio.utils._cache import NgioCache
15
+ from ngio.utils._errors import (
16
+ NgioError,
17
+ NgioFileExistsError,
18
+ NgioFileNotFoundError,
19
+ NgioValueError,
20
+ )
16
21
 
17
22
  AccessModeLiteral = Literal["r", "r+", "w", "w-", "a"]
18
23
  # StoreLike is more restrictive than it could be
@@ -27,32 +32,33 @@ StoreOrGroup = GenericStore | zarr.Group
27
32
 
28
33
  def _check_store(store) -> NgioSupportedStore:
29
34
  """Check the store and return a valid store."""
30
- if isinstance(store, NgioSupportedStore):
31
- return store
32
-
33
- raise NotImplementedError(
34
- f"Store type {type(store)} is not supported. "
35
- f"Supported types are: {NgioSupportedStore}"
36
- )
35
+ if not isinstance(store, NgioSupportedStore):
36
+ warnings.warn(
37
+ f"Store type {type(store)} is not explicitly supported. "
38
+ f"Supported types are: {NgioSupportedStore}. "
39
+ "Proceeding, but this may lead to unexpected behavior.",
40
+ UserWarning,
41
+ stacklevel=2,
42
+ )
43
+ return store
37
44
 
38
45
 
39
- def _check_group(group: zarr.Group, mode: AccessModeLiteral) -> zarr.Group:
46
+ def _check_group(
47
+ group: zarr.Group, mode: AccessModeLiteral | None = None
48
+ ) -> zarr.Group:
40
49
  """Check the group and return a valid group."""
41
- if group.read_only and mode in ["w", "w-"]:
42
- raise NgioValueError(
43
- "The group is read only. Cannot open in write mode ['w', 'w-']"
44
- )
50
+ if group.read_only and mode not in [None, "r"]:
51
+ raise NgioValueError(f"The group is read only. Cannot open in mode {mode}.")
45
52
 
46
53
  if mode == "r" and not group.read_only:
47
54
  # let's make sure we don't accidentally write to the group
48
55
  group = zarr.open_group(store=group.store, path=group.path, mode="r")
49
-
50
56
  return group
51
57
 
52
58
 
53
59
  def open_group_wrapper(
54
60
  store: StoreOrGroup,
55
- mode: AccessModeLiteral,
61
+ mode: AccessModeLiteral | None = None,
56
62
  zarr_format: Literal[2, 3] | None = None,
57
63
  ) -> zarr.Group:
58
64
  """Wrapper around zarr.open_group with some additional checks.
@@ -72,6 +78,7 @@ def open_group_wrapper(
72
78
 
73
79
  try:
74
80
  _check_store(store)
81
+ mode = mode if mode is not None else "a"
75
82
  group = zarr.open_group(store=store, mode=mode, zarr_format=zarr_format)
76
83
 
77
84
  except FileExistsError as e:
@@ -98,68 +105,32 @@ class ZarrGroupHandler:
98
105
  store: StoreOrGroup,
99
106
  zarr_format: Literal[2, 3] | None = None,
100
107
  cache: bool = False,
101
- mode: AccessModeLiteral = "a",
102
- parallel_safe: bool = False,
103
- parent: "ZarrGroupHandler | None" = None,
108
+ mode: AccessModeLiteral | None = None,
104
109
  ):
105
110
  """Initialize the handler.
106
111
 
107
112
  Args:
108
113
  store (StoreOrGroup): The Zarr store or group containing the image data.
109
114
  meta_mode (str): The mode of the metadata handler.
110
- zarr_format (int): The Zarr format version to use.
115
+ zarr_format (int | None): The Zarr format version to use.
111
116
  cache (bool): Whether to cache the metadata.
112
- mode (str): The mode of the store.
113
- parallel_safe (bool): If True, the handler will create a lock file to make
114
- that can be used to make the handler parallel safe.
115
- Be aware that the lock needs to be used manually.
116
- parent (ZarrGroupHandler | None): The parent handler.
117
+ mode (str | None): The mode of the store.
117
118
  """
118
- if mode not in ["r", "r+", "w", "w-", "a"]:
119
+ if mode not in ["r", "r+", "w", "w-", "a", None]:
119
120
  raise NgioValueError(f"Mode {mode} is not supported.")
120
121
 
121
- if parallel_safe and cache:
122
- raise NgioValueError(
123
- "The cache and parallel_safe options are mutually exclusive."
124
- "If you want to use the lock mechanism, you should not use the cache."
125
- )
126
-
127
122
  group = open_group_wrapper(store=store, mode=mode, zarr_format=zarr_format)
128
- _store = group.store
129
-
130
- # Make sure the cache is set in the attrs
131
- # in the same way as the cache in the handler
132
-
133
- ## TODO
134
- # Figure out how to handle the cache in the new zarr version
135
- # group.attrs.cache = cache
136
-
137
- if parallel_safe:
138
- if not isinstance(_store, LocalStore):
139
- raise NgioValueError(
140
- "The store needs to be a LocalStore to use the lock mechanism. "
141
- f"Instead, got {_store.__class__.__name__}."
142
- )
143
-
144
- store_path = _store.root / group.path
145
- self._lock_path = store_path.with_suffix(".lock")
146
- self._lock = FileLock(self._lock_path, timeout=10)
147
-
148
- else:
149
- self._lock_path = None
150
- self._lock = None
151
-
152
123
  self._group = group
153
- self._mode = mode
154
124
  self.use_cache = cache
155
- self._parallel_safe = parallel_safe
156
- self._cache = {}
157
- self._parent = parent
125
+
126
+ self._group_cache: NgioCache[zarr.Group] = NgioCache(use_cache=cache)
127
+ self._array_cache: NgioCache[zarr.Array] = NgioCache(use_cache=cache)
128
+ self._lock: tuple[Path, BaseFileLock] | None = None
158
129
 
159
130
  def __repr__(self) -> str:
160
131
  """Return a string representation of the handler."""
161
132
  return (
162
- f"ZarrGroupHandler(full_url={self.full_url}, mode={self.mode}, "
133
+ f"ZarrGroupHandler(full_url={self.full_url}, read_only={self.read_only}, "
163
134
  f"cache={self.use_cache}"
164
135
  )
165
136
 
@@ -183,35 +154,55 @@ class ZarrGroupHandler:
183
154
  return self._group.metadata.zarr_format
184
155
 
185
156
  @property
186
- def mode(self) -> AccessModeLiteral:
187
- """Return the mode of the group."""
188
- return self._mode # type: ignore
157
+ def read_only(self) -> bool:
158
+ """Return whether the group is read only."""
159
+ return self._group.read_only
160
+
161
+ def _create_lock(self) -> tuple[Path, BaseFileLock]:
162
+ """Create the lock."""
163
+ if self._lock is not None:
164
+ return self._lock
165
+
166
+ if self.use_cache is True:
167
+ raise NgioValueError(
168
+ "Lock mechanism is not compatible with caching. "
169
+ "Please set cache=False to use the lock mechanism."
170
+ )
171
+
172
+ if not isinstance(self.store, LocalStore):
173
+ raise NgioValueError(
174
+ "The store needs to be a LocalStore to use the lock mechanism. "
175
+ f"Instead, got {self.store.__class__.__name__}."
176
+ )
177
+
178
+ store_path = Path(self.store.root) / self.group.path
179
+ _lock_path = store_path.with_suffix(".lock")
180
+ _lock = FileLock(_lock_path, timeout=10)
181
+ return _lock_path, _lock
189
182
 
190
183
  @property
191
184
  def lock(self) -> BaseFileLock:
192
185
  """Return the lock."""
193
186
  if self._lock is None:
194
- raise NgioValueError(
195
- "The handler is not parallel safe. "
196
- "Reopen the handler with parallel_safe=True."
197
- )
198
- return self._lock
187
+ self._lock = self._create_lock()
188
+ return self._lock[1]
199
189
 
200
190
  @property
201
- def parent(self) -> "ZarrGroupHandler | None":
202
- """Return the parent handler."""
203
- return self._parent
191
+ def lock_path(self) -> Path:
192
+ """Return the lock path."""
193
+ if self._lock is None:
194
+ self._lock = self._create_lock()
195
+ return self._lock[0]
204
196
 
205
197
  def remove_lock(self) -> None:
206
198
  """Return the lock."""
207
- if self._lock is None or self._lock_path is None:
199
+ if self._lock is None:
208
200
  return None
209
201
 
210
- lock_path = Path(self._lock_path)
211
- if lock_path.exists() and self._lock.lock_counter == 0:
202
+ lock_path, lock = self._lock
203
+ if lock_path.exists() and lock.lock_counter == 0:
212
204
  lock_path.unlink()
213
205
  self._lock = None
214
- self._lock_path = None
215
206
  return None
216
207
 
217
208
  raise NgioValueError("The lock is still in use. Cannot remove it.")
@@ -222,10 +213,7 @@ class ZarrGroupHandler:
222
213
  This is useful when the group has been modified
223
214
  outside of the handler.
224
215
  """
225
- if self.mode == "r":
226
- mode = "r"
227
- else:
228
- mode = "r+"
216
+ mode = "r" if self.read_only else "r+"
229
217
  return zarr.open_group(
230
218
  store=self._group.store,
231
219
  path=self._group.path,
@@ -233,72 +221,57 @@ class ZarrGroupHandler:
233
221
  zarr_format=self._group.metadata.zarr_format,
234
222
  )
235
223
 
224
+ def reopen_handler(self) -> "ZarrGroupHandler":
225
+ """Reopen the handler.
226
+
227
+ This is useful when the group has been modified
228
+ outside of the handler.
229
+ """
230
+ mode = "r" if self.read_only else "r+"
231
+ group = self.reopen_group()
232
+ return ZarrGroupHandler(
233
+ store=group,
234
+ zarr_format=group.metadata.zarr_format,
235
+ cache=self.use_cache,
236
+ mode=mode,
237
+ )
238
+
239
+ def clean_cache(self) -> None:
240
+ """Clear the cached metadata."""
241
+ group = self.reopen_group()
242
+ self.__init__(
243
+ store=group,
244
+ zarr_format=group.metadata.zarr_format,
245
+ cache=self.use_cache,
246
+ mode="r" if self.read_only else "r+",
247
+ )
248
+
236
249
  @property
237
250
  def group(self) -> zarr.Group:
238
251
  """Return the group."""
239
- if self._parallel_safe:
240
- # If we are parallel safe, we need to reopen the group
252
+ if self.use_cache is False:
253
+ # If we are not using cache, we need to reopen the group
241
254
  # to make sure that the attributes are up to date
242
255
  return self.reopen_group()
243
256
  return self._group
244
257
 
245
- def add_to_cache(self, key: str, value: object) -> None:
246
- """Add an object to the cache."""
247
- if not self.use_cache:
248
- return None
249
- self._cache[key] = value
250
-
251
- def get_from_cache(self, key: str) -> object | None:
252
- """Get an object from the cache."""
253
- if not self.use_cache:
254
- return None
255
- return self._cache.get(key, None)
256
-
257
- def clean_cache(self) -> None:
258
- """Clear the cached metadata."""
259
- self._cache = {}
260
-
261
258
  def load_attrs(self) -> dict:
262
259
  """Load the attributes of the group."""
263
- attrs = self.get_from_cache("attrs")
264
- if attrs is not None and isinstance(attrs, dict):
265
- return attrs
266
-
267
- attrs = dict(self.group.attrs)
268
-
269
- self.add_to_cache("attrs", attrs)
270
- return attrs
271
-
272
- def _write_attrs(self, attrs: dict, overwrite: bool = False) -> None:
273
- """Write the metadata to the store."""
274
- if self.group.read_only:
275
- raise NgioValueError("The group is read only. Cannot write metadata.")
276
-
277
- # we need to invalidate the current attrs cache
278
- self.add_to_cache("attrs", None)
279
- if overwrite:
280
- self.group.attrs.clear()
281
-
282
- self.group.attrs.update(attrs)
260
+ return self.reopen_group().attrs.asdict()
283
261
 
284
262
  def write_attrs(self, attrs: dict, overwrite: bool = False) -> None:
285
263
  """Write the metadata to the store."""
286
264
  # Maybe we should use the lock here
287
- self._write_attrs(attrs, overwrite)
288
-
289
- def _obj_get(self, path: str):
290
- """Get a group from the group."""
291
- group_or_array = self.get_from_cache(path)
292
- if group_or_array is not None:
293
- return group_or_array
294
-
295
- group_or_array = self.group.get(path, None)
296
- self.add_to_cache(path, group_or_array)
297
- return group_or_array
265
+ if self.read_only:
266
+ raise NgioValueError("The group is read only. Cannot write metadata.")
267
+ group = self.reopen_group()
268
+ if overwrite:
269
+ group.attrs.clear()
270
+ group.attrs.update(attrs)
298
271
 
299
272
  def create_group(self, path: str, overwrite: bool = False) -> zarr.Group:
300
273
  """Create a group in the group."""
301
- if self.mode == "r":
274
+ if self.group.read_only:
302
275
  raise NgioValueError("Cannot create a group in read only mode.")
303
276
 
304
277
  try:
@@ -308,7 +281,7 @@ class ZarrGroupHandler:
308
281
  f"A Zarr group already exists at {path}, "
309
282
  "consider setting overwrite=True."
310
283
  ) from e
311
- self.add_to_cache(path, group)
284
+ self._group_cache.set(path, group, overwrite=overwrite)
312
285
  return group
313
286
 
314
287
  def get_group(
@@ -334,18 +307,22 @@ class ZarrGroupHandler:
334
307
  if overwrite:
335
308
  return self.create_group(path, overwrite=overwrite)
336
309
 
337
- group = self._obj_get(path)
310
+ group = self._group_cache.get(path)
338
311
  if isinstance(group, zarr.Group):
339
312
  return group
340
313
 
341
- if group is not None:
342
- raise NgioValueError(
343
- f"The object at {path} is not a group, but a {type(group)}"
344
- )
314
+ group = self.group.get(path, default=None)
315
+ if isinstance(group, zarr.Group):
316
+ self._group_cache.set(path, group, overwrite=overwrite)
317
+ return group
318
+
319
+ if isinstance(group, zarr.Array):
320
+ raise NgioValueError(f"The object at {path} is not a group, but an array.")
345
321
 
346
322
  if not create_mode:
347
323
  raise NgioFileNotFoundError(f"No group found at {path}")
348
324
  group = self.create_group(path)
325
+ self._group_cache.set(path, group, overwrite=overwrite)
349
326
  return group
350
327
 
351
328
  def safe_get_group(
@@ -369,56 +346,17 @@ class ZarrGroupHandler:
369
346
 
370
347
  def get_array(self, path: str) -> zarr.Array:
371
348
  """Get an array from the group."""
372
- array = self._obj_get(path)
373
- if array is None:
374
- raise NgioFileNotFoundError(f"No array found at {path}")
375
- if not isinstance(array, zarr.Array):
376
- raise NgioValueError(
377
- f"The object at {path} is not an array, but a {type(array)}"
378
- )
379
- return array
380
-
381
- def create_array(
382
- self,
383
- path: str,
384
- shape: tuple[int, ...],
385
- dtype: str,
386
- chunks: tuple[int, ...] | Literal["auto"] = "auto",
387
- compressors: CompressorLike = "auto",
388
- separator: Literal[".", "/"] = "/",
389
- overwrite: bool = False,
390
- ) -> zarr.Array:
391
- if self.mode == "r":
392
- raise NgioValueError("Cannot create an array in read only mode.")
393
-
394
- if self.zarr_format == 2:
395
- chunks_encoding = {
396
- "name": "v2",
397
- "separator": separator,
398
- }
399
- else:
400
- chunks_encoding = {
401
- "name": "default",
402
- "separator": separator,
403
- }
404
-
405
- try:
406
- return self.group.create_array(
407
- name=path,
408
- shape=shape,
409
- dtype=dtype,
410
- chunks=chunks,
411
- chunk_key_encoding=chunks_encoding,
412
- overwrite=overwrite,
413
- compressors=compressors,
414
- )
415
- except ContainsGroupError as e:
416
- raise NgioFileExistsError(
417
- f"A Zarr array already exists at {path}, "
418
- "consider setting overwrite=True."
419
- ) from e
420
- except Exception as e:
421
- raise NgioValueError(f"Error creating array at {path}") from e
349
+ array = self._array_cache.get(path)
350
+ if isinstance(array, zarr.Array):
351
+ return array
352
+ array = self.group.get(path, default=None)
353
+ if isinstance(array, zarr.Array):
354
+ self._array_cache.set(path, array)
355
+ return array
356
+
357
+ if isinstance(array, zarr.Group):
358
+ raise NgioValueError(f"The object at {path} is not an array, but a group.")
359
+ raise NgioFileNotFoundError(f"No array found at {path}")
422
360
 
423
361
  def derive_handler(
424
362
  self,
@@ -433,12 +371,7 @@ class ZarrGroupHandler:
433
371
  """
434
372
  group = self.get_group(path, create_mode=True, overwrite=overwrite)
435
373
  return ZarrGroupHandler(
436
- store=group,
437
- zarr_format=self.zarr_format,
438
- cache=self.use_cache,
439
- mode=self.mode,
440
- parallel_safe=self._parallel_safe,
441
- parent=self,
374
+ store=group, zarr_format=self.zarr_format, cache=self.use_cache, mode="r+"
442
375
  )
443
376
 
444
377
  def safe_derive_handler(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ngio
3
- Version: 0.5.0a1
3
+ Version: 0.5.0a2
4
4
  Summary: Next Generation file format IO
5
5
  Project-URL: homepage, https://github.com/BioVisionCenter/ngio
6
6
  Project-URL: repository, https://github.com/BioVisionCenter/ngio
@@ -17,8 +17,8 @@ Classifier: Typing :: Typed
17
17
  Requires-Python: <3.14,>=3.11
18
18
  Requires-Dist: aiohttp
19
19
  Requires-Dist: anndata
20
- Requires-Dist: dask[array]
21
- Requires-Dist: dask[distributed]
20
+ Requires-Dist: dask[array]<2025.11.0
21
+ Requires-Dist: dask[distributed]<2025.11.0
22
22
  Requires-Dist: filelock
23
23
  Requires-Dist: numpy
24
24
  Requires-Dist: ome-zarr-models
@@ -2,7 +2,7 @@ ngio/__init__.py,sha256=rEgnXuU6TCejUUGsxt4eKmjMhxjYh0fYBxWF4o5YjbE,1435
2
2
  ngio/common/__init__.py,sha256=aPSuUbdGryrxbnlWrsVNe3LZoBAWC4GijR1BNH1UwuU,612
3
3
  ngio/common/_dimensions.py,sha256=w8PYgyWxA8hgJETjFbw5CXf7WrasCL5FbzgfL1in86M,11361
4
4
  ngio/common/_masking_roi.py,sha256=ZZTXordEZoq_ADk0OzADvq-5dPOwUBSuNobzFR8fpTw,5697
5
- ngio/common/_pyramid.py,sha256=186eQSCxP_x2heYKmMP_Edl4Aml9Eo1bBmIHlwslqy8,8699
5
+ ngio/common/_pyramid.py,sha256=loQbtoGWs0g5Ke2s1tSBDsvos0235F-ElhFspsMsIr0,9238
6
6
  ngio/common/_roi.py,sha256=9fKFTHoUiP0xmxvQiFkNmIuwWg3bFuRaAx-froCSqvA,11487
7
7
  ngio/common/_synt_images_utils.py,sha256=B6uYOW1NyrM06YMR-csca3_YnAAkPRTbvnbLdy9tk9E,3188
8
8
  ngio/common/_zoom.py,sha256=U01c-vqXjzZkrpd9Yvs24frVfTls_xPJeeaFCGmUwYI,6727
@@ -15,15 +15,15 @@ ngio/experimental/iterators/_mappers.py,sha256=VVVsjems57wJUnWeufUFcgqa23k7VPeFL
15
15
  ngio/experimental/iterators/_rois_utils.py,sha256=Q-8lQ26neYn63h_RvfypYqvrq2UUN2O3xqVe57k_ufU,4363
16
16
  ngio/experimental/iterators/_segmentation.py,sha256=xzotGvTn04HPeMeXZ_URnQqWco6d2lH6Ng6vkCUh9NM,9153
17
17
  ngio/hcs/__init__.py,sha256=G8j9vD-liLeB_UeGtKYIgshWvJnUA6ks9GwjvWBLdHs,357
18
- ngio/hcs/_plate.py,sha256=qfRwbCKaoz_AWTi8RDFFwOxy5geSknfJrPcqFVno9zI,44288
18
+ ngio/hcs/_plate.py,sha256=9kvvI_qu946xbRqWeY9q1bp45duREyOx-3a-QeKkrGU,43376
19
19
  ngio/images/__init__.py,sha256=9Whvt7GTiCgT_vXaEEqGnDaY1-UsRk3dhLTv091F_g4,1211
20
20
  ngio/images/_abstract_image.py,sha256=hrB9xn4MFRxnxE1d7HKnM8SXVPUGhMD9u32yBHTsFiU,18517
21
- ngio/images/_create.py,sha256=OirIKg843CCSiwy_oUolX2UHlROR_iO4rN_IK47jDWc,10448
22
- ngio/images/_create_synt_container.py,sha256=D3SkjYN_AZ5fSefjEX0iQ3ZGEuBTWZvuPQIwHc6l-CU,5519
23
- ngio/images/_image.py,sha256=JWN7qmNVZxAzm9zmaImYseDt8xzYtOw-N0olR6VQo_8,33483
24
- ngio/images/_label.py,sha256=sJUB8of7t1YErZRu3t7jzGjlRn3KAAs0EL5nFrbvvlc,11921
21
+ ngio/images/_create.py,sha256=IIdX0vER9J3c3aJYZRB7bDGMGv_kv0CbaJjGJM7yjyY,10536
22
+ ngio/images/_create_synt_container.py,sha256=HeRscNNWYHY3rJTO-oKDr5mMuQD6RHWThrv9Rc9yZdg,5539
23
+ ngio/images/_image.py,sha256=LP_XZrL0OLSasPTxaySldKSysyMZvznhFm-E3YLN5Tg,33488
24
+ ngio/images/_label.py,sha256=o7dFDfn-vAgBA80g-vR54M64av_tNebduQqtnG0AkIw,11926
25
25
  ngio/images/_masked_image.py,sha256=YhbBzgPZMav6rX0WYue1BaxAzEIsfaQrxUIOK6ZWZcw,18848
26
- ngio/images/_ome_zarr_container.py,sha256=tfG40PLNfyfaR2pVAmGINlkKJRoQ4W0L0melFotMcP8,38737
26
+ ngio/images/_ome_zarr_container.py,sha256=IlXZojr9HDnfGboLAWD0TlV5Bv7J5fvzKqYc8U43WdU,38777
27
27
  ngio/images/_table_ops.py,sha256=jFv_AMqoB4JBpoWsMtZppZVW7dAOC_u-JpfNm8b33kY,15292
28
28
  ngio/io_pipes/__init__.py,sha256=arW_7GWzZs82kPNKdm_6B1sIDFV0lWwp-ZaORr9Q1FQ,2412
29
29
  ngio/io_pipes/_io_pipes.py,sha256=KuwMYofE11EKx0iplWXBQZ-eE1A9YpGDprWh98cUlAI,10710
@@ -58,7 +58,7 @@ ngio/resources/20200812-CardiomyocyteDifferentiation14-Cycle1_B03/nuclei.png,sha
58
58
  ngio/resources/20200812-CardiomyocyteDifferentiation14-Cycle1_B03/raw.jpg,sha256=82lejQAIokj5w9g-qqhysDTWpHtNvJTkdURG_BjqIxQ,37743
59
59
  ngio/tables/__init__.py,sha256=_BV3sclNMLITu_J8_3DkkUrCB6Kro0HzeWLDCD1ivKM,877
60
60
  ngio/tables/_abstract_table.py,sha256=rwGa47TzbFmosucBWVfFq6JEXtgGvOdUVtU9DIelV88,8204
61
- ngio/tables/_tables_container.py,sha256=3xmpREaN671l40MPprnl1BD-VoOb6xfjECb5mNoMW0w,12173
61
+ ngio/tables/_tables_container.py,sha256=BfwnCsJ3RGnQGRnRXO2XjdaYniL9sxO4HLiJYVgHkOs,12361
62
62
  ngio/tables/backends/__init__.py,sha256=MwSRXNF1rWQBFOTDA_vT3oGoNZpviVgytsL5Txnu08I,1619
63
63
  ngio/tables/backends/_abstract_backend.py,sha256=M1ogsBpWBiQMV65YweZhA845PAtkzG2BsZCPN_7Xp8U,7613
64
64
  ngio/tables/backends/_anndata.py,sha256=KBzXVxaXstVx3ofcLYYFm8ECVFDaHxfUWcKdg5vQPqY,2992
@@ -73,16 +73,17 @@ ngio/tables/v1/__init__.py,sha256=Wr1_9RZFpaN8FYMTnxT9Yjkw4AS7y9FMWailmB_uj5g,61
73
73
  ngio/tables/v1/_condition_table.py,sha256=T0Uq5BKkmMoEspt_Rx0U99Ow6S9GAMZDHqvUO5obCAM,1780
74
74
  ngio/tables/v1/_feature_table.py,sha256=n9uMHwoBh-_dlOhUXCFbmAjXFVXncNCR3SjE2qzXI68,3821
75
75
  ngio/tables/v1/_generic_table.py,sha256=1ktJHeuv7U1g5Z8PFUuTkCjOzcYMQd8xegKHKUedJB8,1240
76
- ngio/tables/v1/_roi_table.py,sha256=g7UpMmpf3uAfaG59WYRnimUBgiB_T1qUJRwMZpMt9cI,17099
76
+ ngio/tables/v1/_roi_table.py,sha256=b7lwjsdCSUOCMT6Lx4hwAqGBKC25Q5bdzoK3DQWnAQM,17074
77
77
  ngio/transforms/__init__.py,sha256=JA0-Ui7skbXkm9ofN-AEhU1FTLutkMkwTdVD-310frQ,113
78
78
  ngio/transforms/_zoom.py,sha256=otyE-vxFnywUJ8U4mHjat-bNG_7_jv62ckTpqDMxyVQ,550
79
- ngio/utils/__init__.py,sha256=XPYh8ehC7uXNU2cFFXZAw-S3DpWpX1Yq2xGkffZv5vI,1142
79
+ ngio/utils/__init__.py,sha256=w_v3BaphrlsVlZF3tZHlyp6dOfRdRHaauBmdwGsjaCE,1174
80
+ ngio/utils/_cache.py,sha256=Ey9fgc_BTdMyqg6c80C0CuGDhOafln8-3e_1MQ0MFzw,1283
80
81
  ngio/utils/_datasets.py,sha256=6GtxfPkjutNaeg5BHuJDBP0GudvQXHLU6mmHp_o0bGA,5650
81
82
  ngio/utils/_errors.py,sha256=pKQ12LUjQLYE1nUawemA5h7HsgznjaSvV1n2PQU33N0,759
82
83
  ngio/utils/_fractal_fsspec_store.py,sha256=RdcCFOgHexRKX9zZvJV5RI-5OPc7VOPS6q_IeRxm24I,1548
83
84
  ngio/utils/_logger.py,sha256=N5W0a_xwze4blS1MolidBkTMbjTbg8GPguJZNun3mAE,1392
84
- ngio/utils/_zarr_utils.py,sha256=ATehiEALPI__eNQSWdSW5PpJLmI2FECrexA9jTZAI2s,16103
85
- ngio-0.5.0a1.dist-info/METADATA,sha256=AT0kbnHlYkhiL0_AmZpOm8DagaEPGCkI1xze_cJy3w8,6104
86
- ngio-0.5.0a1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
87
- ngio-0.5.0a1.dist-info/licenses/LICENSE,sha256=UgN_a1QCeNh9rZWfz-wORQFxE3elQzLWPQaoK6N6fxQ,1502
88
- ngio-0.5.0a1.dist-info/RECORD,,
85
+ ngio/utils/_zarr_utils.py,sha256=Ak3M07hxI9-5W1mG2jxZE8rUrSZnidJ-nEQenKuCqls,14114
86
+ ngio-0.5.0a2.dist-info/METADATA,sha256=Skm_1lG1u3unJ7Ij9mnIYm8RVyU0P-AFISHvl-HEz2M,6124
87
+ ngio-0.5.0a2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
88
+ ngio-0.5.0a2.dist-info/licenses/LICENSE,sha256=UgN_a1QCeNh9rZWfz-wORQFxE3elQzLWPQaoK6N6fxQ,1502
89
+ ngio-0.5.0a2.dist-info/RECORD,,
File without changes