ngio 0.5.0b6__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 (88) hide show
  1. ngio/__init__.py +69 -0
  2. ngio/common/__init__.py +28 -0
  3. ngio/common/_dimensions.py +335 -0
  4. ngio/common/_masking_roi.py +153 -0
  5. ngio/common/_pyramid.py +408 -0
  6. ngio/common/_roi.py +315 -0
  7. ngio/common/_synt_images_utils.py +101 -0
  8. ngio/common/_zoom.py +188 -0
  9. ngio/experimental/__init__.py +5 -0
  10. ngio/experimental/iterators/__init__.py +15 -0
  11. ngio/experimental/iterators/_abstract_iterator.py +390 -0
  12. ngio/experimental/iterators/_feature.py +189 -0
  13. ngio/experimental/iterators/_image_processing.py +130 -0
  14. ngio/experimental/iterators/_mappers.py +48 -0
  15. ngio/experimental/iterators/_rois_utils.py +126 -0
  16. ngio/experimental/iterators/_segmentation.py +235 -0
  17. ngio/hcs/__init__.py +19 -0
  18. ngio/hcs/_plate.py +1354 -0
  19. ngio/images/__init__.py +44 -0
  20. ngio/images/_abstract_image.py +967 -0
  21. ngio/images/_create_synt_container.py +132 -0
  22. ngio/images/_create_utils.py +423 -0
  23. ngio/images/_image.py +926 -0
  24. ngio/images/_label.py +411 -0
  25. ngio/images/_masked_image.py +531 -0
  26. ngio/images/_ome_zarr_container.py +1237 -0
  27. ngio/images/_table_ops.py +471 -0
  28. ngio/io_pipes/__init__.py +75 -0
  29. ngio/io_pipes/_io_pipes.py +361 -0
  30. ngio/io_pipes/_io_pipes_masked.py +488 -0
  31. ngio/io_pipes/_io_pipes_roi.py +146 -0
  32. ngio/io_pipes/_io_pipes_types.py +56 -0
  33. ngio/io_pipes/_match_shape.py +377 -0
  34. ngio/io_pipes/_ops_axes.py +344 -0
  35. ngio/io_pipes/_ops_slices.py +411 -0
  36. ngio/io_pipes/_ops_slices_utils.py +199 -0
  37. ngio/io_pipes/_ops_transforms.py +104 -0
  38. ngio/io_pipes/_zoom_transform.py +180 -0
  39. ngio/ome_zarr_meta/__init__.py +65 -0
  40. ngio/ome_zarr_meta/_meta_handlers.py +536 -0
  41. ngio/ome_zarr_meta/ngio_specs/__init__.py +77 -0
  42. ngio/ome_zarr_meta/ngio_specs/_axes.py +515 -0
  43. ngio/ome_zarr_meta/ngio_specs/_channels.py +462 -0
  44. ngio/ome_zarr_meta/ngio_specs/_dataset.py +89 -0
  45. ngio/ome_zarr_meta/ngio_specs/_ngio_hcs.py +539 -0
  46. ngio/ome_zarr_meta/ngio_specs/_ngio_image.py +438 -0
  47. ngio/ome_zarr_meta/ngio_specs/_pixel_size.py +122 -0
  48. ngio/ome_zarr_meta/v04/__init__.py +27 -0
  49. ngio/ome_zarr_meta/v04/_custom_models.py +18 -0
  50. ngio/ome_zarr_meta/v04/_v04_spec.py +473 -0
  51. ngio/ome_zarr_meta/v05/__init__.py +27 -0
  52. ngio/ome_zarr_meta/v05/_custom_models.py +18 -0
  53. ngio/ome_zarr_meta/v05/_v05_spec.py +511 -0
  54. ngio/resources/20200812-CardiomyocyteDifferentiation14-Cycle1_B03/mask.png +0 -0
  55. ngio/resources/20200812-CardiomyocyteDifferentiation14-Cycle1_B03/nuclei.png +0 -0
  56. ngio/resources/20200812-CardiomyocyteDifferentiation14-Cycle1_B03/raw.jpg +0 -0
  57. ngio/resources/__init__.py +55 -0
  58. ngio/resources/resource_model.py +36 -0
  59. ngio/tables/__init__.py +43 -0
  60. ngio/tables/_abstract_table.py +270 -0
  61. ngio/tables/_tables_container.py +449 -0
  62. ngio/tables/backends/__init__.py +57 -0
  63. ngio/tables/backends/_abstract_backend.py +240 -0
  64. ngio/tables/backends/_anndata.py +139 -0
  65. ngio/tables/backends/_anndata_utils.py +90 -0
  66. ngio/tables/backends/_csv.py +19 -0
  67. ngio/tables/backends/_json.py +92 -0
  68. ngio/tables/backends/_parquet.py +19 -0
  69. ngio/tables/backends/_py_arrow_backends.py +222 -0
  70. ngio/tables/backends/_table_backends.py +226 -0
  71. ngio/tables/backends/_utils.py +608 -0
  72. ngio/tables/v1/__init__.py +23 -0
  73. ngio/tables/v1/_condition_table.py +71 -0
  74. ngio/tables/v1/_feature_table.py +125 -0
  75. ngio/tables/v1/_generic_table.py +49 -0
  76. ngio/tables/v1/_roi_table.py +575 -0
  77. ngio/transforms/__init__.py +5 -0
  78. ngio/transforms/_zoom.py +19 -0
  79. ngio/utils/__init__.py +45 -0
  80. ngio/utils/_cache.py +48 -0
  81. ngio/utils/_datasets.py +165 -0
  82. ngio/utils/_errors.py +37 -0
  83. ngio/utils/_fractal_fsspec_store.py +42 -0
  84. ngio/utils/_zarr_utils.py +534 -0
  85. ngio-0.5.0b6.dist-info/METADATA +148 -0
  86. ngio-0.5.0b6.dist-info/RECORD +88 -0
  87. ngio-0.5.0b6.dist-info/WHEEL +4 -0
  88. ngio-0.5.0b6.dist-info/licenses/LICENSE +28 -0
@@ -0,0 +1,608 @@
1
+ """Utility functions for converting between different tables formats.
2
+
3
+ The supported formats are:
4
+ - pandas DataFrame
5
+ - polars DataFrame or LazyFrame
6
+ - AnnData
7
+
8
+ These functions are used to validate and normalize the tables
9
+ to ensure that conversion between formats is consistent.
10
+ """
11
+
12
+ from copy import deepcopy
13
+ from typing import Literal
14
+
15
+ import numpy as np
16
+ import pandas as pd
17
+ import pandas.api.types as ptypes
18
+ import polars as pl
19
+ from anndata import AnnData
20
+ from pandas import DataFrame
21
+ from polars import DataFrame as PolarsDataFrame
22
+ from polars import LazyFrame
23
+
24
+ from ngio.utils import NgioTableValidationError, NgioValueError
25
+
26
+ TabularData = AnnData | DataFrame | PolarsDataFrame | LazyFrame
27
+
28
+ # -----------------
29
+ # Validation utils
30
+ # -----------------
31
+
32
+
33
+ def _validate_index_key_df(pandas_df: DataFrame, index_key: str | None) -> DataFrame:
34
+ """Validate the index key of the pandas DataFrame.
35
+
36
+ Args:
37
+ pandas_df (DataFrame): The pandas DataFrame to validate.
38
+ index_key (str | None): The column name to use as the index of the DataFrame.
39
+
40
+ Returns:
41
+ DataFrame: DataFrame with validated index key.
42
+
43
+ Raises:
44
+ NgioTableValidationError: If index key is not found in DataFrame.
45
+ """
46
+ if index_key is None:
47
+ return pandas_df
48
+
49
+ if pandas_df.index.name == index_key:
50
+ return pandas_df
51
+
52
+ if index_key in pandas_df.columns:
53
+ pandas_df = pandas_df.set_index(index_key)
54
+ pandas_df.index.name = index_key
55
+ return pandas_df
56
+
57
+ if pandas_df.index.name is None:
58
+ pandas_df.index.name = index_key
59
+ return pandas_df
60
+
61
+ raise NgioTableValidationError(f"Index key '{index_key}' is not found in DataFrame")
62
+
63
+
64
+ def _validate_cast_index_dtype_df(
65
+ pandas_df: DataFrame, index_type: str | None
66
+ ) -> DataFrame:
67
+ """Check if the index of the DataFrame has the correct dtype.
68
+
69
+ Args:
70
+ pandas_df (DataFrame): The pandas DataFrame to validate.
71
+ index_type (str | None): The type to cast the index to ('str' or 'int').
72
+
73
+ Returns:
74
+ DataFrame: DataFrame with index of the specified type.
75
+
76
+ Raises:
77
+ NgioTableValidationError: If index cannot be cast to the specified type.
78
+ NgioValueError: If index_type is not 'str' or 'int'.
79
+ """
80
+ if index_type is None:
81
+ # Nothing to do
82
+ return pandas_df
83
+
84
+ if index_type == "str":
85
+ if ptypes.is_integer_dtype(pandas_df.index):
86
+ # Convert the int index to string is generally safe
87
+ pandas_df = pandas_df.set_index(pandas_df.index.astype(str))
88
+
89
+ if not ptypes.is_string_dtype(pandas_df.index):
90
+ raise NgioTableValidationError(
91
+ f"Table index must be of string type, got {pandas_df.index.dtype}"
92
+ )
93
+
94
+ elif index_type == "int":
95
+ if ptypes.is_string_dtype(pandas_df.index):
96
+ # Try to convert the string index to int
97
+ try:
98
+ pandas_df = pandas_df.set_index(pandas_df.index.astype(int))
99
+ except ValueError as e:
100
+ if "invalid literal for int() with base 10" in str(e):
101
+ raise NgioTableValidationError(
102
+ "Table index must be of integer type, got str."
103
+ f" We tried implicit conversion and failed: {e}"
104
+ ) from None
105
+ else:
106
+ raise e from e
107
+
108
+ if not ptypes.is_integer_dtype(pandas_df.index):
109
+ raise NgioTableValidationError(
110
+ f"Table index must be of integer type, got {pandas_df.index.dtype}"
111
+ )
112
+ else:
113
+ raise NgioValueError(
114
+ f"Invalid index type '{index_type}'. Must be 'int' or 'str'."
115
+ )
116
+
117
+ return pandas_df
118
+
119
+
120
+ def _check_for_mixed_types(series: pd.Series) -> None:
121
+ """Check if the column has mixed types.
122
+
123
+ Args:
124
+ series (pd.Series): The pandas Series to check.
125
+
126
+ Raises:
127
+ NgioTableValidationError: If the column has mixed types.
128
+ """
129
+ if series.apply(type).nunique() > 1: # type: ignore (type lint fails here)
130
+ raise NgioTableValidationError(
131
+ f"Column {series.name} has mixed types: "
132
+ f"{series.apply(type).unique()}. " # type: ignore (type lint fails here)
133
+ "Type of all elements must be the same."
134
+ )
135
+
136
+
137
+ def _check_for_supported_types(series: pd.Series) -> Literal["str", "int", "numeric"]:
138
+ """Check if the column has supported types.
139
+
140
+ Args:
141
+ series (pd.Series): The pandas Series to check.
142
+
143
+ Returns:
144
+ Literal["str", "int", "numeric"]: The type category of the series.
145
+
146
+ Raises:
147
+ NgioTableValidationError: If the column has unsupported types.
148
+ """
149
+ if ptypes.is_string_dtype(series):
150
+ return "str"
151
+ if ptypes.is_integer_dtype(series):
152
+ return "int"
153
+ if ptypes.is_numeric_dtype(series):
154
+ return "numeric"
155
+ raise NgioTableValidationError(
156
+ f"Column {series.name} has unsupported type: {series.dtype}."
157
+ " Supported types are string and numerics."
158
+ )
159
+
160
+
161
+ # -----------------
162
+ # Normalization functions
163
+ # -----------------
164
+
165
+
166
+ def normalize_pandas_df(
167
+ pandas_df: DataFrame,
168
+ index_key: str | None = None,
169
+ index_type: Literal["int", "str"] | None = None,
170
+ reset_index: bool = False,
171
+ ) -> DataFrame:
172
+ """Make sure the DataFrame has the correct index and dtype.
173
+
174
+ Args:
175
+ pandas_df (DataFrame): The pandas DataFrame to validate.
176
+ index_key (str | None): The column name to use as the index of the DataFrame.
177
+ Default is None.
178
+ index_type (str | None): The type of the index column in the DataFrame.
179
+ Either 'str' or 'int'. Default is None.
180
+ reset_index (bool): If True the index will be reset (i.e. the index will be
181
+ converted to a column). If False, the index will be kept as is.
182
+
183
+ Returns:
184
+ DataFrame: Normalized pandas DataFrame.
185
+ """
186
+ pandas_df = _validate_index_key_df(pandas_df, index_key)
187
+ pandas_df = _validate_cast_index_dtype_df(pandas_df, index_type)
188
+ if pandas_df.index.name is not None:
189
+ index_key = str(pandas_df.index.name)
190
+
191
+ if reset_index and pandas_df.index.name is not None:
192
+ pandas_df = pandas_df.reset_index()
193
+ return pandas_df
194
+
195
+
196
+ def normalize_polars_lf(
197
+ polars_lf: LazyFrame | PolarsDataFrame,
198
+ index_key: str | None = None,
199
+ index_type: Literal["int", "str"] | None = None,
200
+ ) -> LazyFrame:
201
+ """Validate the polars LazyFrame.
202
+
203
+ Args:
204
+ polars_lf (LazyFrame | PolarsDataFrame): The polars LazyFrame to validate.
205
+ index_key (str | None): The column name to use as the index of the DataFrame.
206
+ Default is None.
207
+ index_type (str | None): The type of the index column in the DataFrame.
208
+ Either 'str' or 'int'. Default is None.
209
+
210
+ Returns:
211
+ LazyFrame: Normalized polars LazyFrame.
212
+
213
+ Raises:
214
+ ValueError: If index_key is not found or index_type is invalid.
215
+ """
216
+ if index_key is not None:
217
+ schema = polars_lf.collect_schema()
218
+ if index_key not in schema:
219
+ raise NgioTableValidationError(
220
+ f"Index key '{index_key}' not found in LazyFrame columns."
221
+ )
222
+
223
+ if index_type is not None:
224
+ if index_type not in ["int", "str"]:
225
+ raise NgioTableValidationError(
226
+ f"Invalid index type '{index_type}'. Must be 'int' or 'str'."
227
+ )
228
+ if index_type == "int" and not schema[index_key].is_integer():
229
+ polars_lf = polars_lf.with_columns(pl.col(index_key).cast(pl.Int64))
230
+ elif index_type == "str" and not schema[index_key] == pl.String():
231
+ polars_lf = polars_lf.with_columns(pl.col(index_key).cast(pl.String()))
232
+
233
+ if isinstance(polars_lf, PolarsDataFrame):
234
+ polars_lf = polars_lf.lazy()
235
+ return polars_lf
236
+
237
+
238
+ def normalize_anndata(
239
+ anndata: AnnData,
240
+ index_key: str | None = None,
241
+ ) -> AnnData:
242
+ """Validate the AnnData object.
243
+
244
+ Args:
245
+ anndata (AnnData): The AnnData object to validate.
246
+ index_key (str | None): The column name to use as the index of the DataFrame.
247
+ Default is None.
248
+
249
+ Returns:
250
+ AnnData: Normalized AnnData object.
251
+ """
252
+ if index_key is None:
253
+ return anndata
254
+ obs = _validate_index_key_df(anndata.obs, index_key)
255
+ obs = _validate_cast_index_dtype_df(obs, "str")
256
+
257
+ if obs.equals(anndata.obs):
258
+ return anndata
259
+
260
+ anndata = deepcopy(anndata)
261
+ anndata.obs = obs
262
+ return anndata
263
+
264
+
265
+ # -----------------
266
+ # Conversion functions
267
+ # -----------------
268
+
269
+
270
+ def convert_pandas_to_polars(
271
+ pandas_df: DataFrame,
272
+ index_key: str | None = None,
273
+ index_type: Literal["int", "str"] | None = None,
274
+ ) -> LazyFrame:
275
+ """Convert a pandas DataFrame to a polars LazyFrame.
276
+
277
+ Args:
278
+ pandas_df (DataFrame): The pandas DataFrame to convert.
279
+ index_key (str | None): The column name to use as the index of the DataFrame.
280
+ Default is None.
281
+ index_type (str | None): The type of the index column in the DataFrame.
282
+ Either 'str' or 'int'. Default is None.
283
+
284
+ Returns:
285
+ LazyFrame: Converted and normalized polars LazyFrame.
286
+ """
287
+ pandas_df = normalize_pandas_df(
288
+ pandas_df,
289
+ index_key=index_key,
290
+ index_type=index_type,
291
+ reset_index=True,
292
+ )
293
+ return pl.from_pandas(pandas_df).lazy()
294
+
295
+
296
+ def convert_polars_to_pandas(
297
+ polars_df: PolarsDataFrame | LazyFrame,
298
+ index_key: str | None = None,
299
+ index_type: Literal["int", "str"] | None = None,
300
+ reset_index: bool = False,
301
+ ) -> DataFrame:
302
+ """Convert a polars DataFrame or LazyFrame to a pandas DataFrame.
303
+
304
+ Args:
305
+ polars_df (PolarsDataFrame | LazyFrame): The polars DataFrame or
306
+ LazyFrame to convert.
307
+ index_key (str | None): The column name to use as the index of the DataFrame.
308
+ Default is None.
309
+ index_type (str | None): The type of the index column in the DataFrame.
310
+ Either 'str' or 'int'. Default is None.
311
+ reset_index (bool): If True the index will be reset (i.e., the index will be
312
+ converted to a column). If False, the index will be kept as is.
313
+
314
+ Returns:
315
+ DataFrame: Converted and normalized pandas DataFrame.
316
+ """
317
+ if isinstance(polars_df, LazyFrame):
318
+ polars_df = polars_df.collect()
319
+
320
+ pandas_df = polars_df.to_pandas()
321
+ pandas_df = normalize_pandas_df(
322
+ pandas_df,
323
+ index_key=index_key,
324
+ index_type=index_type,
325
+ reset_index=reset_index,
326
+ )
327
+ return pandas_df
328
+
329
+
330
+ def convert_pandas_to_anndata(
331
+ pandas_df: DataFrame,
332
+ index_key: str | None = None,
333
+ ) -> AnnData:
334
+ """Convert a pandas DataFrame to an AnnData object.
335
+
336
+ Args:
337
+ pandas_df (DataFrame): The pandas DataFrame to convert.
338
+ index_key (str | None): The column name to use as the index of the DataFrame.
339
+ Default is None.
340
+
341
+ Returns:
342
+ AnnData: Converted AnnData object.
343
+ """
344
+ pandas_df = normalize_pandas_df(
345
+ pandas_df,
346
+ index_key=index_key,
347
+ index_type="str",
348
+ reset_index=False,
349
+ )
350
+
351
+ str_columns, int_columns, num_columns = [], [], []
352
+ for col_name in pandas_df.columns:
353
+ column = pandas_df[col_name]
354
+ _check_for_mixed_types(column) # Mixed types are not allowed in the table
355
+ col_type = _check_for_supported_types(
356
+ column
357
+ ) # Only string and numeric types are allowed
358
+
359
+ if col_type == "str":
360
+ str_columns.append(col_name)
361
+
362
+ elif col_type == "int":
363
+ int_columns.append(col_name)
364
+
365
+ elif col_type == "numeric":
366
+ num_columns.append(col_name)
367
+
368
+ # Converting all observations to string
369
+ obs_df = pandas_df[str_columns + int_columns]
370
+ obs_df.index = pandas_df.index
371
+
372
+ x_df = pandas_df[num_columns]
373
+
374
+ if x_df.dtypes.nunique() > 1:
375
+ x_df = x_df.astype("float64")
376
+
377
+ if x_df.empty:
378
+ # If there are no numeric columns, create an empty array
379
+ # to avoid AnnData failing to create the object
380
+ x_df = np.zeros((len(obs_df), 0), dtype="float64")
381
+
382
+ return AnnData(X=x_df, obs=obs_df)
383
+
384
+
385
+ def convert_anndata_to_pandas(
386
+ anndata: AnnData,
387
+ index_key: str | None = None,
388
+ index_type: Literal["int", "str"] | None = None,
389
+ reset_index: bool = False,
390
+ ) -> DataFrame:
391
+ """Convert an AnnData object to a pandas DataFrame.
392
+
393
+ Args:
394
+ anndata (AnnData): An AnnData object to convert.
395
+ index_key (str | None): The column name to use as the index of the DataFrame.
396
+ Default is None.
397
+ index_type (str | None): The type of the index column in the DataFrame.
398
+ Either 'str' or 'int'. Default is None.
399
+ reset_index (bool): If True the index will be reset (i.e., the index will be
400
+ converted to a column). If False, the index will be kept as is.
401
+
402
+ Returns:
403
+ DataFrame: Converted and normalized pandas DataFrame.
404
+ """
405
+ pandas_df = anndata.to_df()
406
+ pandas_df[anndata.obs.columns.to_list()] = anndata.obs
407
+ pandas_df = normalize_pandas_df(
408
+ pandas_df,
409
+ index_key=index_key,
410
+ index_type=index_type,
411
+ reset_index=reset_index,
412
+ )
413
+ return pandas_df
414
+
415
+
416
+ def convert_anndata_to_polars(
417
+ anndata: AnnData,
418
+ index_key: str | None = None,
419
+ index_type: Literal["int", "str"] | None = None,
420
+ ) -> LazyFrame:
421
+ """Convert an AnnData object to a polars LazyFrame.
422
+
423
+ Args:
424
+ anndata (AnnData): An AnnData object to convert.
425
+ index_key (str | None): The column name to use as the index of the DataFrame.
426
+ Default is None.
427
+ index_type (str | None): The type of the index column in the DataFrame.
428
+ Either 'str' or 'int'. Default is None.
429
+
430
+ Returns:
431
+ LazyFrame: Converted and normalized polars LazyFrame.
432
+ """
433
+ pandas_df = convert_anndata_to_pandas(
434
+ anndata,
435
+ index_key=index_key,
436
+ index_type=index_type,
437
+ reset_index=True,
438
+ )
439
+ return pl.from_pandas(pandas_df).lazy()
440
+
441
+
442
+ def convert_polars_to_anndata(
443
+ polars_df: LazyFrame | PolarsDataFrame,
444
+ index_key: str | None = None,
445
+ ) -> AnnData:
446
+ """Convert a polars LazyFrame or DataFrame to an AnnData object.
447
+
448
+ Args:
449
+ polars_df (LazyFrame | PolarsDataFrame): The polars LazyFrame or
450
+ DataFrame to convert.
451
+ index_key (str | None): The column name to use as the index of the DataFrame.
452
+ Default is None.
453
+
454
+ Returns:
455
+ AnnData: Converted AnnData object.
456
+ """
457
+ if isinstance(polars_df, LazyFrame):
458
+ polars_df = polars_df.collect()
459
+ pandas_df = polars_df.to_pandas()
460
+ return convert_pandas_to_anndata(
461
+ pandas_df,
462
+ index_key=index_key,
463
+ )
464
+
465
+
466
+ # -----------------
467
+ # Conversion functions
468
+ # -----------------
469
+
470
+
471
+ def normalize_table(
472
+ table_data: TabularData,
473
+ index_key: str | None = None,
474
+ index_type: Literal["int", "str"] | None = None,
475
+ ) -> TabularData:
476
+ """Normalize a table to a specific format.
477
+
478
+ Args:
479
+ table_data (TabularData): The table to normalize.
480
+ index_key (str | None): The column name to use as the index of the DataFrame.
481
+ Default is None.
482
+ index_type (str | None): The type of the index column in the DataFrame.
483
+ Either 'str' or 'int'. Default is None.
484
+
485
+ Returns:
486
+ DataFrame | AnnData | PolarsDataFrame | LazyFrame: Normalized table.
487
+ """
488
+ if isinstance(table_data, DataFrame):
489
+ return normalize_pandas_df(
490
+ table_data,
491
+ index_key=index_key,
492
+ index_type=index_type,
493
+ reset_index=False,
494
+ )
495
+ if isinstance(table_data, AnnData):
496
+ return normalize_anndata(table_data, index_key=index_key)
497
+ if isinstance(table_data, PolarsDataFrame) or isinstance(table_data, LazyFrame):
498
+ return normalize_polars_lf(
499
+ table_data,
500
+ index_key=index_key,
501
+ index_type=index_type,
502
+ )
503
+ raise NgioValueError(f"Unsupported table type: {type(table_data)}")
504
+
505
+
506
+ def convert_to_anndata(
507
+ table_data: TabularData,
508
+ index_key: str | None = None,
509
+ ) -> AnnData:
510
+ """Convert a table to an AnnData object.
511
+
512
+ Args:
513
+ table_data (TabularData): The table to convert.
514
+ index_key (str | None): The column name to use as the index of the DataFrame.
515
+ Default is None.
516
+
517
+ Returns:
518
+ AnnData: Converted AnnData object.
519
+ """
520
+ if isinstance(table_data, AnnData):
521
+ return normalize_anndata(table_data, index_key=index_key)
522
+ if isinstance(table_data, DataFrame):
523
+ return convert_pandas_to_anndata(table_data, index_key=index_key)
524
+ if isinstance(table_data, PolarsDataFrame) or isinstance(table_data, LazyFrame):
525
+ return convert_polars_to_anndata(table_data, index_key=index_key)
526
+ raise NgioValueError(f"Unsupported table type: {type(table_data)}")
527
+
528
+
529
+ def convert_to_pandas(
530
+ table_data: TabularData,
531
+ index_key: str | None = None,
532
+ index_type: Literal["int", "str"] | None = None,
533
+ reset_index: bool = False,
534
+ ) -> DataFrame:
535
+ """Convert a table to a pandas DataFrame.
536
+
537
+ Args:
538
+ table_data (TabularData): The table to convert.
539
+ index_key (str | None): The column name to use as the index of the DataFrame.
540
+ Default is None.
541
+ index_type (str | None): The type of the index column in the DataFrame.
542
+ Either 'str' or 'int'. Default is None.
543
+ reset_index (bool): If True the index will be reset (i.e., the index will be
544
+ converted to a column). If False, the index will be kept as is.
545
+
546
+ Returns:
547
+ DataFrame: Converted pandas DataFrame.
548
+ """
549
+ if isinstance(table_data, DataFrame):
550
+ return normalize_pandas_df(
551
+ table_data,
552
+ index_key=index_key,
553
+ index_type=index_type,
554
+ reset_index=reset_index,
555
+ )
556
+ if isinstance(table_data, AnnData):
557
+ return convert_anndata_to_pandas(
558
+ table_data,
559
+ index_key=index_key,
560
+ index_type=index_type,
561
+ reset_index=reset_index,
562
+ )
563
+ if isinstance(table_data, PolarsDataFrame) or isinstance(table_data, LazyFrame):
564
+ return convert_polars_to_pandas(
565
+ table_data,
566
+ index_key=index_key,
567
+ index_type=index_type,
568
+ reset_index=reset_index,
569
+ )
570
+ raise NgioValueError(f"Unsupported table type: {type(table_data)}")
571
+
572
+
573
+ def convert_to_polars(
574
+ table_data: TabularData,
575
+ index_key: str | None = None,
576
+ index_type: Literal["int", "str"] | None = None,
577
+ ) -> LazyFrame:
578
+ """Convert a table to a polars LazyFrame.
579
+
580
+ Args:
581
+ table_data (TabularData): The table to convert.
582
+ index_key (str | None): The column name to use as the index of the DataFrame.
583
+ Default is None.
584
+ index_type (str | None): The type of the index column in the DataFrame.
585
+ Either 'str' or 'int'. Default is None.
586
+
587
+ Returns:
588
+ LazyFrame: Converted polars LazyFrame.
589
+ """
590
+ if isinstance(table_data, PolarsDataFrame) or isinstance(table_data, LazyFrame):
591
+ return normalize_polars_lf(
592
+ table_data,
593
+ index_key=index_key,
594
+ index_type=index_type,
595
+ )
596
+ if isinstance(table_data, DataFrame):
597
+ return convert_pandas_to_polars(
598
+ table_data,
599
+ index_key=index_key,
600
+ index_type=index_type,
601
+ )
602
+ if isinstance(table_data, AnnData):
603
+ return convert_anndata_to_polars(
604
+ table_data,
605
+ index_key=index_key,
606
+ index_type=index_type,
607
+ )
608
+ raise NgioValueError(f"Unsupported table type: {type(table_data)}")
@@ -0,0 +1,23 @@
1
+ """Tables implementations for fractal_tables v1."""
2
+
3
+ from ngio.tables.v1._condition_table import ConditionTableMeta, ConditionTableV1
4
+ from ngio.tables.v1._feature_table import FeatureTableMeta, FeatureTableV1
5
+ from ngio.tables.v1._generic_table import GenericTable
6
+ from ngio.tables.v1._roi_table import (
7
+ MaskingRoiTableV1,
8
+ MaskingRoiTableV1Meta,
9
+ RoiTableV1,
10
+ RoiTableV1Meta,
11
+ )
12
+
13
+ __all__ = [
14
+ "ConditionTableMeta",
15
+ "ConditionTableV1",
16
+ "FeatureTableMeta",
17
+ "FeatureTableV1",
18
+ "GenericTable",
19
+ "MaskingRoiTableV1",
20
+ "MaskingRoiTableV1Meta",
21
+ "RoiTableV1",
22
+ "RoiTableV1Meta",
23
+ ]
@@ -0,0 +1,71 @@
1
+ """Implementation of a generic table class."""
2
+
3
+ from ngio.tables._abstract_table import AbstractBaseTable
4
+ from ngio.tables.backends import (
5
+ BackendMeta,
6
+ TableBackend,
7
+ TabularData,
8
+ )
9
+ from ngio.utils import ZarrGroupHandler
10
+
11
+
12
+ class ConditionTableMeta(BackendMeta):
13
+ """Metadata for the condition table."""
14
+
15
+ table_version: str | None = "1"
16
+ type: str | None = "condition_table"
17
+
18
+
19
+ class ConditionTableV1(AbstractBaseTable):
20
+ """Condition table class.
21
+
22
+ This class is used to load a condition table.
23
+ The condition table is a generic table that does not
24
+ have a specific definition.
25
+
26
+ It is used to store information about the particular conditions
27
+ used to generate the data.
28
+ - How much drug was used in the experiment
29
+ - What treatment was used
30
+ - etc.
31
+ """
32
+
33
+ def __init__(
34
+ self,
35
+ table_data: TabularData | None = None,
36
+ *,
37
+ meta: ConditionTableMeta | None = None,
38
+ ) -> None:
39
+ """Initialize the ConditionTable."""
40
+ if meta is None:
41
+ meta = ConditionTableMeta()
42
+
43
+ super().__init__(
44
+ table_data=table_data,
45
+ meta=meta,
46
+ )
47
+
48
+ @staticmethod
49
+ def table_type() -> str:
50
+ """Return the type of the table."""
51
+ return "condition_table"
52
+
53
+ @staticmethod
54
+ def version() -> str:
55
+ """The generic table does not have a version.
56
+
57
+ Since does not follow a specific schema.
58
+ """
59
+ return "1"
60
+
61
+ @classmethod
62
+ def from_handler(
63
+ cls,
64
+ handler: ZarrGroupHandler,
65
+ backend: TableBackend | None = None,
66
+ ) -> "ConditionTableV1":
67
+ return cls._from_handler(
68
+ handler=handler,
69
+ backend=backend,
70
+ meta_model=ConditionTableMeta,
71
+ )