ngio 0.2.0a3__py3-none-any.whl → 0.2.0b2__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 +4 -4
- ngio/common/__init__.py +12 -2
- ngio/common/_array_pipe.py +106 -0
- ngio/common/_axes_transforms.py +3 -2
- ngio/common/_dimensions.py +7 -0
- ngio/common/_masking_roi.py +158 -0
- ngio/common/_pyramid.py +16 -11
- ngio/common/_roi.py +74 -0
- ngio/common/_slicer.py +1 -2
- ngio/common/_zoom.py +5 -3
- ngio/hcs/__init__.py +2 -57
- ngio/hcs/plate.py +399 -0
- ngio/images/abstract_image.py +97 -28
- ngio/images/create.py +48 -29
- ngio/images/image.py +99 -46
- ngio/images/label.py +109 -92
- ngio/images/masked_image.py +259 -0
- ngio/images/omezarr_container.py +201 -64
- ngio/ome_zarr_meta/__init__.py +25 -13
- ngio/ome_zarr_meta/_meta_handlers.py +718 -69
- ngio/ome_zarr_meta/ngio_specs/__init__.py +8 -0
- ngio/ome_zarr_meta/ngio_specs/_channels.py +11 -0
- ngio/ome_zarr_meta/ngio_specs/_ngio_hcs.py +374 -2
- ngio/ome_zarr_meta/ngio_specs/_ngio_image.py +169 -119
- ngio/ome_zarr_meta/ngio_specs/_pixel_size.py +35 -3
- ngio/ome_zarr_meta/v04/__init__.py +17 -5
- ngio/ome_zarr_meta/v04/_v04_spec_utils.py +85 -12
- ngio/tables/__init__.py +2 -0
- ngio/tables/_validators.py +2 -4
- ngio/tables/backends/_anndata_utils.py +5 -1
- ngio/tables/backends/_anndata_v1.py +2 -1
- ngio/tables/backends/_json_v1.py +1 -1
- ngio/tables/tables_container.py +12 -2
- ngio/tables/v1/__init__.py +1 -2
- ngio/tables/v1/_feature_table.py +7 -5
- ngio/tables/v1/_generic_table.py +65 -11
- ngio/tables/v1/_roi_table.py +145 -27
- ngio/utils/_datasets.py +4 -2
- ngio/utils/_fractal_fsspec_store.py +3 -2
- ngio/utils/_logger.py +3 -1
- ngio/utils/_zarr_utils.py +25 -2
- {ngio-0.2.0a3.dist-info → ngio-0.2.0b2.dist-info}/METADATA +6 -2
- ngio-0.2.0b2.dist-info/RECORD +54 -0
- ngio/ome_zarr_meta/_generic_handlers.py +0 -320
- ngio/ome_zarr_meta/v04/_meta_handlers.py +0 -54
- ngio/tables/v1/_masking_roi_table.py +0 -175
- ngio-0.2.0a3.dist-info/RECORD +0 -54
- {ngio-0.2.0a3.dist-info → ngio-0.2.0b2.dist-info}/WHEEL +0 -0
- {ngio-0.2.0a3.dist-info → ngio-0.2.0b2.dist-info}/licenses/LICENSE +0 -0
|
@@ -28,6 +28,11 @@ from ngio.ome_zarr_meta.ngio_specs._channels import (
|
|
|
28
28
|
default_channel_name,
|
|
29
29
|
)
|
|
30
30
|
from ngio.ome_zarr_meta.ngio_specs._dataset import Dataset
|
|
31
|
+
from ngio.ome_zarr_meta.ngio_specs._ngio_hcs import (
|
|
32
|
+
ImageInWellPath,
|
|
33
|
+
NgioPlateMeta,
|
|
34
|
+
NgioWellMeta,
|
|
35
|
+
)
|
|
31
36
|
from ngio.ome_zarr_meta.ngio_specs._ngio_image import (
|
|
32
37
|
ImageLabelSource,
|
|
33
38
|
NgioImageLabelMeta,
|
|
@@ -49,11 +54,14 @@ __all__ = [
|
|
|
49
54
|
"ChannelVisualisation",
|
|
50
55
|
"ChannelsMeta",
|
|
51
56
|
"Dataset",
|
|
57
|
+
"ImageInWellPath",
|
|
52
58
|
"ImageLabelSource",
|
|
53
59
|
"NgioColors",
|
|
54
60
|
"NgioImageLabelMeta",
|
|
55
61
|
"NgioImageMeta",
|
|
56
62
|
"NgioLabelMeta",
|
|
63
|
+
"NgioPlateMeta",
|
|
64
|
+
"NgioWellMeta",
|
|
57
65
|
"PixelSize",
|
|
58
66
|
"SpaceUnits",
|
|
59
67
|
"TimeUnits",
|
|
@@ -360,6 +360,17 @@ class ChannelsMeta(BaseModel):
|
|
|
360
360
|
if isinstance(active, Collection):
|
|
361
361
|
_active = _check_elements(active, bool)
|
|
362
362
|
|
|
363
|
+
all_lengths = [
|
|
364
|
+
len(labels),
|
|
365
|
+
len(_wavelength_id),
|
|
366
|
+
len(_colors),
|
|
367
|
+
len(_start),
|
|
368
|
+
len(_end),
|
|
369
|
+
len(_active),
|
|
370
|
+
]
|
|
371
|
+
if len(set(all_lengths)) != 1:
|
|
372
|
+
raise NgioValueError("Channels information must all have the same length.")
|
|
373
|
+
|
|
363
374
|
channels = []
|
|
364
375
|
for ch_name, w_id, color, s, e, a in zip(
|
|
365
376
|
labels, _wavelength_id, _colors, _start, _end, _active, strict=True
|
|
@@ -1,5 +1,377 @@
|
|
|
1
1
|
"""HCS (High Content Screening) specific metadata classes for NGIO."""
|
|
2
2
|
|
|
3
|
+
from typing import Literal
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
|
|
5
|
+
from ome_zarr_models.v04.hcs import HCSAttrs
|
|
6
|
+
from ome_zarr_models.v04.plate import (
|
|
7
|
+
Acquisition,
|
|
8
|
+
Column,
|
|
9
|
+
Plate,
|
|
10
|
+
Row,
|
|
11
|
+
WellInPlate,
|
|
12
|
+
)
|
|
13
|
+
from ome_zarr_models.v04.well import WellAttrs
|
|
14
|
+
from ome_zarr_models.v04.well_types import WellImage, WellMeta
|
|
15
|
+
from pydantic import BaseModel
|
|
16
|
+
|
|
17
|
+
from ngio.utils import NgioValueError
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ImageInWellPath(BaseModel):
|
|
21
|
+
"""Image in a well."""
|
|
22
|
+
|
|
23
|
+
row: str
|
|
24
|
+
column: str | int
|
|
25
|
+
path: str
|
|
26
|
+
acquisition_id: int | None = None
|
|
27
|
+
acquisition_name: str | None = None
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class NgioWellMeta(WellAttrs):
|
|
31
|
+
"""HCS well metadata."""
|
|
32
|
+
|
|
33
|
+
@classmethod
|
|
34
|
+
def default_init(
|
|
35
|
+
cls,
|
|
36
|
+
images: list[ImageInWellPath] | None = None,
|
|
37
|
+
version: Literal["0.4"] | None = None,
|
|
38
|
+
) -> "NgioWellMeta":
|
|
39
|
+
well = cls(well=WellMeta(images=[], version=version))
|
|
40
|
+
if images is None:
|
|
41
|
+
return well
|
|
42
|
+
|
|
43
|
+
for image in images:
|
|
44
|
+
well = well.add_image(path=image.path, acquisition=image.acquisition_id)
|
|
45
|
+
return well
|
|
46
|
+
|
|
47
|
+
def paths(self, acquisition: int | None = None) -> list[str]:
|
|
48
|
+
"""Return the images paths in the well.
|
|
49
|
+
|
|
50
|
+
If acquisition is None, return all images paths in the well.
|
|
51
|
+
Else, return the images paths in the well for the given acquisition.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
acquisition (int | None): The acquisition id to filter the images.
|
|
55
|
+
"""
|
|
56
|
+
if acquisition is None:
|
|
57
|
+
return [images.path for images in self.well.images]
|
|
58
|
+
return [
|
|
59
|
+
images.path
|
|
60
|
+
for images in self.well.images
|
|
61
|
+
if images.acquisition == acquisition
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
def add_image(self, path: str, acquisition: int | None = None) -> "NgioWellMeta":
|
|
65
|
+
"""Add an image to the well.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
path (str): The path of the image.
|
|
69
|
+
acquisition (int | None): The acquisition id of the image.
|
|
70
|
+
"""
|
|
71
|
+
list_of_images = self.well.images
|
|
72
|
+
for image in list_of_images:
|
|
73
|
+
if image.path == path:
|
|
74
|
+
raise NgioValueError(
|
|
75
|
+
f"Image at path {path} already exists in the well."
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
new_image = WellImage(path=path, acquisition=acquisition)
|
|
79
|
+
list_of_images.append(new_image)
|
|
80
|
+
return NgioWellMeta(
|
|
81
|
+
well=WellMeta(images=list_of_images, version=self.well.version)
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
def remove_image(self, path: str) -> "NgioWellMeta":
|
|
85
|
+
"""Remove an image from the well.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
path (str): The path of the image.
|
|
89
|
+
"""
|
|
90
|
+
list_of_images = self.well.images
|
|
91
|
+
for image in list_of_images:
|
|
92
|
+
if image.path == path:
|
|
93
|
+
list_of_images.remove(image)
|
|
94
|
+
return NgioWellMeta(
|
|
95
|
+
well=WellMeta(images=list_of_images, version=self.well.version)
|
|
96
|
+
)
|
|
97
|
+
raise NgioValueError(f"Image at path {path} not found in the well.")
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _stringify_column(column: str | int) -> str:
|
|
101
|
+
"""Convert the column to a string.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
column (str | int): The column to convert.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
str: The column as a string.
|
|
108
|
+
"""
|
|
109
|
+
if isinstance(column, str):
|
|
110
|
+
return column
|
|
111
|
+
|
|
112
|
+
# Maybe we should pad the column with zeros
|
|
113
|
+
return str(column)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _find_row_index(rows: list[str], row: str) -> int | None:
|
|
117
|
+
try:
|
|
118
|
+
return rows.index(row)
|
|
119
|
+
except ValueError:
|
|
120
|
+
return None
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _find_column_index(columns: list[str], column: str | int) -> int | None:
|
|
124
|
+
_num_columns = [int(columns) for columns in columns]
|
|
125
|
+
column = int(column)
|
|
126
|
+
try:
|
|
127
|
+
return _num_columns.index(column)
|
|
128
|
+
except ValueError:
|
|
129
|
+
return None
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _relabel_wells(
|
|
133
|
+
wells: list[WellInPlate], rows: list[Row], columns: list[Column]
|
|
134
|
+
) -> list[WellInPlate]:
|
|
135
|
+
new_wells = []
|
|
136
|
+
_rows = [row.name for row in rows]
|
|
137
|
+
_columns = [column.name for column in columns]
|
|
138
|
+
for well in wells:
|
|
139
|
+
row, column = well.path.split("/")
|
|
140
|
+
row_idx = _find_row_index(_rows, row)
|
|
141
|
+
column_idx = _find_column_index(_columns, column)
|
|
142
|
+
|
|
143
|
+
if row_idx is None:
|
|
144
|
+
raise NgioValueError(f"Row {row} not found in the plate.")
|
|
145
|
+
if column_idx is None:
|
|
146
|
+
raise NgioValueError(f"Column {column} not found in the plate.")
|
|
147
|
+
|
|
148
|
+
new_wells.append(
|
|
149
|
+
WellInPlate(
|
|
150
|
+
path=well.path,
|
|
151
|
+
rowIndex=row_idx,
|
|
152
|
+
columnIndex=column_idx,
|
|
153
|
+
)
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
return new_wells
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class NgioPlateMeta(HCSAttrs):
|
|
160
|
+
"""HCS plate metadata."""
|
|
161
|
+
|
|
162
|
+
@classmethod
|
|
163
|
+
def default_init(
|
|
164
|
+
cls,
|
|
165
|
+
images: list[ImageInWellPath] | None = None,
|
|
166
|
+
name: str | None = None,
|
|
167
|
+
version: str | None = None,
|
|
168
|
+
) -> "NgioPlateMeta":
|
|
169
|
+
plate = cls(
|
|
170
|
+
plate=Plate(
|
|
171
|
+
rows=[],
|
|
172
|
+
columns=[],
|
|
173
|
+
acquisitions=None,
|
|
174
|
+
wells=[],
|
|
175
|
+
field_count=None,
|
|
176
|
+
version=version,
|
|
177
|
+
name=name,
|
|
178
|
+
)
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
if images is None:
|
|
182
|
+
return plate
|
|
183
|
+
|
|
184
|
+
for image in images:
|
|
185
|
+
plate = plate.add_well(
|
|
186
|
+
row=image.row,
|
|
187
|
+
column=image.column,
|
|
188
|
+
acquisition_id=image.acquisition_id,
|
|
189
|
+
acquisition_name=image.acquisition_name,
|
|
190
|
+
)
|
|
191
|
+
return plate
|
|
192
|
+
|
|
193
|
+
@property
|
|
194
|
+
def columns(self) -> list[str]:
|
|
195
|
+
"""Returns the list of columns in the plate."""
|
|
196
|
+
return [columns.name for columns in self.plate.columns]
|
|
197
|
+
|
|
198
|
+
@property
|
|
199
|
+
def rows(self) -> list[str]:
|
|
200
|
+
"""Returns the list of rows in the plate."""
|
|
201
|
+
return [rows.name for rows in self.plate.rows]
|
|
202
|
+
|
|
203
|
+
@property
|
|
204
|
+
def acquisitions_names(self) -> list[str | None]:
|
|
205
|
+
"""Return the acquisitions in the plate."""
|
|
206
|
+
if self.plate.acquisitions is None:
|
|
207
|
+
return []
|
|
208
|
+
return [acquisitions.name for acquisitions in self.plate.acquisitions]
|
|
209
|
+
|
|
210
|
+
@property
|
|
211
|
+
def acquisitions_ids(self) -> list[int]:
|
|
212
|
+
"""Return the acquisitions ids in the plate."""
|
|
213
|
+
if self.plate.acquisitions is None:
|
|
214
|
+
return []
|
|
215
|
+
return [acquisitions.id for acquisitions in self.plate.acquisitions]
|
|
216
|
+
|
|
217
|
+
@property
|
|
218
|
+
def wells_paths(self) -> list[str]:
|
|
219
|
+
"""Return the wells paths in the plate."""
|
|
220
|
+
return [wells.path for wells in self.plate.wells]
|
|
221
|
+
|
|
222
|
+
def get_well_path(self, row: str, column: str | int) -> str:
|
|
223
|
+
"""Return the well path for the given row and column.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
row (str): The row of the well.
|
|
227
|
+
column (str | int): The column of the well.
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
str: The path of the well.
|
|
231
|
+
"""
|
|
232
|
+
if row not in self.rows:
|
|
233
|
+
raise NgioValueError(
|
|
234
|
+
f"Row {row} not found in the plate. Available rows are {self.rows}."
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
row_idx = self.rows.index(row)
|
|
238
|
+
|
|
239
|
+
_num_columns = [int(columns) for columns in self.columns]
|
|
240
|
+
|
|
241
|
+
try:
|
|
242
|
+
_column = int(column)
|
|
243
|
+
except ValueError:
|
|
244
|
+
raise NgioValueError(
|
|
245
|
+
f"Column {column} must be an integer or convertible to an integer."
|
|
246
|
+
) from None
|
|
247
|
+
|
|
248
|
+
column_idx = _num_columns.index(_column)
|
|
249
|
+
|
|
250
|
+
for well in self.plate.wells:
|
|
251
|
+
if well.columnIndex == column_idx and well.rowIndex == row_idx:
|
|
252
|
+
return well.path
|
|
253
|
+
|
|
254
|
+
raise NgioValueError(
|
|
255
|
+
f"Well at row {row} and column {column} not found in the plate."
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
def add_well(
|
|
259
|
+
self,
|
|
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.
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
row (str): The row of the well.
|
|
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.
|
|
274
|
+
"""
|
|
275
|
+
relabel_wells = False
|
|
276
|
+
|
|
277
|
+
row_names = self.rows
|
|
278
|
+
row_idx = _find_row_index(row_names, row)
|
|
279
|
+
if row_idx is None:
|
|
280
|
+
row_names.append(row)
|
|
281
|
+
row_names.sort()
|
|
282
|
+
row_idx = row_names.index(row)
|
|
283
|
+
relabel_wells = True
|
|
284
|
+
|
|
285
|
+
rows = [Row(name=row) for row in row_names]
|
|
286
|
+
|
|
287
|
+
columns_names = self.columns
|
|
288
|
+
column_idx = _find_column_index(columns_names, column)
|
|
289
|
+
if column_idx is None:
|
|
290
|
+
columns_names.append(_stringify_column(column))
|
|
291
|
+
# sort as numbers
|
|
292
|
+
columns_names.sort(key=lambda x: int(x))
|
|
293
|
+
column_idx = columns_names.index(_stringify_column(column))
|
|
294
|
+
relabel_wells = True
|
|
295
|
+
|
|
296
|
+
columns = [Column(name=column) for column in columns_names]
|
|
297
|
+
|
|
298
|
+
wells = self.plate.wells
|
|
299
|
+
if relabel_wells:
|
|
300
|
+
wells = _relabel_wells(wells, rows, columns)
|
|
301
|
+
|
|
302
|
+
for well_obj in wells:
|
|
303
|
+
if well_obj.rowIndex == row_idx and well_obj.columnIndex == column_idx:
|
|
304
|
+
break
|
|
305
|
+
else:
|
|
306
|
+
wells.append(
|
|
307
|
+
WellInPlate(
|
|
308
|
+
path=f"{row}/{_stringify_column(column)}",
|
|
309
|
+
rowIndex=row_idx,
|
|
310
|
+
columnIndex=column_idx,
|
|
311
|
+
)
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
acquisitions = self.plate.acquisitions
|
|
315
|
+
if acquisition_id is not None:
|
|
316
|
+
if acquisitions is None and len(wells) > 0:
|
|
317
|
+
acquisitions = [Acquisition(id=0, name=acquisition_name)]
|
|
318
|
+
elif acquisitions is None:
|
|
319
|
+
acquisitions = []
|
|
320
|
+
|
|
321
|
+
for acquisition_obj in acquisitions:
|
|
322
|
+
if acquisition_obj.id == acquisition_id:
|
|
323
|
+
break
|
|
324
|
+
else:
|
|
325
|
+
acquisitions.append(
|
|
326
|
+
Acquisition(
|
|
327
|
+
id=acquisition_id, name=acquisition_name, **acquisition_kwargs
|
|
328
|
+
)
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
new_plate = Plate(
|
|
332
|
+
rows=rows,
|
|
333
|
+
columns=columns,
|
|
334
|
+
acquisitions=acquisitions,
|
|
335
|
+
wells=wells,
|
|
336
|
+
field_count=self.plate.field_count,
|
|
337
|
+
version=self.plate.version,
|
|
338
|
+
)
|
|
339
|
+
return NgioPlateMeta(plate=new_plate)
|
|
340
|
+
|
|
341
|
+
def remove_well(self, row: str, column: str | int) -> "NgioPlateMeta":
|
|
342
|
+
"""Remove a well from the plate.
|
|
343
|
+
|
|
344
|
+
Args:
|
|
345
|
+
row (str): The row of the well.
|
|
346
|
+
column (str | int): The column of the well.
|
|
347
|
+
"""
|
|
348
|
+
row_idx = _find_row_index(self.rows, row)
|
|
349
|
+
if row_idx is None:
|
|
350
|
+
raise NgioValueError(f"Row {row} not found in the plate.")
|
|
351
|
+
|
|
352
|
+
column_idx = _find_column_index(self.columns, column)
|
|
353
|
+
if column_idx is None:
|
|
354
|
+
raise NgioValueError(f"Column {column} not found in the plate.")
|
|
355
|
+
|
|
356
|
+
wells = self.plate.wells
|
|
357
|
+
for well_obj in wells:
|
|
358
|
+
if well_obj.rowIndex == row_idx and well_obj.columnIndex == column_idx:
|
|
359
|
+
wells.remove(well_obj)
|
|
360
|
+
break
|
|
361
|
+
else:
|
|
362
|
+
raise NgioValueError(
|
|
363
|
+
f"Well at row {row} and column {column} not found in the plate."
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
new_plate = Plate(
|
|
367
|
+
rows=self.plate.rows,
|
|
368
|
+
columns=self.plate.columns,
|
|
369
|
+
acquisitions=self.plate.acquisitions,
|
|
370
|
+
wells=wells,
|
|
371
|
+
field_count=self.plate.field_count,
|
|
372
|
+
version=self.plate.version,
|
|
373
|
+
)
|
|
374
|
+
return NgioPlateMeta(plate=new_plate)
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
# %%
|