ngio 0.2.0a3__py3-none-any.whl → 0.2.0b1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. ngio/__init__.py +4 -4
  2. ngio/common/__init__.py +12 -2
  3. ngio/common/_array_pipe.py +106 -0
  4. ngio/common/_axes_transforms.py +3 -2
  5. ngio/common/_dimensions.py +7 -0
  6. ngio/common/_masking_roi.py +158 -0
  7. ngio/common/_pyramid.py +16 -11
  8. ngio/common/_roi.py +74 -0
  9. ngio/common/_slicer.py +1 -2
  10. ngio/common/_zoom.py +5 -3
  11. ngio/hcs/__init__.py +2 -57
  12. ngio/hcs/plate.py +399 -0
  13. ngio/images/abstract_image.py +97 -28
  14. ngio/images/create.py +48 -29
  15. ngio/images/image.py +99 -46
  16. ngio/images/label.py +109 -92
  17. ngio/images/masked_image.py +259 -0
  18. ngio/images/omezarr_container.py +201 -64
  19. ngio/ome_zarr_meta/__init__.py +25 -13
  20. ngio/ome_zarr_meta/_meta_handlers.py +718 -69
  21. ngio/ome_zarr_meta/ngio_specs/__init__.py +8 -0
  22. ngio/ome_zarr_meta/ngio_specs/_channels.py +11 -0
  23. ngio/ome_zarr_meta/ngio_specs/_ngio_hcs.py +374 -2
  24. ngio/ome_zarr_meta/ngio_specs/_ngio_image.py +169 -119
  25. ngio/ome_zarr_meta/ngio_specs/_pixel_size.py +35 -3
  26. ngio/ome_zarr_meta/v04/__init__.py +17 -5
  27. ngio/ome_zarr_meta/v04/_v04_spec_utils.py +85 -12
  28. ngio/tables/__init__.py +2 -0
  29. ngio/tables/_validators.py +2 -4
  30. ngio/tables/backends/_anndata_utils.py +2 -1
  31. ngio/tables/backends/_anndata_v1.py +2 -1
  32. ngio/tables/backends/_json_v1.py +1 -1
  33. ngio/tables/tables_container.py +12 -2
  34. ngio/tables/v1/__init__.py +1 -2
  35. ngio/tables/v1/_feature_table.py +7 -5
  36. ngio/tables/v1/_generic_table.py +65 -11
  37. ngio/tables/v1/_roi_table.py +145 -27
  38. ngio/utils/_datasets.py +4 -2
  39. ngio/utils/_fractal_fsspec_store.py +3 -2
  40. ngio/utils/_logger.py +3 -1
  41. ngio/utils/_zarr_utils.py +25 -2
  42. {ngio-0.2.0a3.dist-info → ngio-0.2.0b1.dist-info}/METADATA +4 -1
  43. ngio-0.2.0b1.dist-info/RECORD +54 -0
  44. ngio/ome_zarr_meta/_generic_handlers.py +0 -320
  45. ngio/ome_zarr_meta/v04/_meta_handlers.py +0 -54
  46. ngio/tables/v1/_masking_roi_table.py +0 -175
  47. ngio-0.2.0a3.dist-info/RECORD +0 -54
  48. {ngio-0.2.0a3.dist-info → ngio-0.2.0b1.dist-info}/WHEEL +0 -0
  49. {ngio-0.2.0a3.dist-info → ngio-0.2.0b1.dist-info}/licenses/LICENSE +0 -0
@@ -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
- class NgioWellMeta:
5
- images: list[str]
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
+ # %%