ngio 0.2.0a3__py3-none-any.whl → 0.2.0b1__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 (49) hide show
  1. ngio/__init__.py +4 -4
  2. ngio/common/__init__.py +12 -2
  3. ngio/common/_array_pipe.py +106 -0
  4. ngio/common/_axes_transforms.py +3 -2
  5. ngio/common/_dimensions.py +7 -0
  6. ngio/common/_masking_roi.py +158 -0
  7. ngio/common/_pyramid.py +16 -11
  8. ngio/common/_roi.py +74 -0
  9. ngio/common/_slicer.py +1 -2
  10. ngio/common/_zoom.py +5 -3
  11. ngio/hcs/__init__.py +2 -57
  12. ngio/hcs/plate.py +399 -0
  13. ngio/images/abstract_image.py +97 -28
  14. ngio/images/create.py +48 -29
  15. ngio/images/image.py +99 -46
  16. ngio/images/label.py +109 -92
  17. ngio/images/masked_image.py +259 -0
  18. ngio/images/omezarr_container.py +201 -64
  19. ngio/ome_zarr_meta/__init__.py +25 -13
  20. ngio/ome_zarr_meta/_meta_handlers.py +718 -69
  21. ngio/ome_zarr_meta/ngio_specs/__init__.py +8 -0
  22. ngio/ome_zarr_meta/ngio_specs/_channels.py +11 -0
  23. ngio/ome_zarr_meta/ngio_specs/_ngio_hcs.py +374 -2
  24. ngio/ome_zarr_meta/ngio_specs/_ngio_image.py +169 -119
  25. ngio/ome_zarr_meta/ngio_specs/_pixel_size.py +35 -3
  26. ngio/ome_zarr_meta/v04/__init__.py +17 -5
  27. ngio/ome_zarr_meta/v04/_v04_spec_utils.py +85 -12
  28. ngio/tables/__init__.py +2 -0
  29. ngio/tables/_validators.py +2 -4
  30. ngio/tables/backends/_anndata_utils.py +2 -1
  31. ngio/tables/backends/_anndata_v1.py +2 -1
  32. ngio/tables/backends/_json_v1.py +1 -1
  33. ngio/tables/tables_container.py +12 -2
  34. ngio/tables/v1/__init__.py +1 -2
  35. ngio/tables/v1/_feature_table.py +7 -5
  36. ngio/tables/v1/_generic_table.py +65 -11
  37. ngio/tables/v1/_roi_table.py +145 -27
  38. ngio/utils/_datasets.py +4 -2
  39. ngio/utils/_fractal_fsspec_store.py +3 -2
  40. ngio/utils/_logger.py +3 -1
  41. ngio/utils/_zarr_utils.py +25 -2
  42. {ngio-0.2.0a3.dist-info → ngio-0.2.0b1.dist-info}/METADATA +4 -1
  43. ngio-0.2.0b1.dist-info/RECORD +54 -0
  44. ngio/ome_zarr_meta/_generic_handlers.py +0 -320
  45. ngio/ome_zarr_meta/v04/_meta_handlers.py +0 -54
  46. ngio/tables/v1/_masking_roi_table.py +0 -175
  47. ngio-0.2.0a3.dist-info/RECORD +0 -54
  48. {ngio-0.2.0a3.dist-info → ngio-0.2.0b1.dist-info}/WHEEL +0 -0
  49. {ngio-0.2.0a3.dist-info → ngio-0.2.0b1.dist-info}/licenses/LICENSE +0 -0
@@ -13,7 +13,7 @@ class JsonTableBackend(AbstractTableBackend):
13
13
  @staticmethod
14
14
  def backend_name() -> str:
15
15
  """The name of the backend."""
16
- return "json_v1"
16
+ return "experimental_json_v1"
17
17
 
18
18
  @staticmethod
19
19
  def implements_anndata() -> bool:
@@ -4,6 +4,7 @@ from typing import Literal, Protocol
4
4
 
5
5
  from ngio.tables.v1 import FeatureTableV1, MaskingROITableV1, RoiTableV1
6
6
  from ngio.tables.v1._generic_table import GenericTable
7
+ from ngio.tables.v1._roi_table import _GenericRoiTableV1
7
8
  from ngio.utils import (
8
9
  AccessModeLiteral,
9
10
  NgioValidationError,
@@ -12,6 +13,7 @@ from ngio.utils import (
12
13
  ZarrGroupHandler,
13
14
  )
14
15
 
16
+ GenericRoiTable = _GenericRoiTableV1
15
17
  RoiTable = RoiTableV1
16
18
  MaskingROITable = MaskingROITableV1
17
19
  FeatureTable = FeatureTableV1
@@ -55,7 +57,9 @@ class Table(Protocol):
55
57
  ...
56
58
 
57
59
 
58
- TypedTable = Literal["roi_table", "masking_roi_table", "feature_table"]
60
+ TypedTable = Literal[
61
+ "roi_table", "masking_roi_table", "feature_table", "generic_roi_table"
62
+ ]
59
63
 
60
64
 
61
65
  def _unique_table_name(type_name, version) -> str:
@@ -99,7 +103,6 @@ class ImplementedTables:
99
103
  return table
100
104
  except Exception as e:
101
105
  _errors[name] = e
102
- print(_errors)
103
106
  # If no table was found, we can try to load the table from a generic table
104
107
  try:
105
108
  table = GenericTable._from_handler(
@@ -183,6 +186,13 @@ class TablesContainer:
183
186
  handler = self._group_handler.derive_handler(path=name)
184
187
  return handler
185
188
 
189
+ def list_roi_tables(self) -> list[str]:
190
+ """List all ROI tables in the group."""
191
+ _tables = []
192
+ for _type in ["roi_table", "masking_roi_table"]:
193
+ _tables.extend(self.list(_type))
194
+ return _tables
195
+
186
196
  def list(self, filter_types: str | None = None) -> list[str]:
187
197
  """List all labels in the group."""
188
198
  tables = self._get_tables_list()
@@ -2,7 +2,6 @@
2
2
 
3
3
  from ngio.tables.v1._feature_table import FeatureTableV1
4
4
  from ngio.tables.v1._generic_table import GenericTable
5
- from ngio.tables.v1._masking_roi_table import MaskingROITableV1
6
- from ngio.tables.v1._roi_table import RoiTableV1
5
+ from ngio.tables.v1._roi_table import MaskingROITableV1, RoiTableV1
7
6
 
8
7
  __all__ = ["FeatureTableV1", "GenericTable", "MaskingROITableV1", "RoiTableV1"]
@@ -11,7 +11,7 @@ from pydantic import BaseModel
11
11
 
12
12
  from ngio.tables._validators import validate_index_key
13
13
  from ngio.tables.backends import ImplementedTableBackends
14
- from ngio.utils import ZarrGroupHandler
14
+ from ngio.utils import NgioValueError, ZarrGroupHandler
15
15
 
16
16
 
17
17
  class RegionMeta(BaseModel):
@@ -83,7 +83,7 @@ class FeatureTableV1:
83
83
  def dataframe(self) -> pd.DataFrame:
84
84
  """Return the table as a DataFrame."""
85
85
  if self._dataframe is None and self._table_backend is None:
86
- raise ValueError(
86
+ raise NgioValueError(
87
87
  "The table does not have a DataFrame in memory nor a backend."
88
88
  )
89
89
 
@@ -91,7 +91,7 @@ class FeatureTableV1:
91
91
  self._dataframe = self._table_backend.load_as_dataframe()
92
92
 
93
93
  if self._dataframe is None:
94
- raise ValueError(
94
+ raise NgioValueError(
95
95
  "The table does not have a DataFrame in memory nor a backend."
96
96
  )
97
97
  return self._dataframe
@@ -125,7 +125,9 @@ class FeatureTableV1:
125
125
  meta.backend = backend_name
126
126
 
127
127
  if not backend.implements_dataframe:
128
- raise ValueError("The backend does not implement the dataframe protocol.")
128
+ raise NgioValueError(
129
+ "The backend does not implement the dataframe protocol."
130
+ )
129
131
 
130
132
  table = cls()
131
133
  table._meta = meta
@@ -151,7 +153,7 @@ class FeatureTableV1:
151
153
  def consolidate(self) -> None:
152
154
  """Write the current state of the table to the Zarr file."""
153
155
  if self._table_backend is None:
154
- raise ValueError(
156
+ raise NgioValueError(
155
157
  "No backend set for the table. "
156
158
  "Please add the table to a OME-Zarr Image before calling consolidate."
157
159
  )
@@ -1,10 +1,15 @@
1
1
  """Implementation of a generic table class."""
2
2
 
3
3
  import pandas as pd
4
+ from anndata import AnnData
4
5
  from pydantic import BaseModel
5
6
 
6
7
  from ngio.tables.backends import ImplementedTableBackends
7
- from ngio.utils import ZarrGroupHandler
8
+ from ngio.tables.backends._anndata_utils import (
9
+ anndata_to_dataframe,
10
+ dataframe_to_anndata,
11
+ )
12
+ from ngio.utils import NgioValueError, ZarrGroupHandler
8
13
 
9
14
 
10
15
  class GenericTableMeta(BaseModel):
@@ -24,11 +29,26 @@ class GenericTable:
24
29
 
25
30
  def __init__(
26
31
  self,
27
- dataframe: pd.DataFrame,
32
+ dataframe: pd.DataFrame | None = None,
33
+ anndata: AnnData | None = None,
28
34
  ) -> None:
29
35
  """Initialize the GenericTable."""
30
36
  self._meta = GenericTableMeta()
37
+ if dataframe is None and anndata is None:
38
+ raise NgioValueError(
39
+ "Either a DataFrame or an AnnData object must be provided."
40
+ )
41
+
42
+ if dataframe is not None and anndata is not None:
43
+ raise NgioValueError(
44
+ "Only one of DataFrame or AnnData object can be provided."
45
+ )
46
+
31
47
  self._dataframe = dataframe
48
+ self._anndata = anndata
49
+
50
+ self.anndata_native = True if anndata is not None else False
51
+
32
52
  self._table_backend = None
33
53
 
34
54
  @staticmethod
@@ -54,12 +74,35 @@ class GenericTable:
54
74
  @property
55
75
  def dataframe(self) -> pd.DataFrame:
56
76
  """Return the table as a DataFrame."""
57
- return self._dataframe
77
+ if self._dataframe is not None:
78
+ return self._dataframe
79
+
80
+ if self._anndata is not None:
81
+ return anndata_to_dataframe(self._anndata)
82
+
83
+ raise NgioValueError("No table loaded.")
58
84
 
59
85
  @dataframe.setter
60
86
  def dataframe(self, dataframe: pd.DataFrame) -> None:
61
87
  """Set the table as a DataFrame."""
62
88
  self._dataframe = dataframe
89
+ self.anndata_native = False
90
+
91
+ @property
92
+ def anndata(self) -> AnnData:
93
+ """Return the table as an AnnData object."""
94
+ if self._anndata is not None:
95
+ return self._anndata
96
+
97
+ if self._dataframe is not None:
98
+ return dataframe_to_anndata(self._dataframe)
99
+ raise NgioValueError("No table loaded.")
100
+
101
+ @anndata.setter
102
+ def anndata(self, anndata: AnnData) -> None:
103
+ """Set the table as an AnnData object."""
104
+ self._anndata = anndata
105
+ self.anndata_native = True
63
106
 
64
107
  @classmethod
65
108
  def _from_handler(
@@ -81,12 +124,18 @@ class GenericTable:
81
124
  )
82
125
  meta.backend = backend_name
83
126
 
84
- if not backend.implements_dataframe:
85
- raise ValueError("The backend does not implement the dataframe protocol.")
127
+ if backend.implements_anndata():
128
+ anndata = backend.load_as_anndata()
129
+ table = cls(anndata=anndata)
86
130
 
87
- dataframe = backend.load_as_dataframe()
131
+ elif backend.implements_dataframe():
132
+ dataframe = backend.load_as_dataframe()
133
+ table = cls(dataframe=dataframe)
134
+ else:
135
+ raise NgioValueError(
136
+ "The backend does not implement the dataframe protocol."
137
+ )
88
138
 
89
- table = cls(dataframe)
90
139
  table._meta = meta
91
140
  table._table_backend = backend
92
141
  return table
@@ -108,11 +157,16 @@ class GenericTable:
108
157
  def consolidate(self) -> None:
109
158
  """Write the current state of the table to the Zarr file."""
110
159
  if self._table_backend is None:
111
- raise ValueError(
160
+ raise NgioValueError(
112
161
  "No backend set for the table. "
113
162
  "Please add the table to a OME-Zarr Image before calling consolidate."
114
163
  )
115
164
 
116
- self._table_backend.write_from_dataframe(
117
- self._dataframe, metadata=self._meta.model_dump(exclude_none=True)
118
- )
165
+ if self.anndata_native:
166
+ self._table_backend.write_from_anndata(
167
+ self.anndata, metadata=self._meta.model_dump(exclude_none=True)
168
+ )
169
+ else:
170
+ self._table_backend.write_from_dataframe(
171
+ self.dataframe, metadata=self._meta.model_dump(exclude_none=True)
172
+ )
@@ -4,8 +4,10 @@ This class follows the roi_table specification at:
4
4
  https://fractal-analytics-platform.github.io/fractal-tasks-core/tables/
5
5
  """
6
6
 
7
+ # Import _type to avoid name conflict with table.type
8
+ from builtins import type as _type
7
9
  from collections.abc import Iterable
8
- from typing import Literal
10
+ from typing import Generic, Literal, TypeVar
9
11
 
10
12
  import pandas as pd
11
13
  from pydantic import BaseModel
@@ -89,7 +91,7 @@ def _rois_to_dataframe(rois: dict[str, WorldCooROI], index_key: str) -> pd.DataF
89
91
  return dataframe
90
92
 
91
93
 
92
- class ROITableV1Meta(BaseModel):
94
+ class RoiTableV1Meta(BaseModel):
93
95
  """Metadata for the ROI table."""
94
96
 
95
97
  fractal_table_version: Literal["1"] = "1"
@@ -97,17 +99,37 @@ class ROITableV1Meta(BaseModel):
97
99
  backend: str | None = None
98
100
 
99
101
 
100
- class RoiTableV1:
101
- """Class to handle fractal ROI tables.
102
+ class RegionMeta(BaseModel):
103
+ """Metadata for the region."""
102
104
 
103
- To know more about the ROI table format, please refer to the
104
- specification at:
105
- https://fractal-analytics-platform.github.io/fractal-tasks-core/tables/
106
- """
105
+ path: str
107
106
 
108
- def __init__(self, rois: Iterable[WorldCooROI] | None = None) -> None:
107
+
108
+ class MaskingRoiTableV1Meta(BaseModel):
109
+ """Metadata for the ROI table."""
110
+
111
+ fractal_table_version: Literal["1"] = "1"
112
+ type: Literal["masking_roi_table"] = "masking_roi_table"
113
+ backend: str | None = None
114
+ region: RegionMeta | None = None
115
+ instance_key: str = "label"
116
+
117
+
118
+ _roi_meta = TypeVar("_roi_meta", RoiTableV1Meta, MaskingRoiTableV1Meta)
119
+
120
+
121
+ class _GenericRoiTableV1(Generic[_roi_meta]):
122
+ """Class to a non-specific table."""
123
+
124
+ _meta: _roi_meta
125
+
126
+ def __init__(
127
+ self, meta: _roi_meta | None = None, rois: Iterable[WorldCooROI] | None = None
128
+ ) -> None:
109
129
  """Create a new ROI table."""
110
- self._meta = ROITableV1Meta()
130
+ if meta is None:
131
+ raise NgioValueError("Metadata must be provided.")
132
+ self._meta = meta
111
133
  self._table_backend = None
112
134
 
113
135
  self._rois = {}
@@ -115,15 +137,30 @@ class RoiTableV1:
115
137
  self.add(rois)
116
138
 
117
139
  @staticmethod
118
- def type() -> Literal["roi_table"]:
140
+ def type() -> str:
119
141
  """Return the type of the table."""
120
- return "roi_table"
142
+ raise NotImplementedError
121
143
 
122
144
  @staticmethod
123
145
  def version() -> Literal["1"]:
124
146
  """Return the version of the fractal table."""
125
147
  return "1"
126
148
 
149
+ @staticmethod
150
+ def _index_key() -> str:
151
+ """Return the index key of the table."""
152
+ raise NotImplementedError
153
+
154
+ @staticmethod
155
+ def _index_type() -> Literal["int", "str"]:
156
+ """Return the index type of the table."""
157
+ raise NotImplementedError
158
+
159
+ @staticmethod
160
+ def _meta_type() -> _type[_roi_meta]:
161
+ """Return the metadata type of the table."""
162
+ raise NotImplementedError
163
+
127
164
  @property
128
165
  def backend_name(self) -> str | None:
129
166
  """Return the name of the backend."""
@@ -134,23 +171,23 @@ class RoiTableV1:
134
171
  @classmethod
135
172
  def _from_handler(
136
173
  cls, handler: ZarrGroupHandler, backend_name: str | None = None
137
- ) -> "RoiTableV1":
174
+ ) -> "_GenericRoiTableV1":
138
175
  """Create a new ROI table from a Zarr store."""
139
- meta = ROITableV1Meta(**handler.load_attrs())
176
+ meta = cls._meta_type()(**handler.load_attrs())
140
177
 
141
178
  if backend_name is None:
142
179
  backend = ImplementedTableBackends().get_backend(
143
180
  backend_name=meta.backend,
144
181
  group_handler=handler,
145
- index_key="FieldIndex",
146
- index_type="str",
182
+ index_key=cls._index_key(),
183
+ index_type=cls._index_type(),
147
184
  )
148
185
  else:
149
186
  backend = ImplementedTableBackends().get_backend(
150
187
  backend_name=backend_name,
151
188
  group_handler=handler,
152
- index_key="FieldIndex",
153
- index_type="str",
189
+ index_key=cls._index_key(),
190
+ index_type=cls._index_type(),
154
191
  )
155
192
  meta.backend = backend_name
156
193
 
@@ -159,6 +196,7 @@ class RoiTableV1:
159
196
  "The backend does not implement the dataframe protocol."
160
197
  )
161
198
 
199
+ # This will be implemented in the child classes
162
200
  table = cls()
163
201
  table._meta = meta
164
202
  table._table_backend = backend
@@ -181,8 +219,8 @@ class RoiTableV1:
181
219
  backend = ImplementedTableBackends().get_backend(
182
220
  backend_name=backend_name,
183
221
  group_handler=handler,
184
- index_key="FieldIndex",
185
- index_type="str",
222
+ index_key=self._index_key(),
223
+ index_type=self._index_type(),
186
224
  )
187
225
  self._meta.backend = backend_name
188
226
  self._table_backend = backend
@@ -191,12 +229,6 @@ class RoiTableV1:
191
229
  """List all ROIs in the table."""
192
230
  return list(self._rois.values())
193
231
 
194
- def get(self, roi_name: str) -> WorldCooROI:
195
- """Get an ROI from the table."""
196
- if roi_name not in self._rois:
197
- raise NgioValueError(f"ROI {roi_name} not found in the table.")
198
- return self._rois[roi_name]
199
-
200
232
  def add(self, roi: WorldCooROI | Iterable[WorldCooROI]) -> None:
201
233
  """Append ROIs to the current table."""
202
234
  if isinstance(roi, WorldCooROI):
@@ -215,7 +247,7 @@ class RoiTableV1:
215
247
  "Please add the table to a OME-Zarr Image before calling consolidate."
216
248
  )
217
249
 
218
- dataframe = _rois_to_dataframe(self._rois, index_key="FieldIndex")
250
+ dataframe = _rois_to_dataframe(self._rois, index_key=self._index_key())
219
251
  dataframe = validate_columns(
220
252
  dataframe,
221
253
  required_columns=REQUIRED_COLUMNS,
@@ -224,3 +256,89 @@ class RoiTableV1:
224
256
  self._table_backend.write_from_dataframe(
225
257
  dataframe, metadata=self._meta.model_dump(exclude_none=True)
226
258
  )
259
+
260
+
261
+ class RoiTableV1(_GenericRoiTableV1[RoiTableV1Meta]):
262
+ """Class to handle fractal ROI tables.
263
+
264
+ To know more about the ROI table format, please refer to the
265
+ specification at:
266
+ https://fractal-analytics-platform.github.io/fractal-tasks-core/tables/
267
+ """
268
+
269
+ def __init__(self, rois: Iterable[WorldCooROI] | None = None) -> None:
270
+ """Create a new ROI table."""
271
+ super().__init__(RoiTableV1Meta(), rois)
272
+
273
+ @staticmethod
274
+ def type() -> Literal["roi_table"]:
275
+ """Return the type of the table."""
276
+ return "roi_table"
277
+
278
+ @staticmethod
279
+ def _index_key() -> str:
280
+ """Return the index key of the table."""
281
+ return "FieldIndex"
282
+
283
+ @staticmethod
284
+ def _index_type() -> Literal["int", "str"]:
285
+ """Return the index type of the table."""
286
+ return "str"
287
+
288
+ @staticmethod
289
+ def _meta_type() -> _type[RoiTableV1Meta]:
290
+ """Return the metadata type of the table."""
291
+ return RoiTableV1Meta
292
+
293
+ def get(self, roi_name: str) -> WorldCooROI:
294
+ """Get an ROI from the table."""
295
+ if roi_name not in self._rois:
296
+ raise NgioValueError(f"ROI {roi_name} not found in the table.")
297
+ return self._rois[roi_name]
298
+
299
+
300
+ class MaskingROITableV1(_GenericRoiTableV1[MaskingRoiTableV1Meta]):
301
+ """Class to handle fractal ROI tables.
302
+
303
+ To know more about the ROI table format, please refer to the
304
+ specification at:
305
+ https://fractal-analytics-platform.github.io/fractal-tasks-core/tables/
306
+ """
307
+
308
+ def __init__(
309
+ self,
310
+ rois: Iterable[WorldCooROI] | None = None,
311
+ reference_label: str | None = None,
312
+ ) -> None:
313
+ """Create a new ROI table."""
314
+ meta = MaskingRoiTableV1Meta()
315
+ if reference_label is not None:
316
+ meta.region = RegionMeta(path=reference_label)
317
+ super().__init__(meta, rois)
318
+
319
+ @staticmethod
320
+ def type() -> Literal["masking_roi_table"]:
321
+ """Return the type of the table."""
322
+ return "masking_roi_table"
323
+
324
+ @staticmethod
325
+ def _index_key() -> str:
326
+ """Return the index key of the table."""
327
+ return "label"
328
+
329
+ @staticmethod
330
+ def _index_type() -> Literal["int", "str"]:
331
+ """Return the index type of the table."""
332
+ return "int"
333
+
334
+ @staticmethod
335
+ def _meta_type() -> _type[MaskingRoiTableV1Meta]:
336
+ """Return the metadata type of the table."""
337
+ return MaskingRoiTableV1Meta
338
+
339
+ def get(self, label: int) -> WorldCooROI:
340
+ """Get an ROI from the table."""
341
+ _label = str(label)
342
+ if _label not in self._rois:
343
+ raise KeyError(f"ROI {_label} not found in the table.")
344
+ return self._rois[_label]
ngio/utils/_datasets.py CHANGED
@@ -4,6 +4,8 @@ from pathlib import Path
4
4
 
5
5
  import pooch
6
6
 
7
+ from ngio.utils._errors import NgioValueError
8
+
7
9
  _ome_zarr_zoo = {
8
10
  "CardiomyocyteTiny": {
9
11
  "url": "https://zenodo.org/records/13305156/files/20200812-CardiomyocyteDifferentiation14-Cycle1.zarr.zip",
@@ -27,7 +29,7 @@ def list_ome_zarr_datasets() -> list[str]:
27
29
 
28
30
  def download_ome_zarr_dataset(
29
31
  dataset_name: str,
30
- download_dir: str = "data",
32
+ download_dir: str | Path = "data",
31
33
  ) -> Path:
32
34
  """Download an OME-Zarr dataset.
33
35
 
@@ -38,7 +40,7 @@ def download_ome_zarr_dataset(
38
40
  download_dir (str): The download directory. Defaults to "data".
39
41
  """
40
42
  if dataset_name not in _ome_zarr_zoo:
41
- raise ValueError(f"Dataset {dataset_name} not found in the OME-Zarr zoo.")
43
+ raise NgioValueError(f"Dataset {dataset_name} not found in the OME-Zarr zoo.")
42
44
  ome_zarr_url = _ome_zarr_zoo[dataset_name]
43
45
  pooch.retrieve(
44
46
  path=download_dir,
@@ -2,11 +2,12 @@ import fsspec.implementations.http
2
2
 
3
3
 
4
4
  def fractal_fsspec_store(
5
- url: str, fractal_token: str, client_kwargs: dict | None = None
5
+ url: str, fractal_token: str | None = None, client_kwargs: dict | None = None
6
6
  ) -> fsspec.mapping.FSMap:
7
7
  """Simple function to get an http fsspec store from a url."""
8
8
  client_kwargs = {} if client_kwargs is None else client_kwargs
9
- client_kwargs["headers"] = {"Authorization": f"Bearer {fractal_token}"}
9
+ if fractal_token is not None:
10
+ client_kwargs["headers"] = {"Authorization": f"Bearer {fractal_token}"}
10
11
  fs = fsspec.implementations.http.HTTPFileSystem(client_kwargs=client_kwargs)
11
12
  store = fs.get_mapper(url)
12
13
  return store
ngio/utils/_logger.py CHANGED
@@ -1,5 +1,7 @@
1
1
  import logging
2
2
 
3
+ from ngio.utils._errors import NgioValueError
4
+
3
5
  # Configure the logger
4
6
  ngio_logger = logging.getLogger("NgioLogger")
5
7
  ngio_logger.setLevel(logging.ERROR)
@@ -24,6 +26,6 @@ def set_logger_level(level: str) -> None:
24
26
  Must be one of "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL".
25
27
  """
26
28
  if level not in ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]:
27
- raise ValueError(f"Invalid log level: {level}")
29
+ raise NgioValueError(f"Invalid log level: {level}")
28
30
 
29
31
  ngio_logger.setLevel(level)
ngio/utils/_zarr_utils.py CHANGED
@@ -131,7 +131,7 @@ class ZarrGroupHandler:
131
131
  "The store needs to be a path to use the lock mechanism."
132
132
  )
133
133
  self._lock_path = f"{_store}.lock"
134
- self._lock = FileLock(self._lock_path)
134
+ self._lock = FileLock(self._lock_path, timeout=10)
135
135
 
136
136
  else:
137
137
  self._lock_path = None
@@ -168,8 +168,13 @@ class ZarrGroupHandler:
168
168
  return self._mode # type: ignore
169
169
 
170
170
  @property
171
- def lock(self) -> BaseFileLock | None:
171
+ def lock(self) -> BaseFileLock:
172
172
  """Return the lock."""
173
+ if self._lock is None:
174
+ raise NgioValueError(
175
+ "The handler is not parallel safe. "
176
+ "Reopen the handler with parallel_safe=True."
177
+ )
173
178
  return self._lock
174
179
 
175
180
  @property
@@ -376,3 +381,21 @@ class ZarrGroupHandler:
376
381
  return True, self.derive_handler(path)
377
382
  except NgioError as e:
378
383
  return False, e
384
+
385
+ def copy_handler(self, handler: "ZarrGroupHandler") -> None:
386
+ """Copy the group to a new store."""
387
+ _, n_skipped, _ = zarr.copy_store(
388
+ source=self.group.store,
389
+ dest=handler.group.store,
390
+ source_path=self.group.path,
391
+ dest_path=handler.group.path,
392
+ if_exists="replace",
393
+ )
394
+ if n_skipped > 0:
395
+ raise NgioValueError(
396
+ f"Error copying group to {handler.full_path}, "
397
+ f"#{n_skipped} files where skipped."
398
+ )
399
+
400
+
401
+ # %%
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ngio
3
- Version: 0.2.0a3
3
+ Version: 0.2.0b1
4
4
  Summary: Next Generation file format IO
5
5
  Project-URL: homepage, https://github.com/lorenzocerrone/ngio
6
6
  Project-URL: repository, https://github.com/lorenzocerrone/ngio
@@ -29,6 +29,7 @@ Requires-Dist: requests
29
29
  Requires-Dist: xarray
30
30
  Requires-Dist: zarr<3
31
31
  Provides-Extra: dev
32
+ Requires-Dist: devtools; extra == 'dev'
32
33
  Requires-Dist: matplotlib; extra == 'dev'
33
34
  Requires-Dist: mypy; extra == 'dev'
34
35
  Requires-Dist: napari; extra == 'dev'
@@ -38,6 +39,7 @@ Requires-Dist: pre-commit; extra == 'dev'
38
39
  Requires-Dist: pyqt5; extra == 'dev'
39
40
  Requires-Dist: rich; extra == 'dev'
40
41
  Requires-Dist: ruff; extra == 'dev'
42
+ Requires-Dist: scikit-image; extra == 'dev'
41
43
  Provides-Extra: docs
42
44
  Requires-Dist: mkdocs; extra == 'docs'
43
45
  Requires-Dist: mkdocs-autorefs; extra == 'docs'
@@ -50,6 +52,7 @@ Requires-Dist: scikit-image; extra == 'docs'
50
52
  Provides-Extra: test
51
53
  Requires-Dist: pytest; extra == 'test'
52
54
  Requires-Dist: pytest-cov; extra == 'test'
55
+ Requires-Dist: scikit-image; extra == 'test'
53
56
  Description-Content-Type: text/markdown
54
57
 
55
58
  # NGIO - Next Generation file format IO