ngio 0.2.0b3__py3-none-any.whl → 0.2.2__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 +30 -10
- ngio/common/__init__.py +3 -3
- ngio/common/_array_pipe.py +7 -1
- ngio/common/_masking_roi.py +4 -4
- ngio/common/_roi.py +14 -14
- ngio/hcs/__init__.py +16 -2
- ngio/hcs/plate.py +465 -54
- ngio/images/__init__.py +7 -7
- ngio/images/abstract_image.py +30 -10
- ngio/images/create.py +25 -35
- ngio/images/image.py +40 -8
- ngio/images/label.py +33 -8
- ngio/images/masked_image.py +19 -5
- ngio/images/{omezarr_container.py → ome_zarr_container.py} +84 -47
- ngio/ome_zarr_meta/__init__.py +5 -0
- ngio/ome_zarr_meta/ngio_specs/__init__.py +10 -0
- ngio/ome_zarr_meta/ngio_specs/_axes.py +90 -65
- ngio/ome_zarr_meta/ngio_specs/_dataset.py +46 -8
- ngio/ome_zarr_meta/ngio_specs/_ngio_hcs.py +242 -69
- ngio/ome_zarr_meta/ngio_specs/_ngio_image.py +53 -19
- ngio/ome_zarr_meta/ngio_specs/_pixel_size.py +28 -11
- ngio/ome_zarr_meta/v04/_custom_models.py +18 -0
- ngio/ome_zarr_meta/v04/_v04_spec_utils.py +3 -4
- ngio/tables/__init__.py +2 -2
- ngio/tables/tables_container.py +3 -3
- ngio/tables/v1/__init__.py +2 -2
- ngio/tables/v1/_feature_table.py +20 -1
- ngio/tables/v1/_generic_table.py +10 -0
- ngio/tables/v1/_roi_table.py +35 -13
- {ngio-0.2.0b3.dist-info → ngio-0.2.2.dist-info}/METADATA +8 -5
- ngio-0.2.2.dist-info/RECORD +55 -0
- ngio-0.2.0b3.dist-info/RECORD +0 -54
- {ngio-0.2.0b3.dist-info → ngio-0.2.2.dist-info}/WHEEL +0 -0
- {ngio-0.2.0b3.dist-info → ngio-0.2.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -6,6 +6,9 @@ from ngio.ome_zarr_meta.ngio_specs._axes import (
|
|
|
6
6
|
AxesMapper,
|
|
7
7
|
AxesSetup,
|
|
8
8
|
Axis,
|
|
9
|
+
AxisType,
|
|
10
|
+
DefaultSpaceUnit,
|
|
11
|
+
DefaultTimeUnit,
|
|
9
12
|
SpaceUnits,
|
|
10
13
|
TimeUnits,
|
|
11
14
|
)
|
|
@@ -86,7 +89,7 @@ class Dataset:
|
|
|
86
89
|
return self._path
|
|
87
90
|
|
|
88
91
|
@property
|
|
89
|
-
def space_unit(self) ->
|
|
92
|
+
def space_unit(self) -> str | None:
|
|
90
93
|
"""Return the space unit for a given axis."""
|
|
91
94
|
x_axis = self._axes_mapper.get_axis("x")
|
|
92
95
|
y_axis = self._axes_mapper.get_axis("y")
|
|
@@ -97,8 +100,6 @@ class Dataset:
|
|
|
97
100
|
)
|
|
98
101
|
|
|
99
102
|
if x_axis.unit == y_axis.unit:
|
|
100
|
-
if not isinstance(x_axis.unit, SpaceUnits):
|
|
101
|
-
raise NgioValidationError("The space unit must be of type SpaceUnits.")
|
|
102
103
|
return x_axis.unit
|
|
103
104
|
else:
|
|
104
105
|
raise NgioValidationError(
|
|
@@ -107,13 +108,11 @@ class Dataset:
|
|
|
107
108
|
)
|
|
108
109
|
|
|
109
110
|
@property
|
|
110
|
-
def time_unit(self) ->
|
|
111
|
+
def time_unit(self) -> str | None:
|
|
111
112
|
"""Return the time unit for a given axis."""
|
|
112
113
|
t_axis = self._axes_mapper.get_axis("t")
|
|
113
114
|
if t_axis is None:
|
|
114
115
|
return None
|
|
115
|
-
if not isinstance(t_axis.unit, TimeUnits):
|
|
116
|
-
raise NgioValidationError("The time unit must be of type TimeUnits.")
|
|
117
116
|
return t_axis.unit
|
|
118
117
|
|
|
119
118
|
@property
|
|
@@ -124,11 +123,50 @@ class Dataset:
|
|
|
124
123
|
y=self.get_scale("y"),
|
|
125
124
|
z=self.get_scale("z"),
|
|
126
125
|
t=self.get_scale("t"),
|
|
127
|
-
space_unit=self.space_unit,
|
|
128
|
-
time_unit=self.time_unit,
|
|
126
|
+
space_unit=self.space_unit, # type: ignore
|
|
127
|
+
time_unit=self.time_unit, # type: ignore
|
|
129
128
|
)
|
|
130
129
|
|
|
131
130
|
@property
|
|
132
131
|
def axes_mapper(self) -> AxesMapper:
|
|
133
132
|
"""Return the axes mapper object."""
|
|
134
133
|
return self._axes_mapper
|
|
134
|
+
|
|
135
|
+
def to_units(
|
|
136
|
+
self,
|
|
137
|
+
*,
|
|
138
|
+
space_unit: SpaceUnits = DefaultSpaceUnit,
|
|
139
|
+
time_unit: TimeUnits = DefaultTimeUnit,
|
|
140
|
+
) -> "Dataset":
|
|
141
|
+
"""Convert the pixel size to the given units.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
space_unit(str): The space unit to convert to.
|
|
145
|
+
time_unit(str): The time unit to convert to.
|
|
146
|
+
"""
|
|
147
|
+
new_axes = []
|
|
148
|
+
for ax in self.axes_mapper.on_disk_axes:
|
|
149
|
+
if ax.axis_type == AxisType.space:
|
|
150
|
+
new_ax = Axis(
|
|
151
|
+
on_disk_name=ax.on_disk_name,
|
|
152
|
+
axis_type=ax.axis_type,
|
|
153
|
+
unit=space_unit,
|
|
154
|
+
)
|
|
155
|
+
new_axes.append(new_ax)
|
|
156
|
+
elif ax.axis_type == AxisType.time:
|
|
157
|
+
new_ax = Axis(
|
|
158
|
+
on_disk_name=ax.on_disk_name, axis_type=ax.axis_type, unit=time_unit
|
|
159
|
+
)
|
|
160
|
+
new_axes.append(new_ax)
|
|
161
|
+
else:
|
|
162
|
+
new_axes.append(ax)
|
|
163
|
+
|
|
164
|
+
return Dataset(
|
|
165
|
+
path=self.path,
|
|
166
|
+
on_disk_axes=new_axes,
|
|
167
|
+
on_disk_scale=self._on_disk_scale,
|
|
168
|
+
on_disk_translation=self._on_disk_translation,
|
|
169
|
+
axes_setup=self.axes_mapper.axes_setup,
|
|
170
|
+
allow_non_canonical_axes=self.axes_mapper.allow_non_canonical_axes,
|
|
171
|
+
strict_canonical_order=self.axes_mapper.strict_canonical_order,
|
|
172
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""HCS (High Content Screening) specific metadata classes for NGIO."""
|
|
2
2
|
|
|
3
|
-
from typing import
|
|
3
|
+
from typing import Annotated
|
|
4
4
|
|
|
5
5
|
from ome_zarr_models.v04.hcs import HCSAttrs
|
|
6
6
|
from ome_zarr_models.v04.plate import (
|
|
@@ -10,11 +10,34 @@ from ome_zarr_models.v04.plate import (
|
|
|
10
10
|
Row,
|
|
11
11
|
WellInPlate,
|
|
12
12
|
)
|
|
13
|
-
from ome_zarr_models.v04.well import WellAttrs
|
|
14
|
-
from ome_zarr_models.v04.well_types import WellImage
|
|
15
|
-
from
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
from ome_zarr_models.v04.well import WellAttrs as WellAttrs04
|
|
14
|
+
from ome_zarr_models.v04.well_types import WellImage as WellImage04
|
|
15
|
+
from ome_zarr_models.v04.well_types import WellMeta as WellMeta04
|
|
16
|
+
from pydantic import BaseModel, SkipValidation, field_serializer
|
|
17
|
+
|
|
18
|
+
from ngio.ome_zarr_meta.ngio_specs._ngio_image import DefaultNgffVersion, NgffVersions
|
|
19
|
+
from ngio.utils import NgioValueError, ngio_logger
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def path_in_well_validation(path: str) -> str:
|
|
23
|
+
"""Validate the path in the well."""
|
|
24
|
+
if path.find("_") != -1:
|
|
25
|
+
# Remove underscores from the path
|
|
26
|
+
# This is a custom serialization step
|
|
27
|
+
old_value = path
|
|
28
|
+
path = path.replace("_", "")
|
|
29
|
+
ngio_logger.warning(
|
|
30
|
+
f"Underscores in well-paths are not allowed. "
|
|
31
|
+
f"Path '{old_value}' was changed to '{path}'"
|
|
32
|
+
f" to comply with the specification."
|
|
33
|
+
)
|
|
34
|
+
# Check if the value contains only alphanumeric characters
|
|
35
|
+
if not path.isalnum():
|
|
36
|
+
raise NgioValueError(
|
|
37
|
+
f"Path '{path}' contains non-alphanumeric characters. "
|
|
38
|
+
f"Only alphanumeric characters are allowed."
|
|
39
|
+
)
|
|
40
|
+
return path
|
|
18
41
|
|
|
19
42
|
|
|
20
43
|
class ImageInWellPath(BaseModel):
|
|
@@ -27,23 +50,55 @@ class ImageInWellPath(BaseModel):
|
|
|
27
50
|
acquisition_name: str | None = None
|
|
28
51
|
|
|
29
52
|
|
|
30
|
-
class
|
|
53
|
+
class CustomWellImage(WellImage04):
|
|
54
|
+
path: Annotated[str, SkipValidation]
|
|
55
|
+
|
|
56
|
+
@field_serializer("path")
|
|
57
|
+
def serialize_path(self, value: str) -> str:
|
|
58
|
+
"""Custom serialization for the path."""
|
|
59
|
+
return path_in_well_validation(value)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class CustomWellMeta(WellMeta04):
|
|
63
|
+
images: list[CustomWellImage] # type: ignore[valid-type]
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class CustomWellAttrs(WellAttrs04):
|
|
67
|
+
well: CustomWellMeta # type: ignore[valid-type]
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class NgioWellMeta(CustomWellAttrs):
|
|
31
71
|
"""HCS well metadata."""
|
|
32
72
|
|
|
33
73
|
@classmethod
|
|
34
74
|
def default_init(
|
|
35
75
|
cls,
|
|
36
|
-
|
|
37
|
-
version: Literal["0.4"] | None = None,
|
|
76
|
+
version: NgffVersions | None = None,
|
|
38
77
|
) -> "NgioWellMeta":
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
for image in images:
|
|
44
|
-
well = well.add_image(path=image.path, acquisition=image.acquisition_id)
|
|
78
|
+
if version is None:
|
|
79
|
+
version = DefaultNgffVersion
|
|
80
|
+
well = cls(well=CustomWellMeta(images=[], version=version))
|
|
45
81
|
return well
|
|
46
82
|
|
|
83
|
+
@property
|
|
84
|
+
def acquisition_ids(self) -> list[int]:
|
|
85
|
+
"""Return the acquisition ids in the well."""
|
|
86
|
+
acquisitions = []
|
|
87
|
+
for images in self.well.images:
|
|
88
|
+
if (
|
|
89
|
+
images.acquisition is not None
|
|
90
|
+
and images.acquisition not in acquisitions
|
|
91
|
+
):
|
|
92
|
+
acquisitions.append(images.acquisition)
|
|
93
|
+
return acquisitions
|
|
94
|
+
|
|
95
|
+
def get_image_acquisition_id(self, image_path: str) -> int | None:
|
|
96
|
+
"""Return the acquisition id for the given image path."""
|
|
97
|
+
for images in self.well.images:
|
|
98
|
+
if images.path == image_path:
|
|
99
|
+
return images.acquisition
|
|
100
|
+
raise NgioValueError(f"Image at path {image_path} not found in the well.")
|
|
101
|
+
|
|
47
102
|
def paths(self, acquisition: int | None = None) -> list[str]:
|
|
48
103
|
"""Return the images paths in the well.
|
|
49
104
|
|
|
@@ -61,12 +116,16 @@ class NgioWellMeta(WellAttrs):
|
|
|
61
116
|
if images.acquisition == acquisition
|
|
62
117
|
]
|
|
63
118
|
|
|
64
|
-
def add_image(
|
|
119
|
+
def add_image(
|
|
120
|
+
self, path: str, acquisition: int | None = None, strict: bool = True
|
|
121
|
+
) -> "NgioWellMeta":
|
|
65
122
|
"""Add an image to the well.
|
|
66
123
|
|
|
67
124
|
Args:
|
|
68
125
|
path (str): The path of the image.
|
|
69
126
|
acquisition (int | None): The acquisition id of the image.
|
|
127
|
+
strict (bool): If True, check if the image already exists in the well.
|
|
128
|
+
If False, do not check if the image already exists in the well.
|
|
70
129
|
"""
|
|
71
130
|
list_of_images = self.well.images
|
|
72
131
|
for image in list_of_images:
|
|
@@ -75,10 +134,20 @@ class NgioWellMeta(WellAttrs):
|
|
|
75
134
|
f"Image at path {path} already exists in the well."
|
|
76
135
|
)
|
|
77
136
|
|
|
78
|
-
|
|
137
|
+
if (
|
|
138
|
+
strict
|
|
139
|
+
and (acquisition is not None)
|
|
140
|
+
and (acquisition not in self.acquisition_ids)
|
|
141
|
+
):
|
|
142
|
+
raise NgioValueError(
|
|
143
|
+
f"Acquisition ID {acquisition} not found in well. "
|
|
144
|
+
"Please add it to the plate metadata first."
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
new_image = CustomWellImage(path=path, acquisition=acquisition)
|
|
79
148
|
list_of_images.append(new_image)
|
|
80
149
|
return NgioWellMeta(
|
|
81
|
-
well=
|
|
150
|
+
well=CustomWellMeta(images=list_of_images, version=self.well.version)
|
|
82
151
|
)
|
|
83
152
|
|
|
84
153
|
def remove_image(self, path: str) -> "NgioWellMeta":
|
|
@@ -92,7 +161,9 @@ class NgioWellMeta(WellAttrs):
|
|
|
92
161
|
if image.path == path:
|
|
93
162
|
list_of_images.remove(image)
|
|
94
163
|
return NgioWellMeta(
|
|
95
|
-
well=
|
|
164
|
+
well=CustomWellMeta(
|
|
165
|
+
images=list_of_images, version=self.well.version
|
|
166
|
+
)
|
|
96
167
|
)
|
|
97
168
|
raise NgioValueError(f"Image at path {path} not found in the well.")
|
|
98
169
|
|
|
@@ -164,7 +235,7 @@ class NgioPlateMeta(HCSAttrs):
|
|
|
164
235
|
cls,
|
|
165
236
|
images: list[ImageInWellPath] | None = None,
|
|
166
237
|
name: str | None = None,
|
|
167
|
-
version:
|
|
238
|
+
version: NgffVersions | None = None,
|
|
168
239
|
) -> "NgioPlateMeta":
|
|
169
240
|
plate = cls(
|
|
170
241
|
plate=Plate(
|
|
@@ -185,9 +256,12 @@ class NgioPlateMeta(HCSAttrs):
|
|
|
185
256
|
plate = plate.add_well(
|
|
186
257
|
row=image.row,
|
|
187
258
|
column=image.column,
|
|
188
|
-
acquisition_id=image.acquisition_id,
|
|
189
|
-
acquisition_name=image.acquisition_name,
|
|
190
259
|
)
|
|
260
|
+
if image.acquisition_id is not None:
|
|
261
|
+
plate = plate.add_acquisition(
|
|
262
|
+
acquisition_id=image.acquisition_id,
|
|
263
|
+
acquisition_name=image.acquisition_name,
|
|
264
|
+
)
|
|
191
265
|
return plate
|
|
192
266
|
|
|
193
267
|
@property
|
|
@@ -208,7 +282,7 @@ class NgioPlateMeta(HCSAttrs):
|
|
|
208
282
|
return [acquisitions.name for acquisitions in self.plate.acquisitions]
|
|
209
283
|
|
|
210
284
|
@property
|
|
211
|
-
def
|
|
285
|
+
def acquisition_ids(self) -> list[int]:
|
|
212
286
|
"""Return the acquisitions ids in the plate."""
|
|
213
287
|
if self.plate.acquisitions is None:
|
|
214
288
|
return []
|
|
@@ -255,50 +329,94 @@ class NgioPlateMeta(HCSAttrs):
|
|
|
255
329
|
f"Well at row {row} and column {column} not found in the plate."
|
|
256
330
|
)
|
|
257
331
|
|
|
258
|
-
def
|
|
259
|
-
|
|
260
|
-
row: str,
|
|
261
|
-
column: str | int,
|
|
262
|
-
acquisition_id: int | None = None,
|
|
263
|
-
acquisition_name: str | None = None,
|
|
264
|
-
**acquisition_kwargs,
|
|
265
|
-
) -> "NgioPlateMeta":
|
|
266
|
-
"""Add an image to the well.
|
|
332
|
+
def add_row(self, row: str) -> "tuple[NgioPlateMeta, int]":
|
|
333
|
+
"""Add a row to the plate.
|
|
267
334
|
|
|
268
335
|
Args:
|
|
269
|
-
row (str): The row
|
|
270
|
-
column (str | int): The column of the well.
|
|
271
|
-
acquisition_id (int | None): The acquisition id of the well.
|
|
272
|
-
acquisition_name (str | None): The acquisition name of the well.
|
|
273
|
-
**acquisition_kwargs: Additional acquisition metadata.
|
|
336
|
+
row (str): The row to add.
|
|
274
337
|
"""
|
|
275
338
|
relabel_wells = False
|
|
276
339
|
|
|
277
340
|
row_names = self.rows
|
|
278
341
|
row_idx = _find_row_index(row_names, row)
|
|
279
|
-
if row_idx is None:
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
342
|
+
if row_idx is not None:
|
|
343
|
+
# Nothing to do
|
|
344
|
+
return self, row_idx
|
|
345
|
+
|
|
346
|
+
row_names.append(row)
|
|
347
|
+
row_names.sort()
|
|
348
|
+
row_idx = row_names.index(row)
|
|
349
|
+
relabel_wells = True
|
|
284
350
|
|
|
285
351
|
rows = [Row(name=row) for row in row_names]
|
|
286
352
|
|
|
353
|
+
if relabel_wells:
|
|
354
|
+
wells = _relabel_wells(self.plate.wells, rows, self.plate.columns)
|
|
355
|
+
else:
|
|
356
|
+
wells = self.plate.wells
|
|
357
|
+
|
|
358
|
+
new_plate = Plate(
|
|
359
|
+
rows=rows,
|
|
360
|
+
columns=self.plate.columns,
|
|
361
|
+
acquisitions=self.plate.acquisitions,
|
|
362
|
+
wells=wells,
|
|
363
|
+
field_count=self.plate.field_count,
|
|
364
|
+
version=self.plate.version,
|
|
365
|
+
)
|
|
366
|
+
return NgioPlateMeta(plate=new_plate), row_idx
|
|
367
|
+
|
|
368
|
+
def add_column(self, column: str | int) -> "tuple[NgioPlateMeta, int]":
|
|
369
|
+
"""Add a column to the plate.
|
|
370
|
+
|
|
371
|
+
Args:
|
|
372
|
+
column (str | int): The column to add.
|
|
373
|
+
"""
|
|
374
|
+
relabel_wells = False
|
|
375
|
+
|
|
287
376
|
columns_names = self.columns
|
|
288
377
|
column_idx = _find_column_index(columns_names, column)
|
|
289
|
-
if column_idx is None:
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
378
|
+
if column_idx is not None:
|
|
379
|
+
# Nothing to do
|
|
380
|
+
return self, column_idx
|
|
381
|
+
|
|
382
|
+
columns_names.append(_stringify_column(column))
|
|
383
|
+
# sort as numbers
|
|
384
|
+
columns_names.sort(key=lambda x: int(x))
|
|
385
|
+
column_idx = columns_names.index(_stringify_column(column))
|
|
386
|
+
relabel_wells = True
|
|
295
387
|
|
|
296
388
|
columns = [Column(name=column) for column in columns_names]
|
|
297
389
|
|
|
298
|
-
wells = self.plate.wells
|
|
299
390
|
if relabel_wells:
|
|
300
|
-
wells = _relabel_wells(wells, rows, columns)
|
|
391
|
+
wells = _relabel_wells(self.plate.wells, self.plate.rows, columns)
|
|
392
|
+
else:
|
|
393
|
+
wells = self.plate.wells
|
|
301
394
|
|
|
395
|
+
new_plate = Plate(
|
|
396
|
+
rows=self.plate.rows,
|
|
397
|
+
columns=columns,
|
|
398
|
+
acquisitions=self.plate.acquisitions,
|
|
399
|
+
wells=wells,
|
|
400
|
+
field_count=self.plate.field_count,
|
|
401
|
+
version=self.plate.version,
|
|
402
|
+
)
|
|
403
|
+
return NgioPlateMeta(plate=new_plate), column_idx
|
|
404
|
+
|
|
405
|
+
def add_well(
|
|
406
|
+
self,
|
|
407
|
+
row: str,
|
|
408
|
+
column: str | int,
|
|
409
|
+
) -> "NgioPlateMeta":
|
|
410
|
+
"""Add an image to the well.
|
|
411
|
+
|
|
412
|
+
Args:
|
|
413
|
+
row (str): The row of the well.
|
|
414
|
+
column (str | int): The column of the well.
|
|
415
|
+
"""
|
|
416
|
+
plate, row_idx = self.add_row(row=row)
|
|
417
|
+
plate, column_idx = plate.add_column(column=column)
|
|
418
|
+
|
|
419
|
+
wells = plate.plate.wells
|
|
302
420
|
for well_obj in wells:
|
|
303
421
|
if well_obj.rowIndex == row_idx and well_obj.columnIndex == column_idx:
|
|
304
422
|
break
|
|
@@ -311,28 +429,49 @@ class NgioPlateMeta(HCSAttrs):
|
|
|
311
429
|
)
|
|
312
430
|
)
|
|
313
431
|
|
|
432
|
+
new_plate = Plate(
|
|
433
|
+
rows=plate.plate.rows,
|
|
434
|
+
columns=plate.plate.columns,
|
|
435
|
+
acquisitions=plate.plate.acquisitions,
|
|
436
|
+
wells=wells,
|
|
437
|
+
field_count=plate.plate.field_count,
|
|
438
|
+
version=plate.plate.version,
|
|
439
|
+
)
|
|
440
|
+
return NgioPlateMeta(plate=new_plate)
|
|
441
|
+
|
|
442
|
+
def add_acquisition(
|
|
443
|
+
self,
|
|
444
|
+
acquisition_id: int,
|
|
445
|
+
acquisition_name: str | None = None,
|
|
446
|
+
**acquisition_kwargs,
|
|
447
|
+
) -> "NgioPlateMeta":
|
|
448
|
+
"""Add an acquisition to the plate.
|
|
449
|
+
|
|
450
|
+
Args:
|
|
451
|
+
acquisition_id (int): The acquisition id of the well.
|
|
452
|
+
acquisition_name (str | None): The acquisition name of the well.
|
|
453
|
+
**acquisition_kwargs: Additional acquisition metadata.
|
|
454
|
+
"""
|
|
314
455
|
acquisitions = self.plate.acquisitions
|
|
315
|
-
if
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
)
|
|
329
|
-
)
|
|
456
|
+
if acquisitions is None:
|
|
457
|
+
acquisitions = []
|
|
458
|
+
|
|
459
|
+
for acquisition_obj in acquisitions:
|
|
460
|
+
if acquisition_obj.id == acquisition_id:
|
|
461
|
+
# If the acquisition already exists
|
|
462
|
+
# Nothing to do
|
|
463
|
+
# Maybe we should update the acquisition name and kwargs
|
|
464
|
+
return self
|
|
465
|
+
|
|
466
|
+
acquisitions.append(
|
|
467
|
+
Acquisition(id=acquisition_id, name=acquisition_name, **acquisition_kwargs)
|
|
468
|
+
)
|
|
330
469
|
|
|
331
470
|
new_plate = Plate(
|
|
332
|
-
rows=rows,
|
|
333
|
-
columns=columns,
|
|
471
|
+
rows=self.plate.rows,
|
|
472
|
+
columns=self.plate.columns,
|
|
334
473
|
acquisitions=acquisitions,
|
|
335
|
-
wells=wells,
|
|
474
|
+
wells=self.plate.wells,
|
|
336
475
|
field_count=self.plate.field_count,
|
|
337
476
|
version=self.plate.version,
|
|
338
477
|
)
|
|
@@ -373,5 +512,39 @@ class NgioPlateMeta(HCSAttrs):
|
|
|
373
512
|
)
|
|
374
513
|
return NgioPlateMeta(plate=new_plate)
|
|
375
514
|
|
|
515
|
+
def derive(
|
|
516
|
+
self,
|
|
517
|
+
name: str | None = None,
|
|
518
|
+
version: NgffVersions | None = None,
|
|
519
|
+
keep_acquisitions: bool = False,
|
|
520
|
+
) -> "NgioPlateMeta":
|
|
521
|
+
"""Derive the plate metadata.
|
|
522
|
+
|
|
523
|
+
Args:
|
|
524
|
+
name (str): The name of the derived plate.
|
|
525
|
+
version (NgffVersion | None): The version of the derived plate.
|
|
526
|
+
If None, use the version of the original plate.
|
|
527
|
+
keep_acquisitions (bool): If True, keep the acquisitions in the plate.
|
|
528
|
+
"""
|
|
529
|
+
columns = self.plate.columns
|
|
530
|
+
rows = self.plate.rows
|
|
531
|
+
|
|
532
|
+
if keep_acquisitions:
|
|
533
|
+
acquisitions = self.plate.acquisitions
|
|
534
|
+
else:
|
|
535
|
+
acquisitions = None
|
|
376
536
|
|
|
377
|
-
|
|
537
|
+
if version is None:
|
|
538
|
+
version = self.plate.version # type: ignore[assignment]
|
|
539
|
+
|
|
540
|
+
return NgioPlateMeta(
|
|
541
|
+
plate=Plate(
|
|
542
|
+
rows=rows,
|
|
543
|
+
columns=columns,
|
|
544
|
+
acquisitions=acquisitions,
|
|
545
|
+
wells=[],
|
|
546
|
+
field_count=self.plate.field_count,
|
|
547
|
+
version=version,
|
|
548
|
+
name=name,
|
|
549
|
+
)
|
|
550
|
+
)
|
|
@@ -7,13 +7,16 @@ can be converted to the OME standard.
|
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
9
|
from collections.abc import Collection
|
|
10
|
-
from
|
|
11
|
-
from typing import Any, TypeVar
|
|
10
|
+
from typing import Any, Literal, TypeVar
|
|
12
11
|
|
|
13
12
|
import numpy as np
|
|
14
13
|
from pydantic import BaseModel
|
|
15
14
|
|
|
16
15
|
from ngio.ome_zarr_meta.ngio_specs._axes import (
|
|
16
|
+
DefaultSpaceUnit,
|
|
17
|
+
DefaultTimeUnit,
|
|
18
|
+
SpaceUnits,
|
|
19
|
+
TimeUnits,
|
|
17
20
|
canonical_axes,
|
|
18
21
|
)
|
|
19
22
|
from ngio.ome_zarr_meta.ngio_specs._channels import Channel, ChannelsMeta
|
|
@@ -22,22 +25,18 @@ from ngio.ome_zarr_meta.ngio_specs._pixel_size import PixelSize
|
|
|
22
25
|
from ngio.utils import NgioValidationError, NgioValueError
|
|
23
26
|
|
|
24
27
|
T = TypeVar("T")
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
class NgffVersion(str, Enum):
|
|
28
|
-
"""Allowed NGFF versions."""
|
|
29
|
-
|
|
30
|
-
v04 = "0.4"
|
|
28
|
+
NgffVersions = Literal["0.4"]
|
|
29
|
+
DefaultNgffVersion: Literal["0.4"] = "0.4"
|
|
31
30
|
|
|
32
31
|
|
|
33
32
|
class ImageLabelSource(BaseModel):
|
|
34
33
|
"""Image label source model."""
|
|
35
34
|
|
|
36
|
-
version:
|
|
35
|
+
version: NgffVersions
|
|
37
36
|
source: dict[str, str | None]
|
|
38
37
|
|
|
39
38
|
@classmethod
|
|
40
|
-
def default_init(cls, version:
|
|
39
|
+
def default_init(cls, version: NgffVersions) -> "ImageLabelSource":
|
|
41
40
|
"""Initialize the ImageLabelSource object."""
|
|
42
41
|
return cls(version=version, source={"image": "../../"})
|
|
43
42
|
|
|
@@ -45,9 +44,11 @@ class ImageLabelSource(BaseModel):
|
|
|
45
44
|
class AbstractNgioImageMeta:
|
|
46
45
|
"""Base class for ImageMeta and LabelMeta."""
|
|
47
46
|
|
|
48
|
-
def __init__(
|
|
47
|
+
def __init__(
|
|
48
|
+
self, version: NgffVersions, name: str | None, datasets: list[Dataset]
|
|
49
|
+
) -> None:
|
|
49
50
|
"""Initialize the ImageMeta object."""
|
|
50
|
-
self._version =
|
|
51
|
+
self._version = version
|
|
51
52
|
self._name = name
|
|
52
53
|
|
|
53
54
|
if len(datasets) == 0:
|
|
@@ -70,13 +71,13 @@ class AbstractNgioImageMeta:
|
|
|
70
71
|
pixel_size: PixelSize,
|
|
71
72
|
scaling_factors: Collection[float] | None = None,
|
|
72
73
|
name: str | None = None,
|
|
73
|
-
version:
|
|
74
|
+
version: NgffVersions = DefaultNgffVersion,
|
|
74
75
|
):
|
|
75
76
|
"""Initialize the ImageMeta object."""
|
|
76
77
|
axes = canonical_axes(
|
|
77
78
|
axes_names,
|
|
78
|
-
space_units=pixel_size.space_unit,
|
|
79
|
-
time_units=pixel_size.time_unit,
|
|
79
|
+
space_units=pixel_size.space_unit, # type: ignore[arg-type]
|
|
80
|
+
time_units=pixel_size.time_unit, # type: ignore[arg-type]
|
|
80
81
|
)
|
|
81
82
|
|
|
82
83
|
px_size_dict = pixel_size.as_dict()
|
|
@@ -109,10 +110,33 @@ class AbstractNgioImageMeta:
|
|
|
109
110
|
datasets=datasets,
|
|
110
111
|
)
|
|
111
112
|
|
|
113
|
+
def to_units(
|
|
114
|
+
self,
|
|
115
|
+
*,
|
|
116
|
+
space_unit: SpaceUnits = DefaultSpaceUnit,
|
|
117
|
+
time_unit: TimeUnits = DefaultTimeUnit,
|
|
118
|
+
):
|
|
119
|
+
"""Convert the pixel size to the given units.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
space_unit(str): The space unit to convert to.
|
|
123
|
+
time_unit(str): The time unit to convert to.
|
|
124
|
+
"""
|
|
125
|
+
new_datasets = []
|
|
126
|
+
for dataset in self.datasets:
|
|
127
|
+
new_dataset = dataset.to_units(space_unit=space_unit, time_unit=time_unit)
|
|
128
|
+
new_datasets.append(new_dataset)
|
|
129
|
+
|
|
130
|
+
return type(self)(
|
|
131
|
+
version=self.version,
|
|
132
|
+
name=self.name,
|
|
133
|
+
datasets=new_datasets,
|
|
134
|
+
)
|
|
135
|
+
|
|
112
136
|
@property
|
|
113
|
-
def version(self) ->
|
|
137
|
+
def version(self) -> NgffVersions:
|
|
114
138
|
"""Version of the OME-NFF metadata used to build the object."""
|
|
115
|
-
return self._version
|
|
139
|
+
return self._version # type: ignore[return-value]
|
|
116
140
|
|
|
117
141
|
@property
|
|
118
142
|
def name(self) -> str | None:
|
|
@@ -139,6 +163,16 @@ class AbstractNgioImageMeta:
|
|
|
139
163
|
"""List of paths of the datasets."""
|
|
140
164
|
return [dataset.path for dataset in self.datasets]
|
|
141
165
|
|
|
166
|
+
@property
|
|
167
|
+
def space_unit(self) -> str | None:
|
|
168
|
+
"""Get the space unit of the pixel size."""
|
|
169
|
+
return self.datasets[0].pixel_size.space_unit
|
|
170
|
+
|
|
171
|
+
@property
|
|
172
|
+
def time_unit(self) -> str | None:
|
|
173
|
+
"""Get the time unit of the pixel size."""
|
|
174
|
+
return self.datasets[0].pixel_size.time_unit
|
|
175
|
+
|
|
142
176
|
def _get_dataset_by_path(self, path: str) -> Dataset:
|
|
143
177
|
"""Get a dataset by its path."""
|
|
144
178
|
for dataset in self.datasets:
|
|
@@ -335,7 +369,7 @@ class NgioLabelMeta(AbstractNgioImageMeta):
|
|
|
335
369
|
|
|
336
370
|
def __init__(
|
|
337
371
|
self,
|
|
338
|
-
version:
|
|
372
|
+
version: NgffVersions,
|
|
339
373
|
name: str | None,
|
|
340
374
|
datasets: list[Dataset],
|
|
341
375
|
image_label: ImageLabelSource | None = None,
|
|
@@ -374,7 +408,7 @@ class NgioImageMeta(AbstractNgioImageMeta):
|
|
|
374
408
|
|
|
375
409
|
def __init__(
|
|
376
410
|
self,
|
|
377
|
-
version:
|
|
411
|
+
version: NgffVersions,
|
|
378
412
|
name: str | None,
|
|
379
413
|
datasets: list[Dataset],
|
|
380
414
|
channels: ChannelsMeta | None = None,
|