napari-ome-arrow 0.0.2__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/__init__.py +15 -0
- napari_ome_arrow/_reader.py +331 -0
- napari_ome_arrow/_version.py +34 -0
- napari_ome_arrow/napari.yaml +23 -0
- napari_ome_arrow-0.0.2.dist-info/METADATA +197 -0
- napari_ome_arrow-0.0.2.dist-info/RECORD +10 -0
- napari_ome_arrow-0.0.2.dist-info/WHEEL +5 -0
- napari_ome_arrow-0.0.2.dist-info/entry_points.txt +2 -0
- napari_ome_arrow-0.0.2.dist-info/licenses/LICENSE +28 -0
- napari_ome_arrow-0.0.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Minimal napari reader for OME-Arrow sources (stack patterns, OME-Zarr, OME-Parquet,
|
|
3
|
+
OME-TIFF) plus a fallback .npy example.
|
|
4
|
+
|
|
5
|
+
Behavior:
|
|
6
|
+
* If NAPARI_OME_ARROW_LAYER_TYPE is set to "image" or "labels",
|
|
7
|
+
that choice is used.
|
|
8
|
+
* Otherwise, in a GUI/Qt context, the user is prompted with a modal
|
|
9
|
+
dialog asking whether to load as image or labels.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import os
|
|
15
|
+
import warnings
|
|
16
|
+
from collections.abc import Sequence
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Any, Union
|
|
19
|
+
|
|
20
|
+
import numpy as np
|
|
21
|
+
from ome_arrow.core import OMEArrow
|
|
22
|
+
|
|
23
|
+
PathLike = Union[str, Path]
|
|
24
|
+
LayerData = tuple[np.ndarray, dict[str, Any], str]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _maybe_set_viewer_3d(arr: np.ndarray) -> None:
|
|
28
|
+
"""
|
|
29
|
+
If the array has a Z axis with size > 1, switch the current napari viewer
|
|
30
|
+
to 3D (ndisplay = 3).
|
|
31
|
+
|
|
32
|
+
Assumes OME-Arrow's TCZYX convention or a subset, i.e., Z is always
|
|
33
|
+
the third-from-last axis. No-op if there's no active viewer.
|
|
34
|
+
"""
|
|
35
|
+
# Need at least (Z, Y, X)
|
|
36
|
+
if arr.ndim < 3:
|
|
37
|
+
return
|
|
38
|
+
|
|
39
|
+
z_size = arr.shape[-3]
|
|
40
|
+
if z_size <= 1:
|
|
41
|
+
return
|
|
42
|
+
|
|
43
|
+
try:
|
|
44
|
+
import napari
|
|
45
|
+
|
|
46
|
+
viewer = napari.current_viewer()
|
|
47
|
+
except Exception:
|
|
48
|
+
# no viewer / not in GUI context → silently skip
|
|
49
|
+
return
|
|
50
|
+
|
|
51
|
+
if viewer is not None:
|
|
52
|
+
viewer.dims.ndisplay = 3
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# --------------------------------------------------------------------- #
|
|
56
|
+
# Mode selection (env var + GUI prompt)
|
|
57
|
+
# --------------------------------------------------------------------- #
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _get_layer_mode(sample_path: str) -> str:
|
|
61
|
+
"""
|
|
62
|
+
Decide whether to load as 'image' or 'labels'.
|
|
63
|
+
|
|
64
|
+
Priority:
|
|
65
|
+
1. NAPARI_OME_ARROW_LAYER_TYPE env var (image/labels)
|
|
66
|
+
2. If in a Qt GUI context, show a modal dialog asking the user
|
|
67
|
+
3. Otherwise, default to 'image'
|
|
68
|
+
"""
|
|
69
|
+
mode = os.environ.get("NAPARI_OME_ARROW_LAYER_TYPE")
|
|
70
|
+
if mode is not None:
|
|
71
|
+
mode = mode.lower()
|
|
72
|
+
if mode in {"image", "labels"}:
|
|
73
|
+
return mode
|
|
74
|
+
raise RuntimeError(
|
|
75
|
+
f"Invalid NAPARI_OME_ARROW_LAYER_TYPE={mode!r}; expected 'image' or 'labels'."
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# No env var → try to prompt in GUI context
|
|
79
|
+
try:
|
|
80
|
+
from qtpy import QtWidgets
|
|
81
|
+
except Exception:
|
|
82
|
+
# no Qt, probably headless: default to image
|
|
83
|
+
warnings.warn(
|
|
84
|
+
"NAPARI_OME_ARROW_LAYER_TYPE not set and Qt not available; "
|
|
85
|
+
"defaulting to 'image'.",
|
|
86
|
+
stacklevel=2,
|
|
87
|
+
)
|
|
88
|
+
return "image"
|
|
89
|
+
|
|
90
|
+
app = QtWidgets.QApplication.instance()
|
|
91
|
+
if app is None:
|
|
92
|
+
# Again, likely headless or non-Qt usage
|
|
93
|
+
warnings.warn(
|
|
94
|
+
"NAPARI_OME_ARROW_LAYER_TYPE not set and no QApplication instance; "
|
|
95
|
+
"defaulting to 'image'.",
|
|
96
|
+
stacklevel=2,
|
|
97
|
+
)
|
|
98
|
+
return "image"
|
|
99
|
+
|
|
100
|
+
# Build a simple modal choice dialog
|
|
101
|
+
msg = QtWidgets.QMessageBox()
|
|
102
|
+
msg.setWindowTitle("napari-ome-arrow: choose layer type")
|
|
103
|
+
msg.setText(
|
|
104
|
+
f"<p align='left'>How should '{Path(sample_path).name}' be loaded?<br><br>"
|
|
105
|
+
"You can also set NAPARI_OME_ARROW_LAYER_TYPE=image or labels to skip this prompt.</p>"
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
# Use ActionRole for ALL buttons so Qt does NOT reorder them
|
|
109
|
+
image_button = msg.addButton("Image", QtWidgets.QMessageBox.ActionRole)
|
|
110
|
+
labels_button = msg.addButton("Labels", QtWidgets.QMessageBox.ActionRole)
|
|
111
|
+
cancel_button = msg.addButton("Cancel", QtWidgets.QMessageBox.ActionRole)
|
|
112
|
+
|
|
113
|
+
# If you want Esc to behave as Cancel:
|
|
114
|
+
msg.setEscapeButton(cancel_button)
|
|
115
|
+
|
|
116
|
+
msg.exec_()
|
|
117
|
+
clicked = msg.clickedButton()
|
|
118
|
+
|
|
119
|
+
if clicked is labels_button:
|
|
120
|
+
return "labels"
|
|
121
|
+
if clicked is image_button:
|
|
122
|
+
return "image"
|
|
123
|
+
|
|
124
|
+
raise RuntimeError("User cancelled napari-ome-arrow load dialog.")
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
# --------------------------------------------------------------------- #
|
|
128
|
+
# Helper utilities
|
|
129
|
+
# --------------------------------------------------------------------- #
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _as_labels(arr: np.ndarray) -> np.ndarray:
|
|
133
|
+
"""Convert any array into an integer label array."""
|
|
134
|
+
if arr.dtype.kind == "f":
|
|
135
|
+
arr = np.nan_to_num(arr, nan=0.0, posinf=0.0, neginf=0.0)
|
|
136
|
+
arr = np.round(arr).astype(np.int32, copy=False)
|
|
137
|
+
elif arr.dtype.kind not in ("i", "u"):
|
|
138
|
+
arr = arr.astype(np.int32, copy=False)
|
|
139
|
+
return arr
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _looks_like_ome_source(path_str: str) -> bool:
|
|
143
|
+
"""Basic extension / pattern sniffing for OME-Arrow supported formats."""
|
|
144
|
+
s = path_str.strip().lower()
|
|
145
|
+
p = Path(path_str)
|
|
146
|
+
|
|
147
|
+
looks_stack = any(c in path_str for c in "<>*")
|
|
148
|
+
looks_zarr = (
|
|
149
|
+
s.endswith((".ome.zarr", ".zarr"))
|
|
150
|
+
or ".zarr/" in s
|
|
151
|
+
or p.exists()
|
|
152
|
+
and p.is_dir()
|
|
153
|
+
and p.suffix.lower() == ".zarr"
|
|
154
|
+
)
|
|
155
|
+
looks_parquet = s.endswith(
|
|
156
|
+
(".ome.parquet", ".parquet", ".pq")
|
|
157
|
+
) or p.suffix.lower() in {
|
|
158
|
+
".parquet",
|
|
159
|
+
".pq",
|
|
160
|
+
}
|
|
161
|
+
looks_tiff = s.endswith(
|
|
162
|
+
(".ome.tif", ".ome.tiff", ".tif", ".tiff")
|
|
163
|
+
) or p.suffix.lower() in {
|
|
164
|
+
".tif",
|
|
165
|
+
".tiff",
|
|
166
|
+
}
|
|
167
|
+
looks_npy = s.endswith(".npy")
|
|
168
|
+
return (
|
|
169
|
+
looks_stack or looks_zarr or looks_parquet or looks_tiff or looks_npy
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
# --------------------------------------------------------------------- #
|
|
174
|
+
# napari entry point: napari_get_reader
|
|
175
|
+
# --------------------------------------------------------------------- #
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def napari_get_reader(path: Union[PathLike, Sequence[PathLike]]):
|
|
179
|
+
"""
|
|
180
|
+
Napari plugin hook: return a reader callable if this plugin can read `path`.
|
|
181
|
+
|
|
182
|
+
This MUST return a function object (e.g. `reader_function`) or None.
|
|
183
|
+
"""
|
|
184
|
+
# napari may pass a list/tuple or a single path
|
|
185
|
+
first = str(path[0] if isinstance(path, (list, tuple)) else path).strip()
|
|
186
|
+
|
|
187
|
+
if _looks_like_ome_source(first):
|
|
188
|
+
return reader_function
|
|
189
|
+
return None
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
# --------------------------------------------------------------------- #
|
|
193
|
+
# Reader implementation: reader_function
|
|
194
|
+
# --------------------------------------------------------------------- #
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _read_one(src: str, mode: str) -> LayerData:
|
|
198
|
+
"""
|
|
199
|
+
Read a single source into (data, add_kwargs, layer_type),
|
|
200
|
+
obeying `mode` = 'image' or 'labels'.
|
|
201
|
+
"""
|
|
202
|
+
s = src.lower()
|
|
203
|
+
p = Path(src)
|
|
204
|
+
|
|
205
|
+
looks_stack = any(c in src for c in "<>*")
|
|
206
|
+
looks_zarr = (
|
|
207
|
+
s.endswith((".ome.zarr", ".zarr"))
|
|
208
|
+
or ".zarr/" in s
|
|
209
|
+
or p.exists()
|
|
210
|
+
and p.is_dir()
|
|
211
|
+
and p.suffix.lower() == ".zarr"
|
|
212
|
+
)
|
|
213
|
+
looks_parquet = s.endswith(
|
|
214
|
+
(".ome.parquet", ".parquet", ".pq")
|
|
215
|
+
) or p.suffix.lower() in {
|
|
216
|
+
".parquet",
|
|
217
|
+
".pq",
|
|
218
|
+
}
|
|
219
|
+
looks_tiff = s.endswith(
|
|
220
|
+
(".ome.tif", ".ome.tiff", ".tif", ".tiff")
|
|
221
|
+
) or p.suffix.lower() in {
|
|
222
|
+
".tif",
|
|
223
|
+
".tiff",
|
|
224
|
+
}
|
|
225
|
+
looks_npy = s.endswith(".npy")
|
|
226
|
+
|
|
227
|
+
add_kwargs: dict[str, Any] = {"name": p.name}
|
|
228
|
+
|
|
229
|
+
# ---- OME-Arrow-backed sources -----------------------------------
|
|
230
|
+
if looks_stack or looks_zarr or looks_parquet or looks_tiff:
|
|
231
|
+
obj = OMEArrow(src)
|
|
232
|
+
arr = obj.export(how="numpy", dtype=np.uint16) # TCZYX
|
|
233
|
+
info = obj.info() # may contain 'shape': (T, C, Z, Y, X)
|
|
234
|
+
|
|
235
|
+
# Recover from accidental 1D flatten
|
|
236
|
+
if getattr(arr, "ndim", 0) == 1:
|
|
237
|
+
T, C, Z, Y, X = info.get("shape", (1, 1, 1, 0, 0))
|
|
238
|
+
if Y and X and arr.size == Y * X:
|
|
239
|
+
arr = arr.reshape((1, 1, 1, Y, X))
|
|
240
|
+
else:
|
|
241
|
+
raise ValueError(
|
|
242
|
+
f"Flat array with unknown shape for {src}: size={arr.size}"
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
if mode == "image":
|
|
246
|
+
# Image: preserve channels
|
|
247
|
+
if arr.ndim >= 5:
|
|
248
|
+
add_kwargs["channel_axis"] = 1 # TCZYX
|
|
249
|
+
elif arr.ndim == 4:
|
|
250
|
+
add_kwargs["channel_axis"] = 0 # CZYX
|
|
251
|
+
layer_type = "image"
|
|
252
|
+
else:
|
|
253
|
+
# Labels: squash channels, ensure integer dtype
|
|
254
|
+
if arr.ndim == 5: # (T, C, Z, Y, X)
|
|
255
|
+
arr = arr[:, 0, ...]
|
|
256
|
+
elif arr.ndim == 4: # (C, Z, Y, X)
|
|
257
|
+
arr = arr[0, ...]
|
|
258
|
+
arr = _as_labels(arr)
|
|
259
|
+
add_kwargs.setdefault("opacity", 0.7)
|
|
260
|
+
layer_type = "labels"
|
|
261
|
+
|
|
262
|
+
# 🔹 Ask viewer to switch to 3D if there is a real Z-stack
|
|
263
|
+
_maybe_set_viewer_3d(arr)
|
|
264
|
+
|
|
265
|
+
return arr, add_kwargs, layer_type
|
|
266
|
+
|
|
267
|
+
# ---- bare .npy fallback -----------------------------------------
|
|
268
|
+
if looks_npy:
|
|
269
|
+
arr = np.load(src)
|
|
270
|
+
if arr.ndim == 1:
|
|
271
|
+
n = int(np.sqrt(arr.size))
|
|
272
|
+
if n * n == arr.size:
|
|
273
|
+
arr = arr.reshape(n, n)
|
|
274
|
+
else:
|
|
275
|
+
raise ValueError(
|
|
276
|
+
f".npy is 1D and not a square image: {arr.shape}"
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
if mode == "image":
|
|
280
|
+
if arr.ndim == 3 and arr.shape[0] <= 6:
|
|
281
|
+
add_kwargs["channel_axis"] = 0
|
|
282
|
+
layer_type = "image"
|
|
283
|
+
else:
|
|
284
|
+
# labels
|
|
285
|
+
if arr.ndim == 3: # treat as (C, Y, X) → first channel
|
|
286
|
+
arr = arr[0, ...]
|
|
287
|
+
arr = _as_labels(arr)
|
|
288
|
+
add_kwargs.setdefault("opacity", 0.7)
|
|
289
|
+
layer_type = "labels"
|
|
290
|
+
|
|
291
|
+
# 🔹 Same 3D toggle for npy-based data
|
|
292
|
+
_maybe_set_viewer_3d(arr)
|
|
293
|
+
|
|
294
|
+
return arr, add_kwargs, layer_type
|
|
295
|
+
|
|
296
|
+
raise ValueError(f"Unrecognized path for napari-ome-arrow reader: {src}")
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def reader_function(
|
|
300
|
+
path: Union[PathLike, Sequence[PathLike]],
|
|
301
|
+
) -> list[LayerData]:
|
|
302
|
+
"""
|
|
303
|
+
The actual reader callable napari will use.
|
|
304
|
+
|
|
305
|
+
It reads one or more paths, prompting the user (or using the env var)
|
|
306
|
+
to decide 'image' vs 'labels', and returns a list of LayerData tuples.
|
|
307
|
+
"""
|
|
308
|
+
paths: list[str] = [
|
|
309
|
+
str(p) for p in (path if isinstance(path, (list, tuple)) else [path])
|
|
310
|
+
]
|
|
311
|
+
layers: list[LayerData] = []
|
|
312
|
+
|
|
313
|
+
# Use the first path as context for the dialog label
|
|
314
|
+
try:
|
|
315
|
+
mode = _get_layer_mode(sample_path=paths[0]) # 'image' or 'labels'
|
|
316
|
+
except RuntimeError as e:
|
|
317
|
+
# If user canceled the dialog, propagate a clean error for napari
|
|
318
|
+
raise ValueError(str(e)) from e
|
|
319
|
+
|
|
320
|
+
for src in paths:
|
|
321
|
+
try:
|
|
322
|
+
layers.append(_read_one(src, mode=mode))
|
|
323
|
+
except Exception as e:
|
|
324
|
+
warnings.warn(
|
|
325
|
+
f"Failed to read '{src}' with napari-ome-arrow: {e}",
|
|
326
|
+
stacklevel=2,
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
if not layers:
|
|
330
|
+
raise ValueError("No readable inputs found for given path(s).")
|
|
331
|
+
return layers
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# file generated by setuptools-scm
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
|
|
4
|
+
__all__ = [
|
|
5
|
+
"__version__",
|
|
6
|
+
"__version_tuple__",
|
|
7
|
+
"version",
|
|
8
|
+
"version_tuple",
|
|
9
|
+
"__commit_id__",
|
|
10
|
+
"commit_id",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
TYPE_CHECKING = False
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from typing import Tuple
|
|
16
|
+
from typing import Union
|
|
17
|
+
|
|
18
|
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
19
|
+
COMMIT_ID = Union[str, None]
|
|
20
|
+
else:
|
|
21
|
+
VERSION_TUPLE = object
|
|
22
|
+
COMMIT_ID = object
|
|
23
|
+
|
|
24
|
+
version: str
|
|
25
|
+
__version__: str
|
|
26
|
+
__version_tuple__: VERSION_TUPLE
|
|
27
|
+
version_tuple: VERSION_TUPLE
|
|
28
|
+
commit_id: COMMIT_ID
|
|
29
|
+
__commit_id__: COMMIT_ID
|
|
30
|
+
|
|
31
|
+
__version__ = version = '0.0.2'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 0, 2)
|
|
33
|
+
|
|
34
|
+
__commit_id__ = commit_id = None
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
name: napari-ome-arrow
|
|
2
|
+
display_name: OME-Arrow
|
|
3
|
+
contributions:
|
|
4
|
+
commands:
|
|
5
|
+
- id: napari-ome-arrow.get_reader
|
|
6
|
+
python_name: napari_ome_arrow._reader:napari_get_reader
|
|
7
|
+
title: Open OME-Arrow data
|
|
8
|
+
readers:
|
|
9
|
+
- command: napari-ome-arrow.get_reader
|
|
10
|
+
filename_patterns:
|
|
11
|
+
# OME-Parquet
|
|
12
|
+
- "*.ome.parquet"
|
|
13
|
+
- "*.parquet"
|
|
14
|
+
- "*.pq"
|
|
15
|
+
# OME-TIFF
|
|
16
|
+
- "*.ome.tif"
|
|
17
|
+
- "*.ome.tiff"
|
|
18
|
+
- "*.tif"
|
|
19
|
+
- "*.tiff"
|
|
20
|
+
# OME-Zarr
|
|
21
|
+
- "*.ome.zarr"
|
|
22
|
+
- "*.zarr"
|
|
23
|
+
accepts_directories: true
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: napari-ome-arrow
|
|
3
|
+
Version: 0.0.2
|
|
4
|
+
Summary: A Napari plugin for OME-Arrow and OME-Parquet bioimage data
|
|
5
|
+
License:
|
|
6
|
+
Copyright (c) 2025, Dave Bunten
|
|
7
|
+
All rights reserved.
|
|
8
|
+
|
|
9
|
+
Redistribution and use in source and binary forms, with or without
|
|
10
|
+
modification, are permitted provided that the following conditions are met:
|
|
11
|
+
|
|
12
|
+
* Redistributions of source code must retain the above copyright notice, this
|
|
13
|
+
list of conditions and the following disclaimer.
|
|
14
|
+
|
|
15
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
|
16
|
+
this list of conditions and the following disclaimer in the documentation
|
|
17
|
+
and/or other materials provided with the distribution.
|
|
18
|
+
|
|
19
|
+
* Neither the name of copyright holder nor the names of its
|
|
20
|
+
contributors may be used to endorse or promote products derived from
|
|
21
|
+
this software without specific prior written permission.
|
|
22
|
+
|
|
23
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
24
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
25
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
26
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
27
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
28
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
29
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
30
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
31
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
32
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
33
|
+
|
|
34
|
+
Project-URL: Bug Tracker, https://github.com/wayscience/napari-ome-arrow/issues
|
|
35
|
+
Project-URL: Documentation, https://github.com/wayscience/napari-ome-arrow#README.md
|
|
36
|
+
Project-URL: Source Code, https://github.com/wayscience/napari-ome-arrow
|
|
37
|
+
Project-URL: User Support, https://github.com/wayscience/napari-ome-arrow/issues
|
|
38
|
+
Classifier: Framework :: napari
|
|
39
|
+
Classifier: Intended Audience :: Developers
|
|
40
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
41
|
+
Classifier: Operating System :: OS Independent
|
|
42
|
+
Classifier: Programming Language :: Python
|
|
43
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
44
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
45
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
46
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
47
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
48
|
+
Classifier: Topic :: Scientific/Engineering :: Image Processing
|
|
49
|
+
Requires-Python: >=3.11
|
|
50
|
+
Description-Content-Type: text/markdown
|
|
51
|
+
License-File: LICENSE
|
|
52
|
+
Requires-Dist: magicgui
|
|
53
|
+
Requires-Dist: numpy
|
|
54
|
+
Requires-Dist: ome-arrow>=0.0.2
|
|
55
|
+
Requires-Dist: qtpy>=2.4
|
|
56
|
+
Requires-Dist: scikit-image
|
|
57
|
+
Provides-Extra: all
|
|
58
|
+
Requires-Dist: napari; extra == "all"
|
|
59
|
+
Requires-Dist: qtpy>=2.4; extra == "all"
|
|
60
|
+
Provides-Extra: pyqt6
|
|
61
|
+
Requires-Dist: napari[pyqt6]; extra == "pyqt6"
|
|
62
|
+
Requires-Dist: pyqt6>=6.6; extra == "pyqt6"
|
|
63
|
+
Requires-Dist: qtpy>=2.4; extra == "pyqt6"
|
|
64
|
+
Provides-Extra: pyside6
|
|
65
|
+
Requires-Dist: napari[pyside6]; extra == "pyside6"
|
|
66
|
+
Requires-Dist: pyside6>=6.6; extra == "pyside6"
|
|
67
|
+
Requires-Dist: qtpy>=2.4; extra == "pyside6"
|
|
68
|
+
Dynamic: license-file
|
|
69
|
+
|
|
70
|
+
# napari-ome-arrow
|
|
71
|
+
|
|
72
|
+
[](https://github.com/wayscience/napari-ome-arrow/raw/main/LICENSE)
|
|
73
|
+
[](https://pypi.org/project/napari-ome-arrow)
|
|
74
|
+
[](https://python.org)
|
|
75
|
+
[](https://napari-hub.org/plugins/napari-ome-arrow)
|
|
76
|
+
[](https://napari.org/stable/plugins/index.html)
|
|
77
|
+
|
|
78
|
+
`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
|
+
It provides a single, explicit pathway for loading OME-style bioimage data:
|
|
81
|
+
|
|
82
|
+
- **OME-TIFF** (`.ome.tif`, `.ome.tiff`, `.tif`, `.tiff`)
|
|
83
|
+
- **OME-Zarr** (`.ome.zarr`, `.zarr` stores and URLs)
|
|
84
|
+
- **OME-Parquet** (`.ome.parquet`, `.parquet`, `.pq`)
|
|
85
|
+
- **Bio-Formats–style stack patterns** (paths containing `<`, `>`, or `*`)
|
|
86
|
+
- A simple **`.npy` fallback** for quick testing / ad-hoc arrays
|
|
87
|
+
|
|
88
|
+
## Key features
|
|
89
|
+
|
|
90
|
+
- ✅ **Unified reader via OMEArrow**
|
|
91
|
+
All supported formats are loaded through `OME-Arrow`, which normalizes data into a common **TCZYX**-like representation.
|
|
92
|
+
|
|
93
|
+
- ✅ **Explicit image vs labels mode**
|
|
94
|
+
This plugin never guesses whether your data are intensities or segmentation masks. You must tell it:
|
|
95
|
+
|
|
96
|
+
- via the GUI prompt when you drop/open a file in napari, or
|
|
97
|
+
- via an environment variable for scripted/CLI usage.
|
|
98
|
+
|
|
99
|
+
- ✅ **Interactive choice in the GUI**
|
|
100
|
+
When `NAPARI_OME_ARROW_LAYER_TYPE` is not set and you open a supported file, napari shows a small dialog:
|
|
101
|
+
|
|
102
|
+
> *How should `my_data.ome.tif` be loaded?*
|
|
103
|
+
> `[Image] [Labels] [Cancel]`
|
|
104
|
+
|
|
105
|
+
This makes the “image vs labels” choice explicit at load time without relying on file naming conventions.
|
|
106
|
+
|
|
107
|
+
- ✅ **Image mode**
|
|
108
|
+
|
|
109
|
+
- Returns a napari **image layer**
|
|
110
|
+
- Preserves channels and sets `channel_axis` when appropriate
|
|
111
|
+
(e.g. multi-channel OME-TIFF or stack patterns)
|
|
112
|
+
- Works for 2D, 3D (Z-stacks), and higher-dimensional data (T, C, Z, Y, X)
|
|
113
|
+
|
|
114
|
+
- ✅ **Labels mode**
|
|
115
|
+
|
|
116
|
+
- Returns a napari **labels layer**
|
|
117
|
+
- Converts data to an integer dtype (suitable for labels)
|
|
118
|
+
- Applies a reasonable default opacity for overlaying on images
|
|
119
|
+
|
|
120
|
+
- ✅ **Automatic 3D for Z-stacks**
|
|
121
|
+
If the loaded data include a true Z dimension (`Z > 1`, assuming a TCZYX subset), the plugin asks the current viewer to switch to **3D** (`viewer.dims.ndisplay = 3`) so z-stacks open directly in volume mode.
|
|
122
|
+
|
|
123
|
+
- ✅ **Headless / scripted friendly**
|
|
124
|
+
When Qt is not available (e.g., in headless or purely programmatic contexts), the reader:
|
|
125
|
+
|
|
126
|
+
- respects `NAPARI_OME_ARROW_LAYER_TYPE`, and
|
|
127
|
+
- defaults to `"image"` if the variable is not set.
|
|
128
|
+
|
|
129
|
+
______________________________________________________________________
|
|
130
|
+
|
|
131
|
+
This [napari] plugin was generated with [copier] using the [napari-plugin-template] (None).
|
|
132
|
+
|
|
133
|
+
## Installation
|
|
134
|
+
|
|
135
|
+
You can install `napari-ome-arrow` via [pip]:
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
pip install napari-ome-arrow
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
If napari is not already installed, you can install `napari-ome-arrow` with napari and Qt via:
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
pip install "napari-ome-arrow[all]"
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
To install latest development version :
|
|
148
|
+
|
|
149
|
+
```
|
|
150
|
+
pip install git+https://github.com/wayscience/napari-ome-arrow.git
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Usage
|
|
154
|
+
|
|
155
|
+
### From the napari GUI
|
|
156
|
+
|
|
157
|
+
1. Install the plugin (see above).
|
|
158
|
+
1. Start napari.
|
|
159
|
+
1. Drag and drop an OME-TIFF, OME-Zarr, OME-Parquet file, or stack pattern into the viewer.
|
|
160
|
+
1. When prompted, choose **Image** or **Labels**.
|
|
161
|
+
|
|
162
|
+
The plugin will:
|
|
163
|
+
|
|
164
|
+
- load the data through `OMEArrow`,
|
|
165
|
+
- map channels and axes appropriately, and
|
|
166
|
+
- automatically switch to 3D if there is a Z-stack.
|
|
167
|
+
|
|
168
|
+
### From the command line
|
|
169
|
+
|
|
170
|
+
You can control the mode via an environment variable:
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
# Load as regular images
|
|
174
|
+
NAPARI_OME_ARROW_LAYER_TYPE=image napari my_data.ome.tif
|
|
175
|
+
|
|
176
|
+
# Load as labels (segmentation)
|
|
177
|
+
NAPARI_OME_ARROW_LAYER_TYPE=labels napari my_labels.ome.parquet
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Contributing
|
|
181
|
+
|
|
182
|
+
Contributions are very welcome.
|
|
183
|
+
Please reference our [CONTRIBUTING.md](CONTRIBUTING.md) guide.
|
|
184
|
+
|
|
185
|
+
## License
|
|
186
|
+
|
|
187
|
+
Please see the [LICENSE](LICENSE) file for more information.
|
|
188
|
+
|
|
189
|
+
## Issues
|
|
190
|
+
|
|
191
|
+
If you encounter any problems, please [file an issue] along with a detailed description.
|
|
192
|
+
|
|
193
|
+
[copier]: https://copier.readthedocs.io/en/stable/
|
|
194
|
+
[file an issue]: https://github.com/wayscience/napari-ome-arrow/issues
|
|
195
|
+
[napari]: https://github.com/napari/napari
|
|
196
|
+
[napari-plugin-template]: https://github.com/napari/napari-plugin-template
|
|
197
|
+
[pip]: https://pypi.org/project/pip/
|
|
@@ -0,0 +1,10 @@
|
|
|
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,,
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
|
|
2
|
+
Copyright (c) 2025, Dave Bunten
|
|
3
|
+
All rights reserved.
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
* Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
* Neither the name of copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
napari_ome_arrow
|