ome-arrow 0.0.3__py3-none-any.whl → 0.0.5__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.
- ome_arrow/__init__.py +8 -1
- ome_arrow/_version.py +2 -2
- ome_arrow/core.py +51 -8
- ome_arrow/export.py +58 -0
- ome_arrow/ingest.py +304 -112
- ome_arrow/view.py +46 -5
- {ome_arrow-0.0.3.dist-info → ome_arrow-0.0.5.dist-info}/METADATA +24 -10
- ome_arrow-0.0.5.dist-info/RECORD +14 -0
- ome_arrow-0.0.3.dist-info/RECORD +0 -14
- {ome_arrow-0.0.3.dist-info → ome_arrow-0.0.5.dist-info}/WHEEL +0 -0
- {ome_arrow-0.0.3.dist-info → ome_arrow-0.0.5.dist-info}/licenses/LICENSE +0 -0
- {ome_arrow-0.0.3.dist-info → ome_arrow-0.0.5.dist-info}/top_level.txt +0 -0
ome_arrow/__init__.py
CHANGED
|
@@ -4,10 +4,17 @@ Init file for ome_arrow package.
|
|
|
4
4
|
|
|
5
5
|
from ome_arrow._version import version as ome_arrow_version
|
|
6
6
|
from ome_arrow.core import OMEArrow
|
|
7
|
-
from ome_arrow.export import
|
|
7
|
+
from ome_arrow.export import (
|
|
8
|
+
to_numpy,
|
|
9
|
+
to_ome_parquet,
|
|
10
|
+
to_ome_tiff,
|
|
11
|
+
to_ome_vortex,
|
|
12
|
+
to_ome_zarr,
|
|
13
|
+
)
|
|
8
14
|
from ome_arrow.ingest import (
|
|
9
15
|
from_numpy,
|
|
10
16
|
from_ome_parquet,
|
|
17
|
+
from_ome_vortex,
|
|
11
18
|
from_ome_zarr,
|
|
12
19
|
from_tiff,
|
|
13
20
|
to_ome_arrow,
|
ome_arrow/_version.py
CHANGED
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '0.0.
|
|
32
|
-
__version_tuple__ = version_tuple = (0, 0,
|
|
31
|
+
__version__ = version = '0.0.5'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 0, 5)
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
ome_arrow/core.py
CHANGED
|
@@ -5,17 +5,23 @@ Core of the ome_arrow package, used for classes and such.
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
7
|
import pathlib
|
|
8
|
-
from typing import Any, Dict, Iterable, Optional, Tuple
|
|
8
|
+
from typing import TYPE_CHECKING, Any, Dict, Iterable, Optional, Tuple
|
|
9
9
|
|
|
10
10
|
import matplotlib
|
|
11
11
|
import numpy as np
|
|
12
12
|
import pyarrow as pa
|
|
13
|
-
import pyvista
|
|
14
13
|
|
|
15
|
-
from ome_arrow.export import
|
|
14
|
+
from ome_arrow.export import (
|
|
15
|
+
to_numpy,
|
|
16
|
+
to_ome_parquet,
|
|
17
|
+
to_ome_tiff,
|
|
18
|
+
to_ome_vortex,
|
|
19
|
+
to_ome_zarr,
|
|
20
|
+
)
|
|
16
21
|
from ome_arrow.ingest import (
|
|
17
22
|
from_numpy,
|
|
18
23
|
from_ome_parquet,
|
|
24
|
+
from_ome_vortex,
|
|
19
25
|
from_ome_zarr,
|
|
20
26
|
from_stack_pattern_path,
|
|
21
27
|
from_tiff,
|
|
@@ -25,6 +31,10 @@ from ome_arrow.transform import slice_ome_arrow
|
|
|
25
31
|
from ome_arrow.utils import describe_ome_arrow
|
|
26
32
|
from ome_arrow.view import view_matplotlib, view_pyvista
|
|
27
33
|
|
|
34
|
+
# if not in runtime, import pyvista for type hints
|
|
35
|
+
if TYPE_CHECKING:
|
|
36
|
+
import pyvista
|
|
37
|
+
|
|
28
38
|
|
|
29
39
|
class OMEArrow:
|
|
30
40
|
"""
|
|
@@ -47,6 +57,8 @@ class OMEArrow:
|
|
|
47
57
|
self,
|
|
48
58
|
data: str | dict | pa.StructScalar | "np.ndarray",
|
|
49
59
|
tcz: Tuple[int, int, int] = (0, 0, 0),
|
|
60
|
+
column_name: str = "ome_arrow",
|
|
61
|
+
row_index: int = 0,
|
|
50
62
|
) -> None:
|
|
51
63
|
"""
|
|
52
64
|
Construct an OMEArrow from:
|
|
@@ -54,6 +66,7 @@ class OMEArrow:
|
|
|
54
66
|
- a path/URL to an OME-TIFF (.tif/.tiff)
|
|
55
67
|
- a path/URL to an OME-Zarr store (.zarr / .ome.zarr)
|
|
56
68
|
- a path/URL to an OME-Parquet file (.parquet / .pq)
|
|
69
|
+
- a path/URL to a Vortex file (.vortex)
|
|
57
70
|
- a NumPy ndarray (2D-5D; interpreted
|
|
58
71
|
with from_numpy defaults)
|
|
59
72
|
- a dict already matching the OME-Arrow schema
|
|
@@ -91,7 +104,15 @@ class OMEArrow:
|
|
|
91
104
|
".parquet",
|
|
92
105
|
".pq",
|
|
93
106
|
}:
|
|
94
|
-
self.data = from_ome_parquet(
|
|
107
|
+
self.data = from_ome_parquet(
|
|
108
|
+
s, column_name=column_name, row_index=row_index
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
# Vortex
|
|
112
|
+
elif s.lower().endswith(".vortex") or path.suffix.lower() == ".vortex":
|
|
113
|
+
self.data = from_ome_vortex(
|
|
114
|
+
s, column_name=column_name, row_index=row_index
|
|
115
|
+
)
|
|
95
116
|
|
|
96
117
|
# TIFF
|
|
97
118
|
elif path.suffix.lower() in {".tif", ".tiff"} or s.lower().endswith(
|
|
@@ -110,6 +131,7 @@ class OMEArrow:
|
|
|
110
131
|
" • Bio-Formats pattern string (contains '<', '>' or '*')\n"
|
|
111
132
|
" • OME-Zarr path/URL ending with '.zarr' or '.ome.zarr'\n"
|
|
112
133
|
" • OME-Parquet file ending with '.parquet' or '.pq'\n"
|
|
134
|
+
" • Vortex file ending with '.vortex'\n"
|
|
113
135
|
" • OME-TIFF path/URL ending with '.tif' or '.tiff'"
|
|
114
136
|
)
|
|
115
137
|
|
|
@@ -134,7 +156,7 @@ class OMEArrow:
|
|
|
134
156
|
"input data must be str, dict, pa.StructScalar, or numpy.ndarray"
|
|
135
157
|
)
|
|
136
158
|
|
|
137
|
-
def export(
|
|
159
|
+
def export( # noqa: PLR0911
|
|
138
160
|
self,
|
|
139
161
|
how: str = "numpy",
|
|
140
162
|
dtype: np.dtype = np.uint16,
|
|
@@ -158,6 +180,8 @@ class OMEArrow:
|
|
|
158
180
|
parquet_column_name: str = "ome_arrow",
|
|
159
181
|
parquet_compression: str | None = "zstd",
|
|
160
182
|
parquet_metadata: dict[str, str] | None = None,
|
|
183
|
+
vortex_column_name: str = "ome_arrow",
|
|
184
|
+
vortex_metadata: dict[str, str] | None = None,
|
|
161
185
|
) -> np.array | dict | pa.StructScalar | str:
|
|
162
186
|
"""
|
|
163
187
|
Export the OME-Arrow content in a chosen representation.
|
|
@@ -171,6 +195,7 @@ class OMEArrow:
|
|
|
171
195
|
"ome-tiff" → write OME-TIFF via BioIO
|
|
172
196
|
"ome-zarr" → write OME-Zarr (OME-NGFF) via BioIO
|
|
173
197
|
"parquet" → write a single-row Parquet with one struct column
|
|
198
|
+
"vortex" → write a single-row Vortex file with one struct column
|
|
174
199
|
dtype:
|
|
175
200
|
Target dtype for "numpy"/writers (default: np.uint16).
|
|
176
201
|
strict:
|
|
@@ -192,6 +217,8 @@ class OMEArrow:
|
|
|
192
217
|
Try to embed per-channel display colors when safe; otherwise omitted.
|
|
193
218
|
parquet_*:
|
|
194
219
|
Options for Parquet export (column name, compression, file metadata).
|
|
220
|
+
vortex_*:
|
|
221
|
+
Options for Vortex export (column name, file metadata).
|
|
195
222
|
|
|
196
223
|
Returns
|
|
197
224
|
-------
|
|
@@ -202,6 +229,7 @@ class OMEArrow:
|
|
|
202
229
|
- "ome-tiff": output path (str)
|
|
203
230
|
- "ome-zarr": output path (str)
|
|
204
231
|
- "parquet": output path (str)
|
|
232
|
+
- "vortex": output path (str)
|
|
205
233
|
|
|
206
234
|
Raises
|
|
207
235
|
------
|
|
@@ -264,6 +292,18 @@ class OMEArrow:
|
|
|
264
292
|
)
|
|
265
293
|
return out
|
|
266
294
|
|
|
295
|
+
# Vortex (single row, single struct column)
|
|
296
|
+
if mode in {"ome-vortex", "omevortex", "vortex"}:
|
|
297
|
+
if not out:
|
|
298
|
+
raise ValueError("export(how='vortex') requires 'out' path.")
|
|
299
|
+
to_ome_vortex(
|
|
300
|
+
data=self.data,
|
|
301
|
+
out_path=out,
|
|
302
|
+
column_name=vortex_column_name,
|
|
303
|
+
file_metadata=vortex_metadata,
|
|
304
|
+
)
|
|
305
|
+
return out
|
|
306
|
+
|
|
267
307
|
raise ValueError(f"Unknown export method: {how}")
|
|
268
308
|
|
|
269
309
|
def info(self) -> Dict[str, Any]:
|
|
@@ -292,8 +332,8 @@ class OMEArrow:
|
|
|
292
332
|
opacity: str | float = "sigmoid",
|
|
293
333
|
clim: tuple[float, float] | None = None,
|
|
294
334
|
show_axes: bool = True,
|
|
295
|
-
scaling_values: tuple[float, float, float] | None =
|
|
296
|
-
) -> matplotlib.figure.Figure | pyvista.Plotter:
|
|
335
|
+
scaling_values: tuple[float, float, float] | None = None,
|
|
336
|
+
) -> matplotlib.figure.Figure | "pyvista.Plotter":
|
|
297
337
|
"""
|
|
298
338
|
Render an OME-Arrow record using Matplotlib or PyVista.
|
|
299
339
|
|
|
@@ -330,7 +370,10 @@ class OMEArrow:
|
|
|
330
370
|
clim: Contrast limits (``(low, high)``) for PyVista rendering.
|
|
331
371
|
show_axes: If ``True``, display axes in the PyVista scene.
|
|
332
372
|
scaling_values: Physical scale multipliers for the (x, y, z) axes used by
|
|
333
|
-
PyVista, typically to express anisotropy.
|
|
373
|
+
PyVista, typically to express anisotropy. If ``None``, uses metadata
|
|
374
|
+
scaling from the OME-Arrow record (pixels_meta.physical_size_x/y/z).
|
|
375
|
+
These scaling values will default to 1µm if metadata is missing in
|
|
376
|
+
source image metadata.
|
|
334
377
|
|
|
335
378
|
Returns:
|
|
336
379
|
matplotlib.figure.Figure | pyvista.Plotter:
|
ome_arrow/export.py
CHANGED
|
@@ -420,3 +420,61 @@ def to_ome_parquet(
|
|
|
420
420
|
compression=compression,
|
|
421
421
|
row_group_size=row_group_size,
|
|
422
422
|
)
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
def to_ome_vortex(
|
|
426
|
+
data: Dict[str, Any] | pa.StructScalar,
|
|
427
|
+
out_path: str,
|
|
428
|
+
column_name: str = "image",
|
|
429
|
+
file_metadata: Optional[Dict[str, str]] = None,
|
|
430
|
+
) -> None:
|
|
431
|
+
"""Export an OME-Arrow record to a Vortex file.
|
|
432
|
+
|
|
433
|
+
The file is written as a single-row, single-column Arrow table where the
|
|
434
|
+
column holds a struct with the OME-Arrow schema.
|
|
435
|
+
|
|
436
|
+
Args:
|
|
437
|
+
data: OME-Arrow dict or StructScalar.
|
|
438
|
+
out_path: Output path for the Vortex file.
|
|
439
|
+
column_name: Column name to store the struct.
|
|
440
|
+
file_metadata: Optional file-level metadata to attach.
|
|
441
|
+
|
|
442
|
+
Raises:
|
|
443
|
+
ImportError: If the optional `vortex-data` dependency is missing.
|
|
444
|
+
"""
|
|
445
|
+
|
|
446
|
+
try:
|
|
447
|
+
import vortex.io as vxio
|
|
448
|
+
except ImportError as exc:
|
|
449
|
+
raise ImportError(
|
|
450
|
+
"Vortex export requires the optional 'vortex-data' dependency."
|
|
451
|
+
) from exc
|
|
452
|
+
|
|
453
|
+
# 1) Normalize to a plain Python dict (works better with pyarrow builders,
|
|
454
|
+
# especially when the struct has a `null`-typed field like "masks").
|
|
455
|
+
if isinstance(data, pa.StructScalar):
|
|
456
|
+
record_dict = data.as_py()
|
|
457
|
+
else:
|
|
458
|
+
# Validate by round-tripping through a typed scalar, then back to dict.
|
|
459
|
+
record_dict = pa.scalar(data, type=OME_ARROW_STRUCT).as_py()
|
|
460
|
+
|
|
461
|
+
# 2) Build a single-row struct array from the dict, explicitly passing the schema
|
|
462
|
+
struct_array = pa.array([record_dict], type=OME_ARROW_STRUCT) # len=1
|
|
463
|
+
|
|
464
|
+
# 3) Wrap into a one-column table
|
|
465
|
+
table = pa.table({column_name: struct_array})
|
|
466
|
+
|
|
467
|
+
# 4) Attach optional file-level metadata
|
|
468
|
+
meta: Dict[bytes, bytes] = dict(table.schema.metadata or {})
|
|
469
|
+
try:
|
|
470
|
+
meta[b"ome.arrow.type"] = str(OME_ARROW_TAG_TYPE).encode("utf-8")
|
|
471
|
+
meta[b"ome.arrow.version"] = str(OME_ARROW_TAG_VERSION).encode("utf-8")
|
|
472
|
+
except Exception:
|
|
473
|
+
pass
|
|
474
|
+
if file_metadata:
|
|
475
|
+
for k, v in file_metadata.items():
|
|
476
|
+
meta[str(k).encode("utf-8")] = str(v).encode("utf-8")
|
|
477
|
+
table = table.replace_schema_metadata(meta)
|
|
478
|
+
|
|
479
|
+
# 5) Write Vortex (single row, single column)
|
|
480
|
+
vxio.write(table, str(out_path))
|
ome_arrow/ingest.py
CHANGED
|
@@ -3,7 +3,9 @@ Converting to and from OME-Arrow formats.
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
import itertools
|
|
6
|
+
import json
|
|
6
7
|
import re
|
|
8
|
+
import warnings
|
|
7
9
|
from datetime import datetime, timezone
|
|
8
10
|
from pathlib import Path
|
|
9
11
|
from typing import Any, Dict, List, Optional, Sequence, Tuple
|
|
@@ -19,6 +21,228 @@ from bioio_ome_zarr import Reader as OMEZarrReader
|
|
|
19
21
|
from ome_arrow.meta import OME_ARROW_STRUCT, OME_ARROW_TAG_TYPE, OME_ARROW_TAG_VERSION
|
|
20
22
|
|
|
21
23
|
|
|
24
|
+
def _ome_arrow_from_table(
|
|
25
|
+
table: pa.Table,
|
|
26
|
+
*,
|
|
27
|
+
column_name: Optional[str],
|
|
28
|
+
row_index: int,
|
|
29
|
+
strict_schema: bool,
|
|
30
|
+
) -> pa.StructScalar:
|
|
31
|
+
"""Extract a single OME-Arrow record from an Arrow table.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
table: Source Arrow table.
|
|
35
|
+
column_name: Column to read; auto-detected when None or invalid.
|
|
36
|
+
row_index: Row index to extract.
|
|
37
|
+
strict_schema: Require the exact OME-Arrow schema if True.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
A typed OME-Arrow StructScalar.
|
|
41
|
+
|
|
42
|
+
Raises:
|
|
43
|
+
ValueError: If the row index is out of range or no suitable column exists.
|
|
44
|
+
"""
|
|
45
|
+
if table.num_rows == 0:
|
|
46
|
+
raise ValueError("Table contains 0 rows; expected at least 1.")
|
|
47
|
+
if not (0 <= row_index < table.num_rows):
|
|
48
|
+
raise ValueError(f"row_index {row_index} out of range [0, {table.num_rows}).")
|
|
49
|
+
|
|
50
|
+
# 1) Locate the OME-Arrow column
|
|
51
|
+
def _struct_matches_ome_fields(t: pa.StructType) -> bool:
|
|
52
|
+
ome_fields = {f.name for f in OME_ARROW_STRUCT}
|
|
53
|
+
col_fields = {f.name for f in t}
|
|
54
|
+
return ome_fields == col_fields
|
|
55
|
+
|
|
56
|
+
requested_name = column_name
|
|
57
|
+
candidate_col = None
|
|
58
|
+
autodetected_name = None
|
|
59
|
+
|
|
60
|
+
if column_name is not None and column_name in table.column_names:
|
|
61
|
+
arr = table[column_name]
|
|
62
|
+
if not pa.types.is_struct(arr.type):
|
|
63
|
+
raise ValueError(f"Column '{column_name}' is not a Struct; got {arr.type}.")
|
|
64
|
+
if strict_schema and arr.type != OME_ARROW_STRUCT:
|
|
65
|
+
raise ValueError(
|
|
66
|
+
f"Column '{column_name}' schema != OME_ARROW_STRUCT.\n"
|
|
67
|
+
f"Got: {arr.type}\n"
|
|
68
|
+
f"Expect:{OME_ARROW_STRUCT}"
|
|
69
|
+
)
|
|
70
|
+
if not strict_schema and not _struct_matches_ome_fields(arr.type):
|
|
71
|
+
raise ValueError(
|
|
72
|
+
f"Column '{column_name}' does not have the expected OME-Arrow fields."
|
|
73
|
+
)
|
|
74
|
+
candidate_col = arr
|
|
75
|
+
else:
|
|
76
|
+
# Auto-detect a struct column that matches OME-Arrow fields
|
|
77
|
+
for name in table.column_names:
|
|
78
|
+
arr = table[name]
|
|
79
|
+
if pa.types.is_struct(arr.type):
|
|
80
|
+
if strict_schema and arr.type == OME_ARROW_STRUCT:
|
|
81
|
+
candidate_col = arr
|
|
82
|
+
autodetected_name = name
|
|
83
|
+
column_name = name
|
|
84
|
+
break
|
|
85
|
+
if not strict_schema and _struct_matches_ome_fields(arr.type):
|
|
86
|
+
candidate_col = arr
|
|
87
|
+
autodetected_name = name
|
|
88
|
+
column_name = name
|
|
89
|
+
break
|
|
90
|
+
if candidate_col is None:
|
|
91
|
+
if column_name is None:
|
|
92
|
+
hint = "no struct column with OME-Arrow fields was found."
|
|
93
|
+
else:
|
|
94
|
+
hint = f"column '{column_name}' not found and auto-detection failed."
|
|
95
|
+
raise ValueError(f"Could not locate an OME-Arrow struct column: {hint}")
|
|
96
|
+
|
|
97
|
+
# Emit warning if auto-detection was used
|
|
98
|
+
if autodetected_name is not None and autodetected_name != requested_name:
|
|
99
|
+
warnings.warn(
|
|
100
|
+
f"Requested column '{requested_name}' was not usable or not found. "
|
|
101
|
+
f"Auto-detected OME-Arrow column '{autodetected_name}'.",
|
|
102
|
+
UserWarning,
|
|
103
|
+
stacklevel=2,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
# 2) Extract the row as a Python dict
|
|
107
|
+
record_dict: Dict[str, Any] = candidate_col.slice(row_index, 1).to_pylist()[0]
|
|
108
|
+
|
|
109
|
+
# 3) Reconstruct a typed StructScalar using the canonical schema
|
|
110
|
+
scalar = pa.scalar(record_dict, type=OME_ARROW_STRUCT)
|
|
111
|
+
|
|
112
|
+
# Optional: soft validation via file-level metadata (if present)
|
|
113
|
+
try:
|
|
114
|
+
meta = table.schema.metadata or {}
|
|
115
|
+
meta.get(b"ome.arrow.type", b"").decode() == str(OME_ARROW_TAG_TYPE)
|
|
116
|
+
meta.get(b"ome.arrow.version", b"").decode() == str(OME_ARROW_TAG_VERSION)
|
|
117
|
+
except Exception:
|
|
118
|
+
pass
|
|
119
|
+
|
|
120
|
+
return scalar
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _normalize_unit(unit: str | None) -> str | None:
|
|
124
|
+
if not unit:
|
|
125
|
+
return None
|
|
126
|
+
u = unit.strip().lower()
|
|
127
|
+
if u in {"micrometer", "micrometre", "micron", "microns", "um", "µm"}:
|
|
128
|
+
return "µm"
|
|
129
|
+
if u in {"nanometer", "nanometre", "nm"}:
|
|
130
|
+
return "nm"
|
|
131
|
+
return unit
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _read_physical_pixel_sizes(
|
|
135
|
+
img: BioImage,
|
|
136
|
+
) -> tuple[float, float, float, str | None, bool]:
|
|
137
|
+
pps = getattr(img, "physical_pixel_sizes", None)
|
|
138
|
+
if pps is None:
|
|
139
|
+
return 1.0, 1.0, 1.0, None, False
|
|
140
|
+
|
|
141
|
+
vx = getattr(pps, "X", None) or getattr(pps, "x", None)
|
|
142
|
+
vy = getattr(pps, "Y", None) or getattr(pps, "y", None)
|
|
143
|
+
vz = getattr(pps, "Z", None) or getattr(pps, "z", None)
|
|
144
|
+
|
|
145
|
+
if vx is None and vy is None and vz is None:
|
|
146
|
+
return 1.0, 1.0, 1.0, None, False
|
|
147
|
+
|
|
148
|
+
try:
|
|
149
|
+
psize_x = float(vx or 1.0)
|
|
150
|
+
psize_y = float(vy or 1.0)
|
|
151
|
+
psize_z = float(vz or 1.0)
|
|
152
|
+
except Exception:
|
|
153
|
+
return 1.0, 1.0, 1.0, None, False
|
|
154
|
+
|
|
155
|
+
unit = getattr(pps, "unit", None) or getattr(pps, "units", None)
|
|
156
|
+
unit = _normalize_unit(str(unit)) if unit is not None else None
|
|
157
|
+
|
|
158
|
+
return psize_x, psize_y, psize_z, unit, True
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _load_zarr_attrs(zarr_path: Path) -> dict:
|
|
162
|
+
zarr_json = zarr_path / "zarr.json"
|
|
163
|
+
if zarr_json.exists():
|
|
164
|
+
try:
|
|
165
|
+
data = json.loads(zarr_json.read_text())
|
|
166
|
+
return data.get("attributes") or data.get("attrs") or {}
|
|
167
|
+
except Exception:
|
|
168
|
+
return {}
|
|
169
|
+
zattrs = zarr_path / ".zattrs"
|
|
170
|
+
if zattrs.exists():
|
|
171
|
+
try:
|
|
172
|
+
return json.loads(zattrs.read_text())
|
|
173
|
+
except Exception:
|
|
174
|
+
return {}
|
|
175
|
+
return {}
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _extract_multiscales(attrs: dict) -> list[dict]:
|
|
179
|
+
if not isinstance(attrs, dict):
|
|
180
|
+
return []
|
|
181
|
+
ome = attrs.get("ome")
|
|
182
|
+
if isinstance(ome, dict) and isinstance(ome.get("multiscales"), list):
|
|
183
|
+
return ome["multiscales"]
|
|
184
|
+
if isinstance(attrs.get("multiscales"), list):
|
|
185
|
+
return attrs["multiscales"]
|
|
186
|
+
return []
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _read_ngff_scale(zarr_path: Path) -> tuple[float, float, float, str | None] | None:
|
|
190
|
+
zarr_root = zarr_path
|
|
191
|
+
for parent in [zarr_path, *list(zarr_path.parents)]:
|
|
192
|
+
if parent.suffix.lower() in {".zarr", ".ome.zarr"}:
|
|
193
|
+
zarr_root = parent
|
|
194
|
+
break
|
|
195
|
+
|
|
196
|
+
for candidate in (zarr_path, zarr_root):
|
|
197
|
+
attrs = _load_zarr_attrs(candidate)
|
|
198
|
+
multiscales = _extract_multiscales(attrs)
|
|
199
|
+
if multiscales:
|
|
200
|
+
break
|
|
201
|
+
else:
|
|
202
|
+
return None
|
|
203
|
+
|
|
204
|
+
ms = multiscales[0]
|
|
205
|
+
axes = ms.get("axes") or []
|
|
206
|
+
datasets = ms.get("datasets") or []
|
|
207
|
+
if not axes or not datasets:
|
|
208
|
+
return None
|
|
209
|
+
|
|
210
|
+
ds = next((d for d in datasets if str(d.get("path")) == "0"), datasets[0])
|
|
211
|
+
cts = ds.get("coordinateTransformations") or []
|
|
212
|
+
scale_ct = next((ct for ct in cts if ct.get("type") == "scale"), None)
|
|
213
|
+
if not scale_ct:
|
|
214
|
+
return None
|
|
215
|
+
|
|
216
|
+
scale = scale_ct.get("scale") or []
|
|
217
|
+
if len(scale) != len(axes):
|
|
218
|
+
return None
|
|
219
|
+
|
|
220
|
+
axis_scale: dict[str, float] = {}
|
|
221
|
+
axis_unit: dict[str, str] = {}
|
|
222
|
+
for i, ax in enumerate(axes):
|
|
223
|
+
name = str(ax.get("name", "")).lower()
|
|
224
|
+
if name in {"x", "y", "z"}:
|
|
225
|
+
try:
|
|
226
|
+
axis_scale[name] = float(scale[i])
|
|
227
|
+
except Exception:
|
|
228
|
+
continue
|
|
229
|
+
unit = _normalize_unit(ax.get("unit"))
|
|
230
|
+
if unit:
|
|
231
|
+
axis_unit[name] = unit
|
|
232
|
+
|
|
233
|
+
if not axis_scale:
|
|
234
|
+
return None
|
|
235
|
+
|
|
236
|
+
psize_x = axis_scale.get("x", 1.0)
|
|
237
|
+
psize_y = axis_scale.get("y", 1.0)
|
|
238
|
+
psize_z = axis_scale.get("z", 1.0)
|
|
239
|
+
|
|
240
|
+
units = [axis_unit.get(a) for a in ("x", "y", "z") if axis_unit.get(a)]
|
|
241
|
+
unit = units[0] if units and len(set(units)) == 1 else None
|
|
242
|
+
|
|
243
|
+
return psize_x, psize_y, psize_z, unit
|
|
244
|
+
|
|
245
|
+
|
|
22
246
|
def to_ome_arrow(
|
|
23
247
|
type_: str = OME_ARROW_TAG_TYPE,
|
|
24
248
|
version: str = OME_ARROW_TAG_VERSION,
|
|
@@ -337,13 +561,8 @@ def from_tiff(
|
|
|
337
561
|
if size_x <= 0 or size_y <= 0:
|
|
338
562
|
raise ValueError("Image must have positive Y and X dims.")
|
|
339
563
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
psize_x = float(getattr(pps, "X", None) or 1.0)
|
|
343
|
-
psize_y = float(getattr(pps, "Y", None) or 1.0)
|
|
344
|
-
psize_z = float(getattr(pps, "Z", None) or 1.0)
|
|
345
|
-
except Exception:
|
|
346
|
-
psize_x = psize_y = psize_z = 1.0
|
|
564
|
+
psize_x, psize_y, psize_z, unit, _pps_valid = _read_physical_pixel_sizes(img)
|
|
565
|
+
psize_unit = unit or "µm"
|
|
347
566
|
|
|
348
567
|
# --- NEW: coerce top-level strings --------------------------------
|
|
349
568
|
img_id = str(image_id or p.stem)
|
|
@@ -393,7 +612,7 @@ def from_tiff(
|
|
|
393
612
|
physical_size_x=psize_x,
|
|
394
613
|
physical_size_y=psize_y,
|
|
395
614
|
physical_size_z=psize_z,
|
|
396
|
-
physical_size_unit=
|
|
615
|
+
physical_size_unit=psize_unit,
|
|
397
616
|
channels=channels,
|
|
398
617
|
planes=planes,
|
|
399
618
|
masks=None,
|
|
@@ -409,6 +628,20 @@ def from_stack_pattern_path(
|
|
|
409
628
|
image_id: Optional[str] = None,
|
|
410
629
|
name: Optional[str] = None,
|
|
411
630
|
) -> pa.StructScalar:
|
|
631
|
+
"""Build an OME-Arrow record from a filename pattern describing a stack.
|
|
632
|
+
|
|
633
|
+
Args:
|
|
634
|
+
pattern_path: Path or pattern string describing the stack layout.
|
|
635
|
+
default_dim_for_unspecified: Dimension to use when tokens lack a dim.
|
|
636
|
+
map_series_to: Dimension to map series tokens to (e.g., "T"), or None.
|
|
637
|
+
clamp_to_uint16: Whether to clamp pixel values to uint16.
|
|
638
|
+
channel_names: Optional list of channel names to apply.
|
|
639
|
+
image_id: Optional image identifier override.
|
|
640
|
+
name: Optional display name override.
|
|
641
|
+
|
|
642
|
+
Returns:
|
|
643
|
+
A validated OME-Arrow StructScalar describing the stack.
|
|
644
|
+
"""
|
|
412
645
|
path = Path(pattern_path)
|
|
413
646
|
folder = path.parent
|
|
414
647
|
line = path.name.strip()
|
|
@@ -740,13 +973,15 @@ def from_ome_zarr(
|
|
|
740
973
|
if size_x <= 0 or size_y <= 0:
|
|
741
974
|
raise ValueError("Image must have positive Y and X dimensions.")
|
|
742
975
|
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
976
|
+
psize_x, psize_y, psize_z, unit, pps_valid = _read_physical_pixel_sizes(img)
|
|
977
|
+
psize_unit = unit or "µm"
|
|
978
|
+
|
|
979
|
+
if not pps_valid:
|
|
980
|
+
ngff_scale = _read_ngff_scale(p)
|
|
981
|
+
if ngff_scale is not None:
|
|
982
|
+
psize_x, psize_y, psize_z, unit = ngff_scale
|
|
983
|
+
if unit:
|
|
984
|
+
psize_unit = unit
|
|
750
985
|
|
|
751
986
|
img_id = str(image_id or p.stem)
|
|
752
987
|
display_name = str(name or p.name)
|
|
@@ -804,7 +1039,7 @@ def from_ome_zarr(
|
|
|
804
1039
|
physical_size_x=psize_x,
|
|
805
1040
|
physical_size_y=psize_y,
|
|
806
1041
|
physical_size_z=psize_z,
|
|
807
|
-
physical_size_unit=
|
|
1042
|
+
physical_size_unit=psize_unit,
|
|
808
1043
|
channels=channels,
|
|
809
1044
|
planes=planes,
|
|
810
1045
|
masks=None,
|
|
@@ -818,115 +1053,72 @@ def from_ome_parquet(
|
|
|
818
1053
|
row_index: int = 0,
|
|
819
1054
|
strict_schema: bool = False,
|
|
820
1055
|
) -> pa.StructScalar:
|
|
821
|
-
"""
|
|
822
|
-
Read an OME-Arrow record from a Parquet file and return a typed StructScalar.
|
|
823
|
-
|
|
824
|
-
Expected layout (as produced by `to_ome_parquet`):
|
|
825
|
-
- single Parquet file
|
|
826
|
-
- a single column (default name "ome_arrow") of `OME_ARROW_STRUCT` type
|
|
827
|
-
- one row (row_index=0)
|
|
1056
|
+
"""Read an OME-Arrow record from a Parquet file.
|
|
828
1057
|
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
1058
|
+
Args:
|
|
1059
|
+
parquet_path: Path to the Parquet file.
|
|
1060
|
+
column_name: Column to read; auto-detected when None or invalid.
|
|
1061
|
+
row_index: Row index to extract.
|
|
1062
|
+
strict_schema: Require the exact OME-Arrow schema if True.
|
|
834
1063
|
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
parquet_path : str | Path
|
|
838
|
-
Path to the .parquet file.
|
|
839
|
-
column_name : Optional[str], default "ome_arrow"
|
|
840
|
-
Name of the column that stores the OME-Arrow struct. If None, auto-detect.
|
|
841
|
-
row_index : int, default 0
|
|
842
|
-
Which row to read if the table contains multiple rows.
|
|
843
|
-
strict_schema : bool, default False
|
|
844
|
-
If True, require the column's type to equal `OME_ARROW_STRUCT` exactly.
|
|
845
|
-
If False, we only require the column to be a Struct with the same field
|
|
846
|
-
names (order can vary).
|
|
1064
|
+
Returns:
|
|
1065
|
+
A typed OME-Arrow StructScalar.
|
|
847
1066
|
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
A validated OME-Arrow struct scalar.
|
|
852
|
-
|
|
853
|
-
Raises
|
|
854
|
-
------
|
|
855
|
-
FileNotFoundError
|
|
856
|
-
If the file does not exist.
|
|
857
|
-
ValueError
|
|
858
|
-
If a suitable column/row cannot be found or schema checks fail.
|
|
1067
|
+
Raises:
|
|
1068
|
+
FileNotFoundError: If the Parquet path does not exist.
|
|
1069
|
+
ValueError: If the row index is out of range or no suitable column exists.
|
|
859
1070
|
"""
|
|
860
1071
|
p = Path(parquet_path)
|
|
861
1072
|
if not p.exists():
|
|
862
1073
|
raise FileNotFoundError(f"No such file: {p}")
|
|
863
1074
|
|
|
864
1075
|
table = pq.read_table(p)
|
|
1076
|
+
return _ome_arrow_from_table(
|
|
1077
|
+
table,
|
|
1078
|
+
column_name=column_name,
|
|
1079
|
+
row_index=row_index,
|
|
1080
|
+
strict_schema=strict_schema,
|
|
1081
|
+
)
|
|
865
1082
|
|
|
866
|
-
if table.num_rows == 0:
|
|
867
|
-
raise ValueError("Parquet file contains 0 rows; expected at least 1.")
|
|
868
|
-
if not (0 <= row_index < table.num_rows):
|
|
869
|
-
raise ValueError(f"row_index {row_index} out of range [0, {table.num_rows}).")
|
|
870
|
-
|
|
871
|
-
# 1) Locate the OME-Arrow column
|
|
872
|
-
def _struct_matches_ome_fields(t: pa.StructType) -> bool:
|
|
873
|
-
ome_fields = {f.name for f in OME_ARROW_STRUCT}
|
|
874
|
-
col_fields = {f.name for f in t}
|
|
875
|
-
return ome_fields == col_fields
|
|
876
1083
|
|
|
877
|
-
|
|
1084
|
+
def from_ome_vortex(
|
|
1085
|
+
vortex_path: str | Path,
|
|
1086
|
+
*,
|
|
1087
|
+
column_name: Optional[str] = "ome_arrow",
|
|
1088
|
+
row_index: int = 0,
|
|
1089
|
+
strict_schema: bool = False,
|
|
1090
|
+
) -> pa.StructScalar:
|
|
1091
|
+
"""Read an OME-Arrow record from a Vortex file.
|
|
878
1092
|
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
raise ValueError(
|
|
885
|
-
f"Column '{column_name}' schema != OME_ARROW_STRUCT.\n"
|
|
886
|
-
f"Got: {arr.type}\n"
|
|
887
|
-
f"Expect:{OME_ARROW_STRUCT}"
|
|
888
|
-
)
|
|
889
|
-
if not strict_schema and not _struct_matches_ome_fields(arr.type):
|
|
890
|
-
raise ValueError(
|
|
891
|
-
f"Column '{column_name}' does not have the expected OME-Arrow fields."
|
|
892
|
-
)
|
|
893
|
-
candidate_col = arr
|
|
894
|
-
else:
|
|
895
|
-
# Auto-detect a struct column that matches OME-Arrow fields
|
|
896
|
-
for name in table.column_names:
|
|
897
|
-
arr = table[name]
|
|
898
|
-
if pa.types.is_struct(arr.type):
|
|
899
|
-
if strict_schema and arr.type == OME_ARROW_STRUCT:
|
|
900
|
-
candidate_col = arr
|
|
901
|
-
column_name = name
|
|
902
|
-
break
|
|
903
|
-
if not strict_schema and _struct_matches_ome_fields(arr.type):
|
|
904
|
-
candidate_col = arr
|
|
905
|
-
column_name = name
|
|
906
|
-
break
|
|
907
|
-
if candidate_col is None:
|
|
908
|
-
if column_name is None:
|
|
909
|
-
hint = "no struct column with OME-Arrow fields was found."
|
|
910
|
-
else:
|
|
911
|
-
hint = f"column '{column_name}' not found and auto-detection failed."
|
|
912
|
-
raise ValueError(f"Could not locate an OME-Arrow struct column: {hint}")
|
|
1093
|
+
Args:
|
|
1094
|
+
vortex_path: Path to the Vortex file.
|
|
1095
|
+
column_name: Column to read; auto-detected when None or invalid.
|
|
1096
|
+
row_index: Row index to extract.
|
|
1097
|
+
strict_schema: Require the exact OME-Arrow schema if True.
|
|
913
1098
|
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
record_dict: Dict[str, Any] = candidate_col.slice(row_index, 1).to_pylist()[0]
|
|
1099
|
+
Returns:
|
|
1100
|
+
A typed OME-Arrow StructScalar.
|
|
917
1101
|
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
1102
|
+
Raises:
|
|
1103
|
+
FileNotFoundError: If the Vortex path does not exist.
|
|
1104
|
+
ImportError: If the optional `vortex-data` dependency is missing.
|
|
1105
|
+
ValueError: If the row index is out of range or no suitable column exists.
|
|
1106
|
+
"""
|
|
1107
|
+
p = Path(vortex_path)
|
|
1108
|
+
if not p.exists():
|
|
1109
|
+
raise FileNotFoundError(f"No such file: {p}")
|
|
921
1110
|
|
|
922
|
-
# Optional: soft validation via file-level metadata (if present)
|
|
923
1111
|
try:
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
1112
|
+
import vortex
|
|
1113
|
+
except ImportError as exc:
|
|
1114
|
+
raise ImportError(
|
|
1115
|
+
"Vortex support requires the optional 'vortex-data' dependency."
|
|
1116
|
+
) from exc
|
|
1117
|
+
|
|
1118
|
+
table = vortex.open(str(p)).to_arrow().read_all()
|
|
1119
|
+
return _ome_arrow_from_table(
|
|
1120
|
+
table,
|
|
1121
|
+
column_name=column_name,
|
|
1122
|
+
row_index=row_index,
|
|
1123
|
+
strict_schema=strict_schema,
|
|
1124
|
+
)
|
ome_arrow/view.py
CHANGED
|
@@ -2,16 +2,27 @@
|
|
|
2
2
|
Viewing utilities for OME-Arrow data.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
5
7
|
import contextlib
|
|
8
|
+
import warnings
|
|
9
|
+
from typing import TYPE_CHECKING
|
|
6
10
|
|
|
7
11
|
import matplotlib.pyplot as plt
|
|
8
12
|
import numpy as np
|
|
9
13
|
import pyarrow as pa
|
|
10
|
-
import pyvista as pv
|
|
11
14
|
from matplotlib.axes import Axes
|
|
12
15
|
from matplotlib.figure import Figure
|
|
13
16
|
from matplotlib.image import AxesImage
|
|
14
17
|
|
|
18
|
+
try: # optional dependency
|
|
19
|
+
import pyvista as pv
|
|
20
|
+
except ImportError: # pragma: no cover - exercised when viz extra missing
|
|
21
|
+
pv = None # type: ignore[assignment]
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
import pyvista
|
|
25
|
+
|
|
15
26
|
|
|
16
27
|
def view_matplotlib(
|
|
17
28
|
data: dict[str, object] | pa.StructScalar,
|
|
@@ -22,6 +33,23 @@ def view_matplotlib(
|
|
|
22
33
|
cmap: str = "gray",
|
|
23
34
|
show: bool = True,
|
|
24
35
|
) -> tuple[Figure, Axes, AxesImage]:
|
|
36
|
+
"""Render a single (t, c, z) plane with Matplotlib.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
data: OME-Arrow row or dict containing pixels_meta and planes.
|
|
40
|
+
tcz: (t, c, z) indices of the plane to render.
|
|
41
|
+
autoscale: If True, infer vmin/vmax from the image data.
|
|
42
|
+
vmin: Explicit lower display limit for intensity scaling.
|
|
43
|
+
vmax: Explicit upper display limit for intensity scaling.
|
|
44
|
+
cmap: Matplotlib colormap name.
|
|
45
|
+
show: Whether to display the plot immediately.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
A tuple of (figure, axes, image) from Matplotlib.
|
|
49
|
+
|
|
50
|
+
Raises:
|
|
51
|
+
ValueError: If the requested plane is missing or pixel sizes mismatch.
|
|
52
|
+
"""
|
|
25
53
|
if isinstance(data, pa.StructScalar):
|
|
26
54
|
data = data.as_py()
|
|
27
55
|
|
|
@@ -63,6 +91,21 @@ def view_matplotlib(
|
|
|
63
91
|
return fig, ax, im
|
|
64
92
|
|
|
65
93
|
|
|
94
|
+
def _require_pyvista() -> "pyvista":
|
|
95
|
+
"""
|
|
96
|
+
Ensure PyVista is available, raising a helpful error otherwise.
|
|
97
|
+
"""
|
|
98
|
+
if pv is None:
|
|
99
|
+
msg = (
|
|
100
|
+
"PyVista-based visualization requires the optional 'viz' extras. "
|
|
101
|
+
"Install with `pip install ome-arrow[viz]` to enable 3D viewing."
|
|
102
|
+
)
|
|
103
|
+
warnings.warn(msg, RuntimeWarning)
|
|
104
|
+
raise RuntimeError(msg)
|
|
105
|
+
|
|
106
|
+
return pv
|
|
107
|
+
|
|
108
|
+
|
|
66
109
|
def view_pyvista(
|
|
67
110
|
data: dict | pa.StructScalar,
|
|
68
111
|
c: int = 0,
|
|
@@ -77,16 +120,14 @@ def view_pyvista(
|
|
|
77
120
|
percentile_clim: tuple[float, float] = (1.0, 99.9), # robust contrast
|
|
78
121
|
sampling_scale: float = 0.5, # smaller = denser rays (sharper, slower)
|
|
79
122
|
show: bool = True,
|
|
80
|
-
) ->
|
|
123
|
+
) -> "pyvista.Plotter":
|
|
81
124
|
"""
|
|
82
125
|
Jupyter-inline interactive volume view using PyVista backends.
|
|
83
126
|
Tries 'trame' → 'html' → 'static' when backend='auto'.
|
|
84
127
|
|
|
85
128
|
sampling_scale controls ray step via the mapper after add_volume.
|
|
86
129
|
"""
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
import numpy as np
|
|
130
|
+
pv = _require_pyvista()
|
|
90
131
|
|
|
91
132
|
# ---- unwrap OME-Arrow row
|
|
92
133
|
row = data.as_py() if isinstance(data, pa.StructScalar) else data
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ome-arrow
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.5
|
|
4
4
|
Summary: Using OME specifications with Apache Arrow for fast, queryable, and language agnostic bioimage data.
|
|
5
5
|
Author: Dave Bunten
|
|
6
6
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
@@ -16,25 +16,29 @@ Requires-Dist: bioio-ome-tiff>=1.4
|
|
|
16
16
|
Requires-Dist: bioio-ome-zarr>=3.0.3
|
|
17
17
|
Requires-Dist: bioio-tifffile>=1.3
|
|
18
18
|
Requires-Dist: fire>=0.7
|
|
19
|
-
Requires-Dist: ipywidgets>=8.1.8
|
|
20
|
-
Requires-Dist: jupyterlab-widgets>=3.0.16
|
|
21
19
|
Requires-Dist: matplotlib>=3.10.7
|
|
22
20
|
Requires-Dist: numpy>=2.2.6
|
|
23
21
|
Requires-Dist: pandas>=2.2.3
|
|
24
22
|
Requires-Dist: pillow>=12
|
|
25
23
|
Requires-Dist: pyarrow>=22
|
|
26
|
-
|
|
27
|
-
Requires-Dist:
|
|
28
|
-
Requires-Dist:
|
|
29
|
-
Requires-Dist:
|
|
24
|
+
Provides-Extra: viz
|
|
25
|
+
Requires-Dist: ipywidgets>=8.1.8; extra == "viz"
|
|
26
|
+
Requires-Dist: jupyterlab-widgets>=3.0.16; extra == "viz"
|
|
27
|
+
Requires-Dist: pyvista>=0.46.4; extra == "viz"
|
|
28
|
+
Requires-Dist: trame>=3.12; extra == "viz"
|
|
29
|
+
Requires-Dist: trame-vtk>=2.10; extra == "viz"
|
|
30
|
+
Requires-Dist: trame-vuetify>=3.1; extra == "viz"
|
|
31
|
+
Provides-Extra: vortex
|
|
32
|
+
Requires-Dist: vortex-data>=0.56; extra == "vortex"
|
|
30
33
|
Dynamic: license-file
|
|
31
34
|
|
|
32
|
-
<img
|
|
35
|
+
<img width="600" src="https://raw.githubusercontent.com/wayscience/ome-arrow/main/docs/src/_static/logo.png?raw=true">
|
|
33
36
|
|
|
34
37
|

|
|
35
38
|
[](https://github.com/wayscience/ome-arrow/actions/workflows/run-tests.yml?query=branch%3Amain)
|
|
36
39
|
[](https://github.com/astral-sh/ruff)
|
|
37
40
|
[](https://github.com/astral-sh/uv)
|
|
41
|
+
[](https://doi.org/10.5281/zenodo.17664969)
|
|
38
42
|
|
|
39
43
|
# Open, interoperable, and queryable microscopy images with OME Arrow
|
|
40
44
|
|
|
@@ -52,6 +56,13 @@ OME Arrow enables image data to be stored alongside metadata or derived data suc
|
|
|
52
56
|
Images in OME Arrow are composed of mutlilayer [structs](https://arrow.apache.org/docs/python/generated/pyarrow.struct.html) so they may be stored as values within tables.
|
|
53
57
|
This means you can store, query, and build relationships on data from the same location using any system which is compatible with Apache Arrow (including Parquet) through common data interfaces (such as SQL and DuckDB).
|
|
54
58
|
|
|
59
|
+
## Project focus
|
|
60
|
+
|
|
61
|
+
This package is intentionally dedicated to work at a per-image level and not large batch handling (though it may be used for those purposes by users or in other projects).
|
|
62
|
+
|
|
63
|
+
- For visualizing OME Arrow and OME Parquet data in Napari, please see the [`napari-ome-arrow`](https://github.com/WayScience/napari-ome-arrow) Napari plugin.
|
|
64
|
+
- For more comprehensive handling of many images and features in the context of the OME Parquet format please see the [`CytoDataFrame`](https://github.com/cytomining/CytoDataFrame) project (and relevant [example notebook](https://github.com/cytomining/CytoDataFrame/blob/main/docs/src/examples/cytodataframe_at_a_glance.ipynb)).
|
|
65
|
+
|
|
55
66
|
## Installation
|
|
56
67
|
|
|
57
68
|
Install OME Arrow from PyPI or from source:
|
|
@@ -89,12 +100,15 @@ oa_image.info()
|
|
|
89
100
|
oa_image.view(how="matplotlib")
|
|
90
101
|
|
|
91
102
|
# Display the image with pyvista
|
|
92
|
-
# (great for ZYX 3D images).
|
|
103
|
+
# (great for ZYX 3D images; install extras: `pip install 'ome-arrow[viz]'`).
|
|
93
104
|
oa_image.view(how="pyvista")
|
|
94
105
|
|
|
95
106
|
# Export to OME-Parquet.
|
|
96
107
|
# We can also export OME-TIFF, OME-Zarr or NumPy arrays.
|
|
97
108
|
oa_image.export(how="ome-parquet", out="your_image.ome.parquet")
|
|
109
|
+
|
|
110
|
+
# Export to Vortex (install extras: `pip install 'ome-arrow[vortex]'`).
|
|
111
|
+
oa_image.export(how="vortex", out="your_image.vortex")
|
|
98
112
|
```
|
|
99
113
|
|
|
100
114
|
## Contributing, Development, and Testing
|
|
@@ -107,5 +121,5 @@ OME Arrow is used or inspired by the following projects, check them out!
|
|
|
107
121
|
|
|
108
122
|
- [`napari-ome-arrow`](https://github.com/WayScience/napari-ome-arrow): enables you to view OME Arrow and related images.
|
|
109
123
|
- [`nViz`](https://github.com/WayScience/nViz): focuses on ingesting and visualizing various 3D image data.
|
|
110
|
-
- [`CytoDataFrame`](https://github.com/cytomining/CytoDataFrame): provides a DataFrame-like experience for viewing feature and microscopy image data within Jupyter notebook interfaces.
|
|
124
|
+
- [`CytoDataFrame`](https://github.com/cytomining/CytoDataFrame): provides a DataFrame-like experience for viewing feature and microscopy image data within Jupyter notebook interfaces and creating OME Parquet files.
|
|
111
125
|
- [`coSMicQC`](https://github.com/cytomining/coSMicQC): performs quality control on microscopy feature datasets, visualized using CytoDataFrames.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
ome_arrow/__init__.py,sha256=WWenJP9XxLZNGQPVOEFBDlDM1kSvj_QdHssrET6UuNQ,644
|
|
2
|
+
ome_arrow/_version.py,sha256=YRV1ohn6CdKEhsUOmFFMmr5UTjMv4Ydw3WJGxF2BHBs,704
|
|
3
|
+
ome_arrow/core.py,sha256=fgEFOwckYi3asosEUhGB8UL9Q93hO56H6qw9fUczFO8,19946
|
|
4
|
+
ome_arrow/export.py,sha256=e9Nx25bD2K51gQng-4rUXM4v1l8-K1YkxGjWKImFrJ4,16972
|
|
5
|
+
ome_arrow/ingest.py,sha256=Vt9hljI718vR-qpJXH4jk4Shs1OtFPfIVhmsILkbNxQ,38714
|
|
6
|
+
ome_arrow/meta.py,sha256=qeD0e_ItAQyZDT7ypkBU0rBh9oHIu2ziz9MCfPpPp9g,4199
|
|
7
|
+
ome_arrow/transform.py,sha256=0275_Mn1mlGXSWJ86llch8JoJyvqEOfvG-ub1dUWFNI,5997
|
|
8
|
+
ome_arrow/utils.py,sha256=XHovcqmjqoiBpKvXY47-_yUwf07f8zVE_F9BR_VKaPU,2383
|
|
9
|
+
ome_arrow/view.py,sha256=B2ZEE8LWlYzTBk0Fa19GHC1seEN_IdgOkfmJXcLRG2U,10691
|
|
10
|
+
ome_arrow-0.0.5.dist-info/licenses/LICENSE,sha256=9-2Pyhu3vTt2RJU8DorHQtHeNO_e5RLeFJTyOU4hOi4,1508
|
|
11
|
+
ome_arrow-0.0.5.dist-info/METADATA,sha256=l_CqAdgv7NFsNT48eDaC3s2uvKpg9g2FDgA3TAtricI,6110
|
|
12
|
+
ome_arrow-0.0.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
13
|
+
ome_arrow-0.0.5.dist-info/top_level.txt,sha256=aWOtkGXo_pfU-yy82guzGhz8Zh2h2nFl8Kc5qdzMGuE,10
|
|
14
|
+
ome_arrow-0.0.5.dist-info/RECORD,,
|
ome_arrow-0.0.3.dist-info/RECORD
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
ome_arrow/__init__.py,sha256=DfQsw8l0mx1Qt3YiiMv2SUljKETP3wS5hrD5eBbjMDM,583
|
|
2
|
-
ome_arrow/_version.py,sha256=pBZsQt6tlL02W-ri--X_4JCubpAK7jjCSnOmUp_isjc,704
|
|
3
|
-
ome_arrow/core.py,sha256=NUCV9KUH3yCOlpetRS5NNVG_phodutE1F2ujDBPhHgY,18351
|
|
4
|
-
ome_arrow/export.py,sha256=CCTnEdHko4Z0i5LEHuNGFLznWSsPyAFcS42H5nHU22Q,14875
|
|
5
|
-
ome_arrow/ingest.py,sha256=zZz94LaLOpmoxnryLeoPsaWV0EzkYkGFizYSVcbd5w8,33016
|
|
6
|
-
ome_arrow/meta.py,sha256=qeD0e_ItAQyZDT7ypkBU0rBh9oHIu2ziz9MCfPpPp9g,4199
|
|
7
|
-
ome_arrow/transform.py,sha256=0275_Mn1mlGXSWJ86llch8JoJyvqEOfvG-ub1dUWFNI,5997
|
|
8
|
-
ome_arrow/utils.py,sha256=XHovcqmjqoiBpKvXY47-_yUwf07f8zVE_F9BR_VKaPU,2383
|
|
9
|
-
ome_arrow/view.py,sha256=DT8i56uV8Rw22KkqwjPPPKWJWNtfgR9OkI8Qj1WD8Ds,9355
|
|
10
|
-
ome_arrow-0.0.3.dist-info/licenses/LICENSE,sha256=9-2Pyhu3vTt2RJU8DorHQtHeNO_e5RLeFJTyOU4hOi4,1508
|
|
11
|
-
ome_arrow-0.0.3.dist-info/METADATA,sha256=VrhOZ3ENlUTdd3smTk_pCN8ptbuZJbhDwuCxdLu8UDc,4910
|
|
12
|
-
ome_arrow-0.0.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
13
|
-
ome_arrow-0.0.3.dist-info/top_level.txt,sha256=aWOtkGXo_pfU-yy82guzGhz8Zh2h2nFl8Kc5qdzMGuE,10
|
|
14
|
-
ome_arrow-0.0.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|