ngio 0.4.7__py3-none-any.whl → 0.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ngio/__init__.py +5 -2
- ngio/common/__init__.py +11 -6
- ngio/common/_masking_roi.py +34 -54
- ngio/common/_pyramid.py +322 -75
- ngio/common/_roi.py +258 -330
- ngio/experimental/iterators/_feature.py +3 -3
- ngio/experimental/iterators/_rois_utils.py +10 -11
- ngio/hcs/_plate.py +192 -136
- ngio/images/_abstract_image.py +539 -35
- ngio/images/_create_synt_container.py +45 -47
- ngio/images/_create_utils.py +406 -0
- ngio/images/_image.py +524 -248
- ngio/images/_label.py +257 -180
- ngio/images/_masked_image.py +2 -2
- ngio/images/_ome_zarr_container.py +658 -255
- ngio/io_pipes/_io_pipes.py +9 -9
- ngio/io_pipes/_io_pipes_masked.py +7 -7
- ngio/io_pipes/_io_pipes_roi.py +6 -6
- ngio/io_pipes/_io_pipes_types.py +3 -3
- ngio/io_pipes/_match_shape.py +6 -8
- ngio/io_pipes/_ops_slices_utils.py +8 -5
- ngio/ome_zarr_meta/__init__.py +29 -18
- ngio/ome_zarr_meta/_meta_handlers.py +402 -689
- ngio/ome_zarr_meta/ngio_specs/__init__.py +4 -0
- ngio/ome_zarr_meta/ngio_specs/_axes.py +152 -51
- ngio/ome_zarr_meta/ngio_specs/_dataset.py +13 -22
- ngio/ome_zarr_meta/ngio_specs/_ngio_hcs.py +129 -91
- ngio/ome_zarr_meta/ngio_specs/_ngio_image.py +69 -69
- ngio/ome_zarr_meta/v04/__init__.py +5 -1
- ngio/ome_zarr_meta/v04/{_v04_spec_utils.py → _v04_spec.py} +55 -86
- ngio/ome_zarr_meta/v05/__init__.py +27 -0
- ngio/ome_zarr_meta/v05/_custom_models.py +18 -0
- ngio/ome_zarr_meta/v05/_v05_spec.py +495 -0
- ngio/resources/__init__.py +1 -1
- ngio/resources/resource_model.py +1 -1
- ngio/tables/_tables_container.py +82 -24
- ngio/tables/backends/_abstract_backend.py +7 -0
- ngio/tables/backends/_anndata.py +60 -7
- ngio/tables/backends/_anndata_utils.py +2 -4
- ngio/tables/backends/_csv.py +3 -19
- ngio/tables/backends/_json.py +10 -13
- ngio/tables/backends/_parquet.py +3 -31
- ngio/tables/backends/_py_arrow_backends.py +222 -0
- ngio/tables/backends/_utils.py +1 -1
- ngio/tables/v1/_roi_table.py +41 -24
- ngio/utils/__init__.py +8 -12
- ngio/utils/_cache.py +48 -0
- ngio/utils/_datasets.py +6 -0
- ngio/utils/_zarr_utils.py +354 -236
- {ngio-0.4.7.dist-info → ngio-0.5.0.dist-info}/METADATA +13 -6
- ngio-0.5.0.dist-info/RECORD +88 -0
- ngio/images/_create.py +0 -276
- ngio/tables/backends/_non_zarr_backends.py +0 -196
- ngio/utils/_logger.py +0 -50
- ngio-0.4.7.dist-info/RECORD +0 -85
- {ngio-0.4.7.dist-info → ngio-0.5.0.dist-info}/WHEEL +0 -0
- {ngio-0.4.7.dist-info → ngio-0.5.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,32 +1,31 @@
|
|
|
1
1
|
"""HCS (High Content Screening) specific metadata classes for NGIO."""
|
|
2
2
|
|
|
3
|
+
import warnings
|
|
3
4
|
from typing import Annotated
|
|
4
5
|
|
|
5
|
-
from ome_zarr_models.
|
|
6
|
-
from ome_zarr_models.v04.plate import (
|
|
6
|
+
from ome_zarr_models.common.plate import (
|
|
7
7
|
Acquisition,
|
|
8
8
|
Column,
|
|
9
|
-
|
|
9
|
+
PlateBase,
|
|
10
10
|
Row,
|
|
11
11
|
WellInPlate,
|
|
12
12
|
)
|
|
13
|
-
from ome_zarr_models.
|
|
14
|
-
from ome_zarr_models.v04.well_types import WellImage as WellImage04
|
|
15
|
-
from ome_zarr_models.v04.well_types import WellMeta as WellMeta04
|
|
13
|
+
from ome_zarr_models.common.well_types import WellImage as WellImageCommon
|
|
16
14
|
from pydantic import BaseModel, SkipValidation, field_serializer
|
|
17
15
|
|
|
18
16
|
from ngio.ome_zarr_meta.ngio_specs._ngio_image import DefaultNgffVersion, NgffVersions
|
|
19
|
-
from ngio.utils import NgioValueError
|
|
17
|
+
from ngio.utils import NgioValueError
|
|
20
18
|
|
|
21
19
|
|
|
22
20
|
def path_in_well_validation(path: str) -> str:
|
|
23
21
|
"""Validate the path in the well."""
|
|
24
22
|
# Check if the value contains only alphanumeric characters
|
|
25
23
|
if not path.isalnum():
|
|
26
|
-
|
|
24
|
+
warnings.warn(
|
|
27
25
|
f"Path '{path}' contains non-alphanumeric characters. "
|
|
28
26
|
"This may cause issues with some tools. "
|
|
29
|
-
"Consider using only alphanumeric characters in the path."
|
|
27
|
+
"Consider using only alphanumeric characters in the path.",
|
|
28
|
+
stacklevel=2,
|
|
30
29
|
)
|
|
31
30
|
return path
|
|
32
31
|
|
|
@@ -41,7 +40,7 @@ class ImageInWellPath(BaseModel):
|
|
|
41
40
|
acquisition_name: str | None = None
|
|
42
41
|
|
|
43
42
|
|
|
44
|
-
class CustomWellImage(
|
|
43
|
+
class CustomWellImage(WellImageCommon):
|
|
45
44
|
path: Annotated[str, SkipValidation]
|
|
46
45
|
|
|
47
46
|
@field_serializer("path")
|
|
@@ -50,32 +49,29 @@ class CustomWellImage(WellImage04):
|
|
|
50
49
|
return path_in_well_validation(value)
|
|
51
50
|
|
|
52
51
|
|
|
53
|
-
class
|
|
54
|
-
|
|
55
|
-
|
|
52
|
+
class PlateWithVersion(PlateBase):
|
|
53
|
+
version: NgffVersions
|
|
56
54
|
|
|
57
|
-
class CustomWellAttrs(WellAttrs04):
|
|
58
|
-
well: CustomWellMeta # type: ignore (override of WellAttrs04.well)
|
|
59
55
|
|
|
60
|
-
|
|
61
|
-
class NgioWellMeta(CustomWellAttrs):
|
|
56
|
+
class NgioWellMeta(BaseModel):
|
|
62
57
|
"""HCS well metadata."""
|
|
63
58
|
|
|
59
|
+
images: list[CustomWellImage] # type: ignore (override of WellMeta04.images)
|
|
60
|
+
version: NgffVersions
|
|
61
|
+
|
|
64
62
|
@classmethod
|
|
65
63
|
def default_init(
|
|
66
64
|
cls,
|
|
67
|
-
|
|
65
|
+
ngff_version: NgffVersions = DefaultNgffVersion,
|
|
68
66
|
) -> "NgioWellMeta":
|
|
69
|
-
|
|
70
|
-
version = DefaultNgffVersion
|
|
71
|
-
well = cls(well=CustomWellMeta(images=[], version=version))
|
|
67
|
+
well = cls(images=[], version=ngff_version)
|
|
72
68
|
return well
|
|
73
69
|
|
|
74
70
|
@property
|
|
75
71
|
def acquisition_ids(self) -> list[int]:
|
|
76
72
|
"""Return the acquisition ids in the well."""
|
|
77
73
|
acquisitions = []
|
|
78
|
-
for images in self.
|
|
74
|
+
for images in self.images:
|
|
79
75
|
if (
|
|
80
76
|
images.acquisition is not None
|
|
81
77
|
and images.acquisition not in acquisitions
|
|
@@ -85,7 +81,7 @@ class NgioWellMeta(CustomWellAttrs):
|
|
|
85
81
|
|
|
86
82
|
def get_image_acquisition_id(self, image_path: str) -> int | None:
|
|
87
83
|
"""Return the acquisition id for the given image path."""
|
|
88
|
-
for images in self.
|
|
84
|
+
for images in self.images:
|
|
89
85
|
if images.path == image_path:
|
|
90
86
|
return images.acquisition
|
|
91
87
|
raise NgioValueError(f"Image at path {image_path} not found in the well.")
|
|
@@ -100,11 +96,9 @@ class NgioWellMeta(CustomWellAttrs):
|
|
|
100
96
|
acquisition (int | None): The acquisition id to filter the images.
|
|
101
97
|
"""
|
|
102
98
|
if acquisition is None:
|
|
103
|
-
return [images.path for images in self.
|
|
99
|
+
return [images.path for images in self.images]
|
|
104
100
|
return [
|
|
105
|
-
images.path
|
|
106
|
-
for images in self.well.images
|
|
107
|
-
if images.acquisition == acquisition
|
|
101
|
+
images.path for images in self.images if images.acquisition == acquisition
|
|
108
102
|
]
|
|
109
103
|
|
|
110
104
|
def add_image(
|
|
@@ -118,7 +112,7 @@ class NgioWellMeta(CustomWellAttrs):
|
|
|
118
112
|
strict (bool): If True, check if the image already exists in the well.
|
|
119
113
|
If False, do not check if the image already exists in the well.
|
|
120
114
|
"""
|
|
121
|
-
list_of_images = self.
|
|
115
|
+
list_of_images = self.images
|
|
122
116
|
for image in list_of_images:
|
|
123
117
|
if image.path == path:
|
|
124
118
|
raise NgioValueError(
|
|
@@ -137,9 +131,7 @@ class NgioWellMeta(CustomWellAttrs):
|
|
|
137
131
|
|
|
138
132
|
new_image = CustomWellImage(path=path, acquisition=acquisition)
|
|
139
133
|
list_of_images.append(new_image)
|
|
140
|
-
return NgioWellMeta(
|
|
141
|
-
well=CustomWellMeta(images=list_of_images, version=self.well.version)
|
|
142
|
-
)
|
|
134
|
+
return NgioWellMeta(images=list_of_images, version=self.version)
|
|
143
135
|
|
|
144
136
|
def remove_image(self, path: str) -> "NgioWellMeta":
|
|
145
137
|
"""Remove an image from the well.
|
|
@@ -147,35 +139,66 @@ class NgioWellMeta(CustomWellAttrs):
|
|
|
147
139
|
Args:
|
|
148
140
|
path (str): The path of the image.
|
|
149
141
|
"""
|
|
150
|
-
list_of_images = self.
|
|
142
|
+
list_of_images = self.images
|
|
151
143
|
for image in list_of_images:
|
|
152
144
|
if image.path == path:
|
|
153
145
|
list_of_images.remove(image)
|
|
154
|
-
return NgioWellMeta(
|
|
155
|
-
well=CustomWellMeta(
|
|
156
|
-
images=list_of_images, version=self.well.version
|
|
157
|
-
)
|
|
158
|
-
)
|
|
146
|
+
return NgioWellMeta(images=list_of_images, version=self.version)
|
|
159
147
|
raise NgioValueError(f"Image at path {path} not found in the well.")
|
|
160
148
|
|
|
161
149
|
|
|
162
|
-
def
|
|
150
|
+
def _format_int_column(column: int, num_digits: int = 2) -> str:
|
|
151
|
+
"""Format the column as a string.
|
|
152
|
+
|
|
153
|
+
We make sure to pad the column with zeros
|
|
154
|
+
to have a consistent format.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
column (int): The column to format.
|
|
158
|
+
num_digits (int): The number of digits to pad the column.
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
str: The column as a string.
|
|
162
|
+
"""
|
|
163
|
+
return f"{column:0{num_digits}d}"
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _stringify_column(column: str | int, num_digits: int = 2) -> str:
|
|
163
167
|
"""Convert the column to a string.
|
|
164
168
|
|
|
169
|
+
This will ensure that columns are always strings.
|
|
170
|
+
and will help with sorting and comparison.
|
|
171
|
+
|
|
165
172
|
Args:
|
|
166
173
|
column (str | int): The column to convert.
|
|
174
|
+
num_digits (int): The number of digits to pad the column.
|
|
167
175
|
|
|
168
176
|
Returns:
|
|
169
177
|
str: The column as a string.
|
|
170
178
|
"""
|
|
171
|
-
if isinstance(column,
|
|
172
|
-
return column
|
|
179
|
+
if isinstance(column, int):
|
|
180
|
+
return _format_int_column(column, num_digits=num_digits)
|
|
173
181
|
|
|
174
|
-
|
|
175
|
-
|
|
182
|
+
elif isinstance(column, str):
|
|
183
|
+
try:
|
|
184
|
+
column_int = int(column)
|
|
185
|
+
return _format_int_column(column_int, num_digits=num_digits)
|
|
186
|
+
except ValueError:
|
|
187
|
+
pass
|
|
188
|
+
return column
|
|
189
|
+
raise NgioValueError(f"Column {column} must be a string or an integer.")
|
|
176
190
|
|
|
177
191
|
|
|
178
192
|
def _find_row_index(rows: list[str], row: str) -> int | None:
|
|
193
|
+
"""Find the index of a row in the list of rows.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
rows: List of row names.
|
|
197
|
+
row: The row name to find.
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
The index of the row, or None if not found.
|
|
201
|
+
"""
|
|
179
202
|
try:
|
|
180
203
|
return rows.index(row)
|
|
181
204
|
except ValueError:
|
|
@@ -183,8 +206,17 @@ def _find_row_index(rows: list[str], row: str) -> int | None:
|
|
|
183
206
|
|
|
184
207
|
|
|
185
208
|
def _find_column_index(columns: list[str], column: str | int) -> int | None:
|
|
186
|
-
|
|
187
|
-
|
|
209
|
+
"""Find the index of a column in the list of columns.
|
|
210
|
+
|
|
211
|
+
Args:
|
|
212
|
+
columns: List of column names.
|
|
213
|
+
column: The column name or number to find.
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
The index of the column, or None if not found.
|
|
217
|
+
"""
|
|
218
|
+
_num_columns = [_stringify_column(columns) for columns in columns]
|
|
219
|
+
column = _stringify_column(column)
|
|
188
220
|
try:
|
|
189
221
|
return _num_columns.index(column)
|
|
190
222
|
except ValueError:
|
|
@@ -194,6 +226,16 @@ def _find_column_index(columns: list[str], column: str | int) -> int | None:
|
|
|
194
226
|
def _relabel_wells(
|
|
195
227
|
wells: list[WellInPlate], rows: list[Row], columns: list[Column]
|
|
196
228
|
) -> list[WellInPlate]:
|
|
229
|
+
"""Relabel well indices after rows or columns have been added.
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
wells: List of wells to relabel.
|
|
233
|
+
rows: Updated list of rows.
|
|
234
|
+
columns: Updated list of columns.
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
List of wells with updated row and column indices.
|
|
238
|
+
"""
|
|
197
239
|
new_wells = []
|
|
198
240
|
_rows = [row.name for row in rows]
|
|
199
241
|
_columns = [column.name for column in columns]
|
|
@@ -218,26 +260,30 @@ def _relabel_wells(
|
|
|
218
260
|
return new_wells
|
|
219
261
|
|
|
220
262
|
|
|
221
|
-
class NgioPlateMeta(
|
|
263
|
+
class NgioPlateMeta(BaseModel):
|
|
222
264
|
"""HCS plate metadata."""
|
|
223
265
|
|
|
266
|
+
plate: PlateWithVersion
|
|
267
|
+
version: NgffVersions
|
|
268
|
+
|
|
224
269
|
@classmethod
|
|
225
270
|
def default_init(
|
|
226
271
|
cls,
|
|
227
272
|
images: list[ImageInWellPath] | None = None,
|
|
228
273
|
name: str | None = None,
|
|
229
|
-
|
|
274
|
+
ngff_version: NgffVersions = DefaultNgffVersion,
|
|
230
275
|
) -> "NgioPlateMeta":
|
|
231
276
|
plate = cls(
|
|
232
|
-
plate=
|
|
277
|
+
plate=PlateWithVersion(
|
|
233
278
|
rows=[],
|
|
234
279
|
columns=[],
|
|
235
280
|
acquisitions=None,
|
|
236
281
|
wells=[],
|
|
237
282
|
field_count=None,
|
|
238
|
-
version=version,
|
|
239
283
|
name=name,
|
|
240
|
-
|
|
284
|
+
version=ngff_version,
|
|
285
|
+
),
|
|
286
|
+
version=ngff_version,
|
|
241
287
|
)
|
|
242
288
|
|
|
243
289
|
if images is None:
|
|
@@ -294,23 +340,8 @@ class NgioPlateMeta(HCSAttrs):
|
|
|
294
340
|
Returns:
|
|
295
341
|
str: The path of the well.
|
|
296
342
|
"""
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
f"Row {row} not found in the plate. Available rows are {self.rows}."
|
|
300
|
-
)
|
|
301
|
-
|
|
302
|
-
row_idx = self.rows.index(row)
|
|
303
|
-
|
|
304
|
-
_num_columns = [int(columns) for columns in self.columns]
|
|
305
|
-
|
|
306
|
-
try:
|
|
307
|
-
_column = int(column)
|
|
308
|
-
except ValueError:
|
|
309
|
-
raise NgioValueError(
|
|
310
|
-
f"Column {column} must be an integer or convertible to an integer."
|
|
311
|
-
) from None
|
|
312
|
-
|
|
313
|
-
column_idx = _num_columns.index(_column)
|
|
343
|
+
row_idx = _find_row_index(self.rows, row)
|
|
344
|
+
column_idx = _find_column_index(self.columns, column)
|
|
314
345
|
|
|
315
346
|
for well in self.plate.wells:
|
|
316
347
|
if well.columnIndex == column_idx and well.rowIndex == row_idx:
|
|
@@ -346,16 +377,16 @@ class NgioPlateMeta(HCSAttrs):
|
|
|
346
377
|
else:
|
|
347
378
|
wells = self.plate.wells
|
|
348
379
|
|
|
349
|
-
new_plate =
|
|
380
|
+
new_plate = PlateWithVersion(
|
|
350
381
|
rows=rows,
|
|
351
382
|
columns=self.plate.columns,
|
|
352
383
|
acquisitions=self.plate.acquisitions,
|
|
353
384
|
wells=wells,
|
|
354
385
|
field_count=self.plate.field_count,
|
|
355
386
|
name=self.plate.name,
|
|
356
|
-
version=self.
|
|
387
|
+
version=self.version,
|
|
357
388
|
)
|
|
358
|
-
return NgioPlateMeta(plate=new_plate), row_idx
|
|
389
|
+
return NgioPlateMeta(plate=new_plate, version=self.version), row_idx
|
|
359
390
|
|
|
360
391
|
def add_column(self, column: str | int) -> "tuple[NgioPlateMeta, int]":
|
|
361
392
|
"""Add a column to the plate.
|
|
@@ -373,7 +404,7 @@ class NgioPlateMeta(HCSAttrs):
|
|
|
373
404
|
|
|
374
405
|
columns_names.append(_stringify_column(column))
|
|
375
406
|
# sort as numbers
|
|
376
|
-
columns_names.sort(key=lambda x:
|
|
407
|
+
columns_names.sort(key=lambda x: _stringify_column(x))
|
|
377
408
|
column_idx = columns_names.index(_stringify_column(column))
|
|
378
409
|
relabel_wells = True
|
|
379
410
|
|
|
@@ -384,27 +415,30 @@ class NgioPlateMeta(HCSAttrs):
|
|
|
384
415
|
else:
|
|
385
416
|
wells = self.plate.wells
|
|
386
417
|
|
|
387
|
-
new_plate =
|
|
418
|
+
new_plate = PlateWithVersion(
|
|
388
419
|
rows=self.plate.rows,
|
|
389
420
|
columns=columns,
|
|
390
421
|
acquisitions=self.plate.acquisitions,
|
|
391
422
|
wells=wells,
|
|
392
423
|
field_count=self.plate.field_count,
|
|
393
424
|
name=self.plate.name,
|
|
394
|
-
version=self.
|
|
425
|
+
version=self.version,
|
|
395
426
|
)
|
|
396
|
-
return NgioPlateMeta(plate=new_plate), column_idx
|
|
427
|
+
return NgioPlateMeta(plate=new_plate, version=self.version), column_idx
|
|
397
428
|
|
|
398
429
|
def add_well(
|
|
399
430
|
self,
|
|
400
431
|
row: str,
|
|
401
432
|
column: str | int,
|
|
402
433
|
) -> "NgioPlateMeta":
|
|
403
|
-
"""Add
|
|
434
|
+
"""Add a well to the plate.
|
|
404
435
|
|
|
405
436
|
Args:
|
|
406
437
|
row (str): The row of the well.
|
|
407
438
|
column (str | int): The column of the well.
|
|
439
|
+
|
|
440
|
+
Returns:
|
|
441
|
+
NgioPlateMeta: Updated plate metadata with the new well.
|
|
408
442
|
"""
|
|
409
443
|
plate, row_idx = self.add_row(row=row)
|
|
410
444
|
plate, column_idx = plate.add_column(column=column)
|
|
@@ -422,16 +456,16 @@ class NgioPlateMeta(HCSAttrs):
|
|
|
422
456
|
)
|
|
423
457
|
)
|
|
424
458
|
|
|
425
|
-
new_plate =
|
|
459
|
+
new_plate = PlateWithVersion(
|
|
426
460
|
rows=plate.plate.rows,
|
|
427
461
|
columns=plate.plate.columns,
|
|
428
462
|
acquisitions=plate.plate.acquisitions,
|
|
429
463
|
wells=wells,
|
|
430
464
|
field_count=plate.plate.field_count,
|
|
431
465
|
name=plate.plate.name,
|
|
432
|
-
version=plate.
|
|
466
|
+
version=plate.version,
|
|
433
467
|
)
|
|
434
|
-
return NgioPlateMeta(plate=new_plate)
|
|
468
|
+
return NgioPlateMeta(plate=new_plate, version=plate.version)
|
|
435
469
|
|
|
436
470
|
def add_acquisition(
|
|
437
471
|
self,
|
|
@@ -461,16 +495,16 @@ class NgioPlateMeta(HCSAttrs):
|
|
|
461
495
|
Acquisition(id=acquisition_id, name=acquisition_name, **acquisition_kwargs)
|
|
462
496
|
)
|
|
463
497
|
|
|
464
|
-
new_plate =
|
|
498
|
+
new_plate = PlateWithVersion(
|
|
465
499
|
rows=self.plate.rows,
|
|
466
500
|
columns=self.plate.columns,
|
|
467
501
|
acquisitions=acquisitions,
|
|
468
502
|
wells=self.plate.wells,
|
|
469
503
|
field_count=self.plate.field_count,
|
|
470
504
|
name=self.plate.name,
|
|
471
|
-
version=self.
|
|
505
|
+
version=self.version,
|
|
472
506
|
)
|
|
473
|
-
return NgioPlateMeta(plate=new_plate)
|
|
507
|
+
return NgioPlateMeta(plate=new_plate, version=self.version)
|
|
474
508
|
|
|
475
509
|
def remove_well(self, row: str, column: str | int) -> "NgioPlateMeta":
|
|
476
510
|
"""Remove a well from the plate.
|
|
@@ -497,30 +531,33 @@ class NgioPlateMeta(HCSAttrs):
|
|
|
497
531
|
f"Well at row {row} and column {column} not found in the plate."
|
|
498
532
|
)
|
|
499
533
|
|
|
500
|
-
new_plate =
|
|
534
|
+
new_plate = PlateWithVersion(
|
|
501
535
|
rows=self.plate.rows,
|
|
502
536
|
columns=self.plate.columns,
|
|
503
537
|
acquisitions=self.plate.acquisitions,
|
|
504
538
|
wells=wells,
|
|
505
539
|
field_count=self.plate.field_count,
|
|
506
540
|
name=self.plate.name,
|
|
507
|
-
version=self.
|
|
541
|
+
version=self.version,
|
|
508
542
|
)
|
|
509
|
-
return NgioPlateMeta(plate=new_plate)
|
|
543
|
+
return NgioPlateMeta(plate=new_plate, version=self.version)
|
|
510
544
|
|
|
511
545
|
def derive(
|
|
512
546
|
self,
|
|
513
547
|
name: str | None = None,
|
|
514
|
-
|
|
548
|
+
ngff_version: NgffVersions | None = None,
|
|
515
549
|
keep_acquisitions: bool = False,
|
|
516
550
|
) -> "NgioPlateMeta":
|
|
517
551
|
"""Derive the plate metadata.
|
|
518
552
|
|
|
519
553
|
Args:
|
|
520
|
-
name (str): The name of the derived plate.
|
|
521
|
-
|
|
554
|
+
name (str | None): The name of the derived plate.
|
|
555
|
+
ngff_version (NgffVersions | None): The version of the derived plate.
|
|
522
556
|
If None, use the version of the original plate.
|
|
523
557
|
keep_acquisitions (bool): If True, keep the acquisitions in the plate.
|
|
558
|
+
|
|
559
|
+
Returns:
|
|
560
|
+
NgioPlateMeta: The derived plate metadata.
|
|
524
561
|
"""
|
|
525
562
|
columns = self.plate.columns
|
|
526
563
|
rows = self.plate.rows
|
|
@@ -530,17 +567,18 @@ class NgioPlateMeta(HCSAttrs):
|
|
|
530
567
|
else:
|
|
531
568
|
acquisitions = None
|
|
532
569
|
|
|
533
|
-
if
|
|
534
|
-
|
|
570
|
+
if ngff_version is None:
|
|
571
|
+
ngff_version = self.version
|
|
535
572
|
|
|
536
573
|
return NgioPlateMeta(
|
|
537
|
-
plate=
|
|
574
|
+
plate=PlateWithVersion(
|
|
538
575
|
rows=rows,
|
|
539
576
|
columns=columns,
|
|
540
577
|
acquisitions=acquisitions,
|
|
541
578
|
wells=[],
|
|
542
579
|
field_count=self.plate.field_count,
|
|
543
|
-
version=version,
|
|
544
580
|
name=name,
|
|
545
|
-
|
|
581
|
+
version=ngff_version,
|
|
582
|
+
),
|
|
583
|
+
version=ngff_version,
|
|
546
584
|
)
|
|
@@ -13,11 +13,11 @@ import numpy as np
|
|
|
13
13
|
from pydantic import BaseModel
|
|
14
14
|
|
|
15
15
|
from ngio.ome_zarr_meta.ngio_specs._axes import (
|
|
16
|
+
AxesHandler,
|
|
16
17
|
DefaultSpaceUnit,
|
|
17
18
|
DefaultTimeUnit,
|
|
18
19
|
SpaceUnits,
|
|
19
20
|
TimeUnits,
|
|
20
|
-
build_canonical_axes_handler,
|
|
21
21
|
)
|
|
22
22
|
from ngio.ome_zarr_meta.ngio_specs._channels import ChannelsMeta
|
|
23
23
|
from ngio.ome_zarr_meta.ngio_specs._dataset import Dataset
|
|
@@ -25,7 +25,7 @@ from ngio.ome_zarr_meta.ngio_specs._pixel_size import PixelSize
|
|
|
25
25
|
from ngio.utils import NgioValidationError, NgioValueError
|
|
26
26
|
|
|
27
27
|
T = TypeVar("T")
|
|
28
|
-
NgffVersions = Literal["0.4"]
|
|
28
|
+
NgffVersions = Literal["0.4", "0.5"]
|
|
29
29
|
DefaultNgffVersion: Literal["0.4"] = "0.4"
|
|
30
30
|
|
|
31
31
|
|
|
@@ -41,6 +41,13 @@ class ImageLabelSource(BaseModel):
|
|
|
41
41
|
return cls(version=version, source={"image": "../../"})
|
|
42
42
|
|
|
43
43
|
|
|
44
|
+
class NgioLabelsGroupMeta(BaseModel):
|
|
45
|
+
"""Metadata model for the /labels group in OME-NGFF."""
|
|
46
|
+
|
|
47
|
+
version: NgffVersions
|
|
48
|
+
labels: list[str]
|
|
49
|
+
|
|
50
|
+
|
|
44
51
|
class AbstractNgioImageMeta:
|
|
45
52
|
"""Base class for ImageMeta and LabelMeta."""
|
|
46
53
|
|
|
@@ -66,42 +73,23 @@ class AbstractNgioImageMeta:
|
|
|
66
73
|
@classmethod
|
|
67
74
|
def default_init(
|
|
68
75
|
cls,
|
|
69
|
-
levels:
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
76
|
+
levels: Sequence[str],
|
|
77
|
+
axes_handler: AxesHandler,
|
|
78
|
+
scales: Sequence[tuple[float, ...]],
|
|
79
|
+
translations: Sequence[tuple[float, ...] | None],
|
|
73
80
|
name: str | None = None,
|
|
74
81
|
version: NgffVersions = DefaultNgffVersion,
|
|
75
82
|
):
|
|
76
83
|
"""Initialize the ImageMeta object."""
|
|
77
|
-
axes_handler = build_canonical_axes_handler(
|
|
78
|
-
axes_names,
|
|
79
|
-
space_units=pixel_size.space_unit,
|
|
80
|
-
time_units=pixel_size.time_unit,
|
|
81
|
-
)
|
|
82
|
-
|
|
83
|
-
px_size_dict = pixel_size.as_dict()
|
|
84
|
-
scale = [px_size_dict.get(name, 1.0) for name in axes_handler.axes_names]
|
|
85
|
-
|
|
86
|
-
if scaling_factors is None:
|
|
87
|
-
_default = {"x": 2.0, "y": 2.0}
|
|
88
|
-
scaling_factors = [
|
|
89
|
-
_default.get(name, 1.0) for name in axes_handler.axes_names
|
|
90
|
-
]
|
|
91
|
-
|
|
92
|
-
if isinstance(levels, int):
|
|
93
|
-
levels = [str(i) for i in range(levels)]
|
|
94
|
-
|
|
95
84
|
datasets = []
|
|
96
|
-
for level in levels:
|
|
85
|
+
for level, scale, translation in zip(levels, scales, translations, strict=True):
|
|
97
86
|
dataset = Dataset(
|
|
98
87
|
path=level,
|
|
99
88
|
axes_handler=axes_handler,
|
|
100
89
|
scale=scale,
|
|
101
|
-
translation=
|
|
90
|
+
translation=translation,
|
|
102
91
|
)
|
|
103
92
|
datasets.append(dataset)
|
|
104
|
-
scale = [s * f for s, f in zip(scale, scaling_factors, strict=True)]
|
|
105
93
|
|
|
106
94
|
return cls(
|
|
107
95
|
version=version,
|
|
@@ -141,11 +129,58 @@ class AbstractNgioImageMeta:
|
|
|
141
129
|
datasets=new_datasets,
|
|
142
130
|
)
|
|
143
131
|
|
|
132
|
+
def rename_axes(self, axes_names: Sequence[str]):
|
|
133
|
+
"""Rename axes in the metadata.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
axes_names(Sequence[str]): The new axes names in the order of the current
|
|
137
|
+
axes.
|
|
138
|
+
"""
|
|
139
|
+
new_axes_handler = self.axes_handler.rename_axes(axes_names=axes_names)
|
|
140
|
+
new_datasets = []
|
|
141
|
+
for dataset in self.datasets:
|
|
142
|
+
new_dataset = Dataset(
|
|
143
|
+
path=dataset.path,
|
|
144
|
+
axes_handler=new_axes_handler,
|
|
145
|
+
scale=dataset.scale,
|
|
146
|
+
translation=dataset.translation,
|
|
147
|
+
)
|
|
148
|
+
new_datasets.append(new_dataset)
|
|
149
|
+
|
|
150
|
+
return type(self)(
|
|
151
|
+
version=self.version,
|
|
152
|
+
name=self.name,
|
|
153
|
+
datasets=new_datasets,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
def rename_image(self, name: str):
|
|
157
|
+
"""Rename the image in the metadata.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
name(str): The new name of the image.
|
|
161
|
+
"""
|
|
162
|
+
return type(self)(
|
|
163
|
+
version=self.version,
|
|
164
|
+
name=name,
|
|
165
|
+
datasets=self.datasets,
|
|
166
|
+
)
|
|
167
|
+
|
|
144
168
|
@property
|
|
145
169
|
def version(self) -> NgffVersions:
|
|
146
170
|
"""Version of the OME-NFF metadata used to build the object."""
|
|
147
171
|
return self._version # type: ignore (version is a Literal type)
|
|
148
172
|
|
|
173
|
+
@property
|
|
174
|
+
def zarr_format(self) -> Literal[2, 3]:
|
|
175
|
+
"""Zarr version used to store the data."""
|
|
176
|
+
match self.version:
|
|
177
|
+
case "0.4":
|
|
178
|
+
return 2
|
|
179
|
+
case "0.5":
|
|
180
|
+
return 3
|
|
181
|
+
case _:
|
|
182
|
+
raise NgioValueError(f"Unsupported NGFF version: {self.version}")
|
|
183
|
+
|
|
149
184
|
@property
|
|
150
185
|
def name(self) -> str | None:
|
|
151
186
|
"""Name of the image."""
|
|
@@ -326,50 +361,15 @@ class AbstractNgioImageMeta:
|
|
|
326
361
|
)
|
|
327
362
|
return dataset, lr_dataset
|
|
328
363
|
|
|
329
|
-
def scaling_factor(self, path: str | None = None) ->
|
|
330
|
-
"""Get the scaling factors
|
|
331
|
-
if self.levels == 1:
|
|
332
|
-
return [1.0] * len(self.axes_handler.axes_names)
|
|
333
|
-
dataset, lr_dataset = self._get_closest_datasets(path=path)
|
|
334
|
-
|
|
335
|
-
scaling_factors = []
|
|
336
|
-
for ax_name in self.axes_handler.axes_names:
|
|
337
|
-
s_d = dataset.get_scale(ax_name)
|
|
338
|
-
s_lr_d = lr_dataset.get_scale(ax_name)
|
|
339
|
-
scaling_factors.append(s_lr_d / s_d)
|
|
340
|
-
return scaling_factors
|
|
341
|
-
|
|
342
|
-
def yx_scaling(self, path: str | None = None) -> tuple[float, float]:
|
|
343
|
-
"""Get the scaling factor from a dataset to its lower resolution."""
|
|
364
|
+
def scaling_factor(self, path: str | None = None) -> tuple[float, ...]:
|
|
365
|
+
"""Get the scaling factors to downscale to the next lower resolution dataset."""
|
|
344
366
|
if self.levels == 1:
|
|
345
|
-
return 1.0,
|
|
367
|
+
return (1.0,) * len(self.axes_handler.axes_names)
|
|
346
368
|
dataset, lr_dataset = self._get_closest_datasets(path=path)
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
"This is the lowest resolution dataset."
|
|
352
|
-
)
|
|
353
|
-
|
|
354
|
-
s_d = dataset.get_scale("y")
|
|
355
|
-
s_lr_d = lr_dataset.get_scale("y")
|
|
356
|
-
scale_y = s_lr_d / s_d
|
|
357
|
-
|
|
358
|
-
s_d = dataset.get_scale("x")
|
|
359
|
-
s_lr_d = lr_dataset.get_scale("x")
|
|
360
|
-
scale_x = s_lr_d / s_d
|
|
361
|
-
|
|
362
|
-
return scale_y, scale_x
|
|
363
|
-
|
|
364
|
-
def z_scaling(self, path: str | None = None) -> float:
|
|
365
|
-
"""Get the scaling factor from a dataset to its lower resolution."""
|
|
366
|
-
if self.levels == 1:
|
|
367
|
-
return 1.0
|
|
368
|
-
dataset, lr_dataset = self._get_closest_datasets(path=path)
|
|
369
|
-
|
|
370
|
-
s_d = dataset.get_scale("z", default=1.0)
|
|
371
|
-
s_lr_d = lr_dataset.get_scale("z", default=1.0)
|
|
372
|
-
return s_lr_d / s_d
|
|
369
|
+
scale = dataset.scale
|
|
370
|
+
lr_scale = lr_dataset.scale
|
|
371
|
+
scaling_factors = [s / s_lr for s_lr, s in zip(scale, lr_scale, strict=True)]
|
|
372
|
+
return tuple(scaling_factors)
|
|
373
373
|
|
|
374
374
|
|
|
375
375
|
class NgioLabelMeta(AbstractNgioImageMeta):
|