napari-ome-arrow 0.0.2__py3-none-any.whl → 0.0.4__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.
- napari_ome_arrow/_reader.py +125 -0
- napari_ome_arrow/_version.py +2 -2
- {napari_ome_arrow-0.0.2.dist-info → napari_ome_arrow-0.0.4.dist-info}/METADATA +10 -1
- napari_ome_arrow-0.0.4.dist-info/RECORD +10 -0
- napari_ome_arrow-0.0.2.dist-info/RECORD +0 -10
- {napari_ome_arrow-0.0.2.dist-info → napari_ome_arrow-0.0.4.dist-info}/WHEEL +0 -0
- {napari_ome_arrow-0.0.2.dist-info → napari_ome_arrow-0.0.4.dist-info}/entry_points.txt +0 -0
- {napari_ome_arrow-0.0.2.dist-info → napari_ome_arrow-0.0.4.dist-info}/licenses/LICENSE +0 -0
- {napari_ome_arrow-0.0.2.dist-info → napari_ome_arrow-0.0.4.dist-info}/top_level.txt +0 -0
napari_ome_arrow/_reader.py
CHANGED
|
@@ -11,6 +11,7 @@ Behavior:
|
|
|
11
11
|
|
|
12
12
|
from __future__ import annotations
|
|
13
13
|
|
|
14
|
+
import math
|
|
14
15
|
import os
|
|
15
16
|
import warnings
|
|
16
17
|
from collections.abc import Sequence
|
|
@@ -18,7 +19,9 @@ from pathlib import Path
|
|
|
18
19
|
from typing import Any, Union
|
|
19
20
|
|
|
20
21
|
import numpy as np
|
|
22
|
+
import pyarrow as pa
|
|
21
23
|
from ome_arrow.core import OMEArrow
|
|
24
|
+
from ome_arrow.meta import OME_ARROW_STRUCT
|
|
22
25
|
|
|
23
26
|
PathLike = Union[str, Path]
|
|
24
27
|
LayerData = tuple[np.ndarray, dict[str, Any], str]
|
|
@@ -170,6 +173,124 @@ def _looks_like_ome_source(path_str: str) -> bool:
|
|
|
170
173
|
)
|
|
171
174
|
|
|
172
175
|
|
|
176
|
+
# --------------------------------------------------------------------- #
|
|
177
|
+
# OME-Parquet helpers (multi-row grid)
|
|
178
|
+
# --------------------------------------------------------------------- #
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _find_ome_parquet_columns(table: pa.Table) -> list[str]:
|
|
182
|
+
"""Return struct columns matching the OME-Arrow schema."""
|
|
183
|
+
import pyarrow as pa
|
|
184
|
+
|
|
185
|
+
expected_fields = {f.name for f in OME_ARROW_STRUCT}
|
|
186
|
+
names: list[str] = []
|
|
187
|
+
for name, col in zip(table.column_names, table.columns, strict=False):
|
|
188
|
+
if (
|
|
189
|
+
pa.types.is_struct(col.type)
|
|
190
|
+
and {f.name for f in col.type} == expected_fields
|
|
191
|
+
):
|
|
192
|
+
names.append(name)
|
|
193
|
+
return names
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _enable_grid(n_layers: int) -> None:
|
|
197
|
+
"""Switch current viewer into grid view when possible."""
|
|
198
|
+
if n_layers <= 1:
|
|
199
|
+
return
|
|
200
|
+
try:
|
|
201
|
+
import napari
|
|
202
|
+
|
|
203
|
+
viewer = napari.current_viewer()
|
|
204
|
+
except Exception:
|
|
205
|
+
return
|
|
206
|
+
if viewer is None:
|
|
207
|
+
return
|
|
208
|
+
|
|
209
|
+
cols = math.ceil(math.sqrt(n_layers))
|
|
210
|
+
rows = math.ceil(n_layers / cols)
|
|
211
|
+
try:
|
|
212
|
+
viewer.grid.enabled = True
|
|
213
|
+
viewer.grid.shape = (rows, cols)
|
|
214
|
+
except Exception:
|
|
215
|
+
# grid is best-effort; ignore if unavailable
|
|
216
|
+
return
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def _read_parquet_rows(src: str, mode: str) -> list[LayerData] | None:
|
|
220
|
+
"""
|
|
221
|
+
Specialized path for multi-row OME-Parquet:
|
|
222
|
+
create one layer per row and enable napari grid view.
|
|
223
|
+
"""
|
|
224
|
+
s = src.lower()
|
|
225
|
+
if not (s.endswith((".ome.parquet", ".parquet", ".pq"))):
|
|
226
|
+
return None
|
|
227
|
+
|
|
228
|
+
try:
|
|
229
|
+
import pyarrow as pa
|
|
230
|
+
import pyarrow.parquet as pq
|
|
231
|
+
except Exception:
|
|
232
|
+
return None
|
|
233
|
+
|
|
234
|
+
table = pq.read_table(src)
|
|
235
|
+
ome_cols = _find_ome_parquet_columns(table)
|
|
236
|
+
if not ome_cols or table.num_rows <= 1:
|
|
237
|
+
return None
|
|
238
|
+
|
|
239
|
+
override = os.environ.get("NAPARI_OME_ARROW_PARQUET_COLUMN")
|
|
240
|
+
if override:
|
|
241
|
+
matched = [c for c in ome_cols if c.lower() == override.lower()]
|
|
242
|
+
selected = matched[0] if matched else ome_cols[0]
|
|
243
|
+
if not matched:
|
|
244
|
+
warnings.warn(
|
|
245
|
+
f"Column '{override}' not found in {Path(src).name}; using {selected!r}.",
|
|
246
|
+
stacklevel=2,
|
|
247
|
+
)
|
|
248
|
+
else:
|
|
249
|
+
selected = ome_cols[0]
|
|
250
|
+
|
|
251
|
+
column = table[selected]
|
|
252
|
+
layers: list[LayerData] = []
|
|
253
|
+
|
|
254
|
+
for idx in range(table.num_rows):
|
|
255
|
+
try:
|
|
256
|
+
record = column.slice(idx, 1).to_pylist()[0]
|
|
257
|
+
scalar = pa.scalar(record, type=OME_ARROW_STRUCT)
|
|
258
|
+
arr = OMEArrow(scalar).export(
|
|
259
|
+
how="numpy", dtype=np.uint16, strict=False
|
|
260
|
+
)
|
|
261
|
+
except Exception as e: # pragma: no cover - warn and skip bad rows
|
|
262
|
+
warnings.warn(
|
|
263
|
+
f"Skipping row {idx} in column '{selected}': {e}",
|
|
264
|
+
stacklevel=2,
|
|
265
|
+
)
|
|
266
|
+
continue
|
|
267
|
+
|
|
268
|
+
add_kwargs: dict[str, Any] = {
|
|
269
|
+
"name": f"{Path(src).name}[{selected}][row {idx}]"
|
|
270
|
+
}
|
|
271
|
+
if mode == "image":
|
|
272
|
+
if arr.ndim >= 5:
|
|
273
|
+
add_kwargs["channel_axis"] = 1 # TCZYX
|
|
274
|
+
elif arr.ndim == 4:
|
|
275
|
+
add_kwargs["channel_axis"] = 0 # CZYX
|
|
276
|
+
layer_type = "image"
|
|
277
|
+
else:
|
|
278
|
+
if arr.ndim == 5:
|
|
279
|
+
arr = arr[:, 0, ...]
|
|
280
|
+
elif arr.ndim == 4:
|
|
281
|
+
arr = arr[0, ...]
|
|
282
|
+
arr = _as_labels(arr)
|
|
283
|
+
add_kwargs.setdefault("opacity", 0.7)
|
|
284
|
+
layer_type = "labels"
|
|
285
|
+
|
|
286
|
+
_maybe_set_viewer_3d(arr)
|
|
287
|
+
layers.append((arr, add_kwargs, layer_type))
|
|
288
|
+
|
|
289
|
+
if layers:
|
|
290
|
+
_enable_grid(len(layers))
|
|
291
|
+
return layers or None
|
|
292
|
+
|
|
293
|
+
|
|
173
294
|
# --------------------------------------------------------------------- #
|
|
174
295
|
# napari entry point: napari_get_reader
|
|
175
296
|
# --------------------------------------------------------------------- #
|
|
@@ -319,6 +440,10 @@ def reader_function(
|
|
|
319
440
|
|
|
320
441
|
for src in paths:
|
|
321
442
|
try:
|
|
443
|
+
parquet_layers = _read_parquet_rows(src, mode)
|
|
444
|
+
if parquet_layers is not None:
|
|
445
|
+
layers.extend(parquet_layers)
|
|
446
|
+
continue
|
|
322
447
|
layers.append(_read_one(src, mode=mode))
|
|
323
448
|
except Exception as e:
|
|
324
449
|
warnings.warn(
|
napari_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.4'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 0, 4)
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: napari-ome-arrow
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.4
|
|
4
4
|
Summary: A Napari plugin for OME-Arrow and OME-Parquet bioimage data
|
|
5
5
|
License:
|
|
6
6
|
Copyright (c) 2025, Dave Bunten
|
|
@@ -74,6 +74,7 @@ Dynamic: license-file
|
|
|
74
74
|
[](https://python.org)
|
|
75
75
|
[](https://napari-hub.org/plugins/napari-ome-arrow)
|
|
76
76
|
[](https://napari.org/stable/plugins/index.html)
|
|
77
|
+
[](https://doi.org/10.5281/zenodo.17613571)
|
|
77
78
|
|
|
78
79
|
`napari-ome-arrow` is a minimal plugin for [napari](https://napari.org) that opens image data through the [OME-Arrow](https://github.com/wayscience/ome-arrow) toolkit.
|
|
79
80
|
|
|
@@ -126,6 +127,9 @@ It provides a single, explicit pathway for loading OME-style bioimage data:
|
|
|
126
127
|
- respects `NAPARI_OME_ARROW_LAYER_TYPE`, and
|
|
127
128
|
- defaults to `"image"` if the variable is not set.
|
|
128
129
|
|
|
130
|
+
- ✅ **Grid view for multi-row OME-Parquet**
|
|
131
|
+
When a Parquet file contains multiple OME-Arrow rows, each row is loaded as its own layer and the viewer is switched to napari’s grid mode. Set `NAPARI_OME_ARROW_PARQUET_COLUMN` to pick which image column to visualize.
|
|
132
|
+
|
|
129
133
|
______________________________________________________________________
|
|
130
134
|
|
|
131
135
|
This [napari] plugin was generated with [copier] using the [napari-plugin-template] (None).
|
|
@@ -175,6 +179,11 @@ NAPARI_OME_ARROW_LAYER_TYPE=image napari my_data.ome.tif
|
|
|
175
179
|
|
|
176
180
|
# Load as labels (segmentation)
|
|
177
181
|
NAPARI_OME_ARROW_LAYER_TYPE=labels napari my_labels.ome.parquet
|
|
182
|
+
|
|
183
|
+
# Pick a specific column in a multi-row OME-Parquet and show in grid mode
|
|
184
|
+
NAPARI_OME_ARROW_LAYER_TYPE=image \\
|
|
185
|
+
NAPARI_OME_ARROW_PARQUET_COLUMN=Image_FileName_OrigDNA_OMEArrow_ORIG \\
|
|
186
|
+
napari tests/data/cytodataframe/BR00117006.ome.parquet
|
|
178
187
|
```
|
|
179
188
|
|
|
180
189
|
## Contributing
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
napari_ome_arrow/__init__.py,sha256=hfpJMmD3HYHJH-0lh-esO04Pe57u8YbK4OO_qcx1b8o,236
|
|
2
|
+
napari_ome_arrow/_reader.py,sha256=JobcKfWGscMonX9u7cu_SdZtZL_Hbcj8-Uu1whuFe0o,14230
|
|
3
|
+
napari_ome_arrow/_version.py,sha256=QlXZ5JTjE_pgpDaeHk0GTExkc75xUZFmd0hA7kGYCJ0,704
|
|
4
|
+
napari_ome_arrow/napari.yaml,sha256=L64Y_YyvIlkOBgP2b8HciimyE3xmzfo9ASj5o-wu9gI,567
|
|
5
|
+
napari_ome_arrow-0.0.4.dist-info/licenses/LICENSE,sha256=RuLVTR9eFL6OX1phw5Ue8tdGrhgoG9ZErP6QrxrUlaE,1486
|
|
6
|
+
napari_ome_arrow-0.0.4.dist-info/METADATA,sha256=WC_VSHA8ZJ102297ls1_rQQThkhLbo97AcruYpPKZpU,8719
|
|
7
|
+
napari_ome_arrow-0.0.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
8
|
+
napari_ome_arrow-0.0.4.dist-info/entry_points.txt,sha256=ZNpIZF-jrixFQlUNA1kR_jCBsBDxvoIrmc_lrQqu8VE,66
|
|
9
|
+
napari_ome_arrow-0.0.4.dist-info/top_level.txt,sha256=IRsRRK6uFbyipfWh77Br3G4_6uvSYp8CL01RjMN6Mzk,17
|
|
10
|
+
napari_ome_arrow-0.0.4.dist-info/RECORD,,
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
napari_ome_arrow/__init__.py,sha256=hfpJMmD3HYHJH-0lh-esO04Pe57u8YbK4OO_qcx1b8o,236
|
|
2
|
-
napari_ome_arrow/_reader.py,sha256=98vYiA5zK19sLCywWRKXJESmd3DbULIvpDJeiIg7raA,10398
|
|
3
|
-
napari_ome_arrow/_version.py,sha256=huLsL1iGeXWQKZ8bjwDdIWC7JOkj3wnzBh-HFMZl1PY,704
|
|
4
|
-
napari_ome_arrow/napari.yaml,sha256=L64Y_YyvIlkOBgP2b8HciimyE3xmzfo9ASj5o-wu9gI,567
|
|
5
|
-
napari_ome_arrow-0.0.2.dist-info/licenses/LICENSE,sha256=RuLVTR9eFL6OX1phw5Ue8tdGrhgoG9ZErP6QrxrUlaE,1486
|
|
6
|
-
napari_ome_arrow-0.0.2.dist-info/METADATA,sha256=3WRgK92QSCD5CT0k4FcY9bcRtCqm09QZdwZ1A_zWE-E,8087
|
|
7
|
-
napari_ome_arrow-0.0.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
8
|
-
napari_ome_arrow-0.0.2.dist-info/entry_points.txt,sha256=ZNpIZF-jrixFQlUNA1kR_jCBsBDxvoIrmc_lrQqu8VE,66
|
|
9
|
-
napari_ome_arrow-0.0.2.dist-info/top_level.txt,sha256=IRsRRK6uFbyipfWh77Br3G4_6uvSYp8CL01RjMN6Mzk,17
|
|
10
|
-
napari_ome_arrow-0.0.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|