ngio 0.2.1__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 +20 -2
- ngio/common/_roi.py +2 -2
- ngio/hcs/__init__.py +16 -2
- ngio/hcs/plate.py +381 -22
- ngio/images/abstract_image.py +10 -0
- ngio/images/create.py +25 -36
- ngio/images/image.py +38 -6
- ngio/images/label.py +23 -2
- ngio/images/ome_zarr_container.py +66 -26
- ngio/ome_zarr_meta/__init__.py +5 -3
- ngio/ome_zarr_meta/ngio_specs/__init__.py +10 -2
- 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 -70
- ngio/ome_zarr_meta/ngio_specs/_ngio_image.py +49 -11
- 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 +2 -2
- {ngio-0.2.1.dist-info → ngio-0.2.2.dist-info}/METADATA +1 -1
- {ngio-0.2.1.dist-info → ngio-0.2.2.dist-info}/RECORD +22 -21
- {ngio-0.2.1.dist-info → ngio-0.2.2.dist-info}/WHEEL +0 -0
- {ngio-0.2.1.dist-info → ngio-0.2.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"""HCS (High Content Screening) specific metadata classes for NGIO."""
|
|
2
2
|
|
|
3
|
+
from typing import Annotated
|
|
4
|
+
|
|
3
5
|
from ome_zarr_models.v04.hcs import HCSAttrs
|
|
4
6
|
from ome_zarr_models.v04.plate import (
|
|
5
7
|
Acquisition,
|
|
@@ -8,12 +10,34 @@ from ome_zarr_models.v04.plate import (
|
|
|
8
10
|
Row,
|
|
9
11
|
WellInPlate,
|
|
10
12
|
)
|
|
11
|
-
from ome_zarr_models.v04.well import WellAttrs
|
|
12
|
-
from ome_zarr_models.v04.well_types import WellImage
|
|
13
|
-
from
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
from ngio.
|
|
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
|
|
17
41
|
|
|
18
42
|
|
|
19
43
|
class ImageInWellPath(BaseModel):
|
|
@@ -26,25 +50,55 @@ class ImageInWellPath(BaseModel):
|
|
|
26
50
|
acquisition_name: str | None = None
|
|
27
51
|
|
|
28
52
|
|
|
29
|
-
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):
|
|
30
71
|
"""HCS well metadata."""
|
|
31
72
|
|
|
32
73
|
@classmethod
|
|
33
74
|
def default_init(
|
|
34
75
|
cls,
|
|
35
|
-
|
|
36
|
-
version: NgffVersion | None = None,
|
|
76
|
+
version: NgffVersions | None = None,
|
|
37
77
|
) -> "NgioWellMeta":
|
|
38
78
|
if version is None:
|
|
39
|
-
version =
|
|
40
|
-
well = cls(well=
|
|
41
|
-
if images is None:
|
|
42
|
-
return well
|
|
43
|
-
|
|
44
|
-
for image in images:
|
|
45
|
-
well = well.add_image(path=image.path, acquisition=image.acquisition_id)
|
|
79
|
+
version = DefaultNgffVersion
|
|
80
|
+
well = cls(well=CustomWellMeta(images=[], version=version))
|
|
46
81
|
return well
|
|
47
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
|
+
|
|
48
102
|
def paths(self, acquisition: int | None = None) -> list[str]:
|
|
49
103
|
"""Return the images paths in the well.
|
|
50
104
|
|
|
@@ -62,12 +116,16 @@ class NgioWellMeta(WellAttrs):
|
|
|
62
116
|
if images.acquisition == acquisition
|
|
63
117
|
]
|
|
64
118
|
|
|
65
|
-
def add_image(
|
|
119
|
+
def add_image(
|
|
120
|
+
self, path: str, acquisition: int | None = None, strict: bool = True
|
|
121
|
+
) -> "NgioWellMeta":
|
|
66
122
|
"""Add an image to the well.
|
|
67
123
|
|
|
68
124
|
Args:
|
|
69
125
|
path (str): The path of the image.
|
|
70
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.
|
|
71
129
|
"""
|
|
72
130
|
list_of_images = self.well.images
|
|
73
131
|
for image in list_of_images:
|
|
@@ -76,10 +134,20 @@ class NgioWellMeta(WellAttrs):
|
|
|
76
134
|
f"Image at path {path} already exists in the well."
|
|
77
135
|
)
|
|
78
136
|
|
|
79
|
-
|
|
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)
|
|
80
148
|
list_of_images.append(new_image)
|
|
81
149
|
return NgioWellMeta(
|
|
82
|
-
well=
|
|
150
|
+
well=CustomWellMeta(images=list_of_images, version=self.well.version)
|
|
83
151
|
)
|
|
84
152
|
|
|
85
153
|
def remove_image(self, path: str) -> "NgioWellMeta":
|
|
@@ -93,7 +161,9 @@ class NgioWellMeta(WellAttrs):
|
|
|
93
161
|
if image.path == path:
|
|
94
162
|
list_of_images.remove(image)
|
|
95
163
|
return NgioWellMeta(
|
|
96
|
-
well=
|
|
164
|
+
well=CustomWellMeta(
|
|
165
|
+
images=list_of_images, version=self.well.version
|
|
166
|
+
)
|
|
97
167
|
)
|
|
98
168
|
raise NgioValueError(f"Image at path {path} not found in the well.")
|
|
99
169
|
|
|
@@ -165,7 +235,7 @@ class NgioPlateMeta(HCSAttrs):
|
|
|
165
235
|
cls,
|
|
166
236
|
images: list[ImageInWellPath] | None = None,
|
|
167
237
|
name: str | None = None,
|
|
168
|
-
version:
|
|
238
|
+
version: NgffVersions | None = None,
|
|
169
239
|
) -> "NgioPlateMeta":
|
|
170
240
|
plate = cls(
|
|
171
241
|
plate=Plate(
|
|
@@ -186,9 +256,12 @@ class NgioPlateMeta(HCSAttrs):
|
|
|
186
256
|
plate = plate.add_well(
|
|
187
257
|
row=image.row,
|
|
188
258
|
column=image.column,
|
|
189
|
-
acquisition_id=image.acquisition_id,
|
|
190
|
-
acquisition_name=image.acquisition_name,
|
|
191
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
|
+
)
|
|
192
265
|
return plate
|
|
193
266
|
|
|
194
267
|
@property
|
|
@@ -209,7 +282,7 @@ class NgioPlateMeta(HCSAttrs):
|
|
|
209
282
|
return [acquisitions.name for acquisitions in self.plate.acquisitions]
|
|
210
283
|
|
|
211
284
|
@property
|
|
212
|
-
def
|
|
285
|
+
def acquisition_ids(self) -> list[int]:
|
|
213
286
|
"""Return the acquisitions ids in the plate."""
|
|
214
287
|
if self.plate.acquisitions is None:
|
|
215
288
|
return []
|
|
@@ -256,50 +329,94 @@ class NgioPlateMeta(HCSAttrs):
|
|
|
256
329
|
f"Well at row {row} and column {column} not found in the plate."
|
|
257
330
|
)
|
|
258
331
|
|
|
259
|
-
def
|
|
260
|
-
|
|
261
|
-
row: str,
|
|
262
|
-
column: str | int,
|
|
263
|
-
acquisition_id: int | None = None,
|
|
264
|
-
acquisition_name: str | None = None,
|
|
265
|
-
**acquisition_kwargs,
|
|
266
|
-
) -> "NgioPlateMeta":
|
|
267
|
-
"""Add an image to the well.
|
|
332
|
+
def add_row(self, row: str) -> "tuple[NgioPlateMeta, int]":
|
|
333
|
+
"""Add a row to the plate.
|
|
268
334
|
|
|
269
335
|
Args:
|
|
270
|
-
row (str): The row
|
|
271
|
-
column (str | int): The column of the well.
|
|
272
|
-
acquisition_id (int | None): The acquisition id of the well.
|
|
273
|
-
acquisition_name (str | None): The acquisition name of the well.
|
|
274
|
-
**acquisition_kwargs: Additional acquisition metadata.
|
|
336
|
+
row (str): The row to add.
|
|
275
337
|
"""
|
|
276
338
|
relabel_wells = False
|
|
277
339
|
|
|
278
340
|
row_names = self.rows
|
|
279
341
|
row_idx = _find_row_index(row_names, row)
|
|
280
|
-
if row_idx is None:
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
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
|
|
285
350
|
|
|
286
351
|
rows = [Row(name=row) for row in row_names]
|
|
287
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
|
+
|
|
288
376
|
columns_names = self.columns
|
|
289
377
|
column_idx = _find_column_index(columns_names, column)
|
|
290
|
-
if column_idx is None:
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
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
|
|
296
387
|
|
|
297
388
|
columns = [Column(name=column) for column in columns_names]
|
|
298
389
|
|
|
299
|
-
wells = self.plate.wells
|
|
300
390
|
if relabel_wells:
|
|
301
|
-
wells = _relabel_wells(wells, rows, columns)
|
|
391
|
+
wells = _relabel_wells(self.plate.wells, self.plate.rows, columns)
|
|
392
|
+
else:
|
|
393
|
+
wells = self.plate.wells
|
|
302
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
|
|
303
420
|
for well_obj in wells:
|
|
304
421
|
if well_obj.rowIndex == row_idx and well_obj.columnIndex == column_idx:
|
|
305
422
|
break
|
|
@@ -312,28 +429,49 @@ class NgioPlateMeta(HCSAttrs):
|
|
|
312
429
|
)
|
|
313
430
|
)
|
|
314
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
|
+
"""
|
|
315
455
|
acquisitions = self.plate.acquisitions
|
|
316
|
-
if
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
)
|
|
330
|
-
)
|
|
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
|
+
)
|
|
331
469
|
|
|
332
470
|
new_plate = Plate(
|
|
333
|
-
rows=rows,
|
|
334
|
-
columns=columns,
|
|
471
|
+
rows=self.plate.rows,
|
|
472
|
+
columns=self.plate.columns,
|
|
335
473
|
acquisitions=acquisitions,
|
|
336
|
-
wells=wells,
|
|
474
|
+
wells=self.plate.wells,
|
|
337
475
|
field_count=self.plate.field_count,
|
|
338
476
|
version=self.plate.version,
|
|
339
477
|
)
|
|
@@ -374,5 +512,39 @@ class NgioPlateMeta(HCSAttrs):
|
|
|
374
512
|
)
|
|
375
513
|
return NgioPlateMeta(plate=new_plate)
|
|
376
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
|
|
536
|
+
|
|
537
|
+
if version is None:
|
|
538
|
+
version = self.plate.version # type: ignore[assignment]
|
|
377
539
|
|
|
378
|
-
|
|
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
|
+
)
|
|
@@ -13,6 +13,10 @@ import numpy as np
|
|
|
13
13
|
from pydantic import BaseModel
|
|
14
14
|
|
|
15
15
|
from ngio.ome_zarr_meta.ngio_specs._axes import (
|
|
16
|
+
DefaultSpaceUnit,
|
|
17
|
+
DefaultTimeUnit,
|
|
18
|
+
SpaceUnits,
|
|
19
|
+
TimeUnits,
|
|
16
20
|
canonical_axes,
|
|
17
21
|
)
|
|
18
22
|
from ngio.ome_zarr_meta.ngio_specs._channels import Channel, ChannelsMeta
|
|
@@ -21,17 +25,18 @@ from ngio.ome_zarr_meta.ngio_specs._pixel_size import PixelSize
|
|
|
21
25
|
from ngio.utils import NgioValidationError, NgioValueError
|
|
22
26
|
|
|
23
27
|
T = TypeVar("T")
|
|
24
|
-
|
|
28
|
+
NgffVersions = Literal["0.4"]
|
|
29
|
+
DefaultNgffVersion: Literal["0.4"] = "0.4"
|
|
25
30
|
|
|
26
31
|
|
|
27
32
|
class ImageLabelSource(BaseModel):
|
|
28
33
|
"""Image label source model."""
|
|
29
34
|
|
|
30
|
-
version:
|
|
35
|
+
version: NgffVersions
|
|
31
36
|
source: dict[str, str | None]
|
|
32
37
|
|
|
33
38
|
@classmethod
|
|
34
|
-
def default_init(cls, version:
|
|
39
|
+
def default_init(cls, version: NgffVersions) -> "ImageLabelSource":
|
|
35
40
|
"""Initialize the ImageLabelSource object."""
|
|
36
41
|
return cls(version=version, source={"image": "../../"})
|
|
37
42
|
|
|
@@ -40,7 +45,7 @@ class AbstractNgioImageMeta:
|
|
|
40
45
|
"""Base class for ImageMeta and LabelMeta."""
|
|
41
46
|
|
|
42
47
|
def __init__(
|
|
43
|
-
self, version:
|
|
48
|
+
self, version: NgffVersions, name: str | None, datasets: list[Dataset]
|
|
44
49
|
) -> None:
|
|
45
50
|
"""Initialize the ImageMeta object."""
|
|
46
51
|
self._version = version
|
|
@@ -66,13 +71,13 @@ class AbstractNgioImageMeta:
|
|
|
66
71
|
pixel_size: PixelSize,
|
|
67
72
|
scaling_factors: Collection[float] | None = None,
|
|
68
73
|
name: str | None = None,
|
|
69
|
-
version:
|
|
74
|
+
version: NgffVersions = DefaultNgffVersion,
|
|
70
75
|
):
|
|
71
76
|
"""Initialize the ImageMeta object."""
|
|
72
77
|
axes = canonical_axes(
|
|
73
78
|
axes_names,
|
|
74
|
-
space_units=pixel_size.space_unit,
|
|
75
|
-
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]
|
|
76
81
|
)
|
|
77
82
|
|
|
78
83
|
px_size_dict = pixel_size.as_dict()
|
|
@@ -105,10 +110,33 @@ class AbstractNgioImageMeta:
|
|
|
105
110
|
datasets=datasets,
|
|
106
111
|
)
|
|
107
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
|
+
|
|
108
136
|
@property
|
|
109
|
-
def version(self) ->
|
|
137
|
+
def version(self) -> NgffVersions:
|
|
110
138
|
"""Version of the OME-NFF metadata used to build the object."""
|
|
111
|
-
return self._version
|
|
139
|
+
return self._version # type: ignore[return-value]
|
|
112
140
|
|
|
113
141
|
@property
|
|
114
142
|
def name(self) -> str | None:
|
|
@@ -135,6 +163,16 @@ class AbstractNgioImageMeta:
|
|
|
135
163
|
"""List of paths of the datasets."""
|
|
136
164
|
return [dataset.path for dataset in self.datasets]
|
|
137
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
|
+
|
|
138
176
|
def _get_dataset_by_path(self, path: str) -> Dataset:
|
|
139
177
|
"""Get a dataset by its path."""
|
|
140
178
|
for dataset in self.datasets:
|
|
@@ -331,7 +369,7 @@ class NgioLabelMeta(AbstractNgioImageMeta):
|
|
|
331
369
|
|
|
332
370
|
def __init__(
|
|
333
371
|
self,
|
|
334
|
-
version:
|
|
372
|
+
version: NgffVersions,
|
|
335
373
|
name: str | None,
|
|
336
374
|
datasets: list[Dataset],
|
|
337
375
|
image_label: ImageLabelSource | None = None,
|
|
@@ -370,7 +408,7 @@ class NgioImageMeta(AbstractNgioImageMeta):
|
|
|
370
408
|
|
|
371
409
|
def __init__(
|
|
372
410
|
self,
|
|
373
|
-
version:
|
|
411
|
+
version: NgffVersions,
|
|
374
412
|
name: str | None,
|
|
375
413
|
datasets: list[Dataset],
|
|
376
414
|
channels: ChannelsMeta | None = None,
|
|
@@ -5,7 +5,12 @@ from functools import total_ordering
|
|
|
5
5
|
|
|
6
6
|
import numpy as np
|
|
7
7
|
|
|
8
|
-
from ngio.ome_zarr_meta.ngio_specs import
|
|
8
|
+
from ngio.ome_zarr_meta.ngio_specs import (
|
|
9
|
+
DefaultSpaceUnit,
|
|
10
|
+
DefaultTimeUnit,
|
|
11
|
+
SpaceUnits,
|
|
12
|
+
TimeUnits,
|
|
13
|
+
)
|
|
9
14
|
|
|
10
15
|
################################################################################################
|
|
11
16
|
#
|
|
@@ -33,8 +38,8 @@ class PixelSize:
|
|
|
33
38
|
y: float,
|
|
34
39
|
z: float,
|
|
35
40
|
t: float = 1,
|
|
36
|
-
space_unit: SpaceUnits =
|
|
37
|
-
time_unit: TimeUnits | None =
|
|
41
|
+
space_unit: SpaceUnits | str | None = DefaultSpaceUnit,
|
|
42
|
+
time_unit: TimeUnits | str | None = DefaultTimeUnit,
|
|
38
43
|
):
|
|
39
44
|
"""Initialize the pixel size."""
|
|
40
45
|
self.x = _validate_type(x, "x")
|
|
@@ -42,13 +47,8 @@ class PixelSize:
|
|
|
42
47
|
self.z = _validate_type(z, "z")
|
|
43
48
|
self.t = _validate_type(t, "t")
|
|
44
49
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
self.space_unit = space_unit
|
|
48
|
-
|
|
49
|
-
if time_unit is not None and not isinstance(time_unit, TimeUnits):
|
|
50
|
-
raise TypeError("time_unit must be of type TimeUnits.")
|
|
51
|
-
self.time_unit = time_unit
|
|
50
|
+
self._space_unit = space_unit
|
|
51
|
+
self._time_unit = time_unit
|
|
52
52
|
|
|
53
53
|
def __repr__(self) -> str:
|
|
54
54
|
"""Return a string representation of the pixel size."""
|
|
@@ -74,13 +74,30 @@ class PixelSize:
|
|
|
74
74
|
"""Check if one pixel size is less than the other."""
|
|
75
75
|
if not isinstance(other, PixelSize):
|
|
76
76
|
raise TypeError("Can only compare PixelSize with PixelSize.")
|
|
77
|
-
ref = PixelSize(
|
|
77
|
+
ref = PixelSize(
|
|
78
|
+
0,
|
|
79
|
+
0,
|
|
80
|
+
0,
|
|
81
|
+
0,
|
|
82
|
+
space_unit=self.space_unit,
|
|
83
|
+
time_unit=self.time_unit, # type: ignore
|
|
84
|
+
)
|
|
78
85
|
return self.distance(ref) < other.distance(ref)
|
|
79
86
|
|
|
80
87
|
def as_dict(self) -> dict:
|
|
81
88
|
"""Return the pixel size as a dictionary."""
|
|
82
89
|
return {"t": self.t, "z": self.z, "y": self.y, "x": self.x}
|
|
83
90
|
|
|
91
|
+
@property
|
|
92
|
+
def space_unit(self) -> SpaceUnits | str | None:
|
|
93
|
+
"""Return the space unit."""
|
|
94
|
+
return self._space_unit
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def time_unit(self) -> TimeUnits | str | None:
|
|
98
|
+
"""Return the time unit."""
|
|
99
|
+
return self._time_unit
|
|
100
|
+
|
|
84
101
|
@property
|
|
85
102
|
def tzyx(self) -> tuple[float, float, float, float]:
|
|
86
103
|
"""Return the voxel size in t, z, y, x order."""
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from typing import Annotated
|
|
2
|
+
|
|
3
|
+
from ome_zarr_models.v04.well import WellAttrs as WellAttrs04
|
|
4
|
+
from ome_zarr_models.v04.well_types import WellImage as WellImage04
|
|
5
|
+
from ome_zarr_models.v04.well_types import WellMeta as WellMeta04
|
|
6
|
+
from pydantic import SkipValidation
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class CustomWellImage(WellImage04):
|
|
10
|
+
path: Annotated[str, SkipValidation]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class CustomWellMeta(WellMeta04):
|
|
14
|
+
images: list[CustomWellImage] # type: ignore[valid-type]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class CustomWellAttrs(WellAttrs04):
|
|
18
|
+
well: CustomWellMeta # type: ignore[valid-type]
|
|
@@ -23,7 +23,6 @@ from ome_zarr_models.v04.multiscales import Multiscale as MultiscaleV04
|
|
|
23
23
|
from ome_zarr_models.v04.omero import Channel as ChannelV04
|
|
24
24
|
from ome_zarr_models.v04.omero import Omero as OmeroV04
|
|
25
25
|
from ome_zarr_models.v04.omero import Window as WindowV04
|
|
26
|
-
from ome_zarr_models.v04.well import WellAttrs as WellAttrsV04
|
|
27
26
|
from pydantic import ValidationError
|
|
28
27
|
|
|
29
28
|
from ngio.ome_zarr_meta.ngio_specs import (
|
|
@@ -41,6 +40,7 @@ from ngio.ome_zarr_meta.ngio_specs import (
|
|
|
41
40
|
NgioWellMeta,
|
|
42
41
|
default_channel_name,
|
|
43
42
|
)
|
|
43
|
+
from ngio.ome_zarr_meta.v04._custom_models import CustomWellAttrs as WellAttrsV04
|
|
44
44
|
|
|
45
45
|
|
|
46
46
|
def _is_v04_image_meta(metadata: dict) -> ImageAttrsV04 | ValidationError:
|
|
@@ -321,7 +321,7 @@ def _ngio_to_v04_multiscale(name: str | None, datasets: list[Dataset]) -> Multis
|
|
|
321
321
|
AxisV04(
|
|
322
322
|
name=axis.on_disk_name,
|
|
323
323
|
type=axis.axis_type.value if axis.axis_type is not None else None,
|
|
324
|
-
unit=axis.unit
|
|
324
|
+
unit=axis.unit if axis.unit is not None else None,
|
|
325
325
|
)
|
|
326
326
|
)
|
|
327
327
|
|