napari-ome-arrow 0.0.3__tar.gz → 0.0.5__tar.gz
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-0.0.3 → napari_ome_arrow-0.0.5}/.github/workflows/run-tests.yml +7 -0
- {napari_ome_arrow-0.0.3 → napari_ome_arrow-0.0.5}/.pre-commit-config.yaml +4 -4
- {napari_ome_arrow-0.0.3 → napari_ome_arrow-0.0.5}/CITATION.cff +32 -2
- {napari_ome_arrow-0.0.3/src/napari_ome_arrow.egg-info → napari_ome_arrow-0.0.5}/PKG-INFO +25 -5
- {napari_ome_arrow-0.0.3 → napari_ome_arrow-0.0.5}/README.md +21 -3
- {napari_ome_arrow-0.0.3 → napari_ome_arrow-0.0.5}/pyproject.toml +5 -4
- napari_ome_arrow-0.0.5/src/napari_ome_arrow/_reader.py +340 -0
- napari_ome_arrow-0.0.5/src/napari_ome_arrow/_reader_infer.py +233 -0
- napari_ome_arrow-0.0.5/src/napari_ome_arrow/_reader_napari.py +107 -0
- napari_ome_arrow-0.0.5/src/napari_ome_arrow/_reader_omearrow.py +474 -0
- napari_ome_arrow-0.0.5/src/napari_ome_arrow/_reader_stack.py +711 -0
- napari_ome_arrow-0.0.5/src/napari_ome_arrow/_reader_types.py +11 -0
- {napari_ome_arrow-0.0.3 → napari_ome_arrow-0.0.5}/src/napari_ome_arrow/_version.py +3 -3
- {napari_ome_arrow-0.0.3 → napari_ome_arrow-0.0.5}/src/napari_ome_arrow/napari.yaml +3 -0
- {napari_ome_arrow-0.0.3 → napari_ome_arrow-0.0.5/src/napari_ome_arrow.egg-info}/PKG-INFO +25 -5
- {napari_ome_arrow-0.0.3 → napari_ome_arrow-0.0.5}/src/napari_ome_arrow.egg-info/SOURCES.txt +5 -0
- {napari_ome_arrow-0.0.3 → napari_ome_arrow-0.0.5}/src/napari_ome_arrow.egg-info/requires.txt +4 -1
- {napari_ome_arrow-0.0.3 → napari_ome_arrow-0.0.5}/uv.lock +205 -318
- napari_ome_arrow-0.0.3/src/napari_ome_arrow/_reader.py +0 -456
- {napari_ome_arrow-0.0.3 → napari_ome_arrow-0.0.5}/.github/ISSUE_TEMPLATE/issue.yml +0 -0
- {napari_ome_arrow-0.0.3 → napari_ome_arrow-0.0.5}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {napari_ome_arrow-0.0.3 → napari_ome_arrow-0.0.5}/.github/dependabot.yml +0 -0
- {napari_ome_arrow-0.0.3 → napari_ome_arrow-0.0.5}/.github/release-drafter.yml +0 -0
- {napari_ome_arrow-0.0.3 → napari_ome_arrow-0.0.5}/.github/workflows/draft-release.yml +0 -0
- {napari_ome_arrow-0.0.3 → napari_ome_arrow-0.0.5}/.github/workflows/publish-pypi.yml +0 -0
- {napari_ome_arrow-0.0.3 → napari_ome_arrow-0.0.5}/.gitignore +0 -0
- {napari_ome_arrow-0.0.3 → napari_ome_arrow-0.0.5}/.napari-hub/config.yml +0 -0
- {napari_ome_arrow-0.0.3 → napari_ome_arrow-0.0.5}/CODE_OF_CONDUCT.md +0 -0
- {napari_ome_arrow-0.0.3 → napari_ome_arrow-0.0.5}/CONTRIBUTING.md +0 -0
- {napari_ome_arrow-0.0.3 → napari_ome_arrow-0.0.5}/LICENSE +0 -0
- {napari_ome_arrow-0.0.3 → napari_ome_arrow-0.0.5}/MANIFEST.in +0 -0
- {napari_ome_arrow-0.0.3 → napari_ome_arrow-0.0.5}/setup.cfg +0 -0
- {napari_ome_arrow-0.0.3 → napari_ome_arrow-0.0.5}/src/napari_ome_arrow/__init__.py +0 -0
- {napari_ome_arrow-0.0.3 → napari_ome_arrow-0.0.5}/src/napari_ome_arrow.egg-info/dependency_links.txt +0 -0
- {napari_ome_arrow-0.0.3 → napari_ome_arrow-0.0.5}/src/napari_ome_arrow.egg-info/entry_points.txt +0 -0
- {napari_ome_arrow-0.0.3 → napari_ome_arrow-0.0.5}/src/napari_ome_arrow.egg-info/top_level.txt +0 -0
|
@@ -52,5 +52,12 @@ jobs:
|
|
|
52
52
|
with:
|
|
53
53
|
qt: true
|
|
54
54
|
wm: herbstluftwm
|
|
55
|
+
- name: Install test dependencies
|
|
56
|
+
run: >
|
|
57
|
+
uv sync --frozen
|
|
58
|
+
--python ${{ matrix.python_version }}
|
|
59
|
+
--extra all
|
|
60
|
+
--extra pyside6
|
|
61
|
+
--extra vortex
|
|
55
62
|
- name: Run pytest
|
|
56
63
|
run: uv run --python ${{ matrix.python_version }} --frozen pytest
|
|
@@ -9,7 +9,7 @@ repos:
|
|
|
9
9
|
- id: check-yaml
|
|
10
10
|
- id: detect-private-key
|
|
11
11
|
- repo: https://github.com/tox-dev/pyproject-fmt
|
|
12
|
-
rev: "v2.
|
|
12
|
+
rev: "v2.12.1"
|
|
13
13
|
hooks:
|
|
14
14
|
- id: pyproject-fmt
|
|
15
15
|
- repo: https://github.com/citation-file-format/cffconvert
|
|
@@ -34,17 +34,17 @@ repos:
|
|
|
34
34
|
additional_dependencies:
|
|
35
35
|
- mdformat-gfm
|
|
36
36
|
- repo: https://github.com/adrienverge/yamllint
|
|
37
|
-
rev: v1.
|
|
37
|
+
rev: v1.38.0
|
|
38
38
|
hooks:
|
|
39
39
|
- id: yamllint
|
|
40
40
|
exclude: pre-commit-config.yaml
|
|
41
41
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
42
|
-
rev: "v0.
|
|
42
|
+
rev: "v0.15.0"
|
|
43
43
|
hooks:
|
|
44
44
|
- id: ruff-format
|
|
45
45
|
- id: ruff-check
|
|
46
46
|
- repo: https://github.com/rhysd/actionlint
|
|
47
|
-
rev: v1.7.
|
|
47
|
+
rev: v1.7.10
|
|
48
48
|
hooks:
|
|
49
49
|
- id: actionlint
|
|
50
50
|
- repo: https://gitlab.com/vojko.pribudic.foss/pre-commit-update
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# Visit https://bit.ly/cffinit to generate yours today!
|
|
3
3
|
---
|
|
4
4
|
cff-version: 1.2.0
|
|
5
|
-
title:
|
|
5
|
+
title: napari_ome_arrow
|
|
6
6
|
message: >-
|
|
7
7
|
If you use this software, please cite it using the
|
|
8
8
|
metadata from the CITATION.cff file.
|
|
@@ -25,10 +25,14 @@ authors:
|
|
|
25
25
|
orcid: 'https://orcid.org/0000-0002-0503-9348'
|
|
26
26
|
repository-code: "https://github.com/wayscience/ome-arrow"
|
|
27
27
|
abstract: >-
|
|
28
|
-
|
|
28
|
+
A Napari plugin for OME-Arrow and OME-Parquet bioimage data
|
|
29
29
|
keywords:
|
|
30
30
|
- python
|
|
31
31
|
license: BSD-3-Clause
|
|
32
|
+
identifiers:
|
|
33
|
+
- description: Zenodo software DOI
|
|
34
|
+
type: doi
|
|
35
|
+
value: 10.5281/zenodo.17613571
|
|
32
36
|
references:
|
|
33
37
|
- authors:
|
|
34
38
|
- name: "ExampleHuman CellProfiler Data Team"
|
|
@@ -125,3 +129,29 @@ references:
|
|
|
125
129
|
JUMP (cpg0000-jump-pilot) was used to help demonstrate napari-ome-arrow's
|
|
126
130
|
ability to parse various image data (specifically, plate BR00117006).
|
|
127
131
|
https://github.com/broadinstitute/cellpainting-gallery
|
|
132
|
+
- type: article
|
|
133
|
+
authors:
|
|
134
|
+
- family-names: Blin
|
|
135
|
+
given-names: Guillaume
|
|
136
|
+
- family-names: Sadurska
|
|
137
|
+
given-names: Dominika
|
|
138
|
+
- family-names: Portero Migueles
|
|
139
|
+
given-names: Rafael
|
|
140
|
+
- family-names: Chen
|
|
141
|
+
given-names: Ni
|
|
142
|
+
- family-names: Watson
|
|
143
|
+
given-names: James A.
|
|
144
|
+
- family-names: Lowell
|
|
145
|
+
given-names: Sally
|
|
146
|
+
title: "Nessys: A new set of tools for the automated detection of nuclei within intact tissues and dense 3D cultures"
|
|
147
|
+
journal: PLoS Biology
|
|
148
|
+
volume: "17"
|
|
149
|
+
issue: "8"
|
|
150
|
+
pages: e3000388
|
|
151
|
+
year: 2019
|
|
152
|
+
doi: "10.1371/journal.pbio.3000388"
|
|
153
|
+
url: "https://doi.org/10.1371/journal.pbio.3000388"
|
|
154
|
+
notes: >
|
|
155
|
+
This work used the file "6001240_labels.zarr" from the DISCEPTS imaging
|
|
156
|
+
dataset, available through the Image Data Resource (IDR) under accession
|
|
157
|
+
number idr0062.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: napari-ome-arrow
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.5
|
|
4
4
|
Summary: A Napari plugin for OME-Arrow and OME-Parquet bioimage data
|
|
5
5
|
License:
|
|
6
6
|
Copyright (c) 2025, Dave Bunten
|
|
@@ -51,7 +51,7 @@ Description-Content-Type: text/markdown
|
|
|
51
51
|
License-File: LICENSE
|
|
52
52
|
Requires-Dist: magicgui
|
|
53
53
|
Requires-Dist: numpy
|
|
54
|
-
Requires-Dist: ome-arrow>=0.0.
|
|
54
|
+
Requires-Dist: ome-arrow>=0.0.3
|
|
55
55
|
Requires-Dist: qtpy>=2.4
|
|
56
56
|
Requires-Dist: scikit-image
|
|
57
57
|
Provides-Extra: all
|
|
@@ -65,6 +65,8 @@ Provides-Extra: pyside6
|
|
|
65
65
|
Requires-Dist: napari[pyside6]; extra == "pyside6"
|
|
66
66
|
Requires-Dist: pyside6>=6.6; extra == "pyside6"
|
|
67
67
|
Requires-Dist: qtpy>=2.4; extra == "pyside6"
|
|
68
|
+
Provides-Extra: vortex
|
|
69
|
+
Requires-Dist: vortex-data; extra == "vortex"
|
|
68
70
|
Dynamic: license-file
|
|
69
71
|
|
|
70
72
|
# napari-ome-arrow
|
|
@@ -74,6 +76,7 @@ Dynamic: license-file
|
|
|
74
76
|
[](https://python.org)
|
|
75
77
|
[](https://napari-hub.org/plugins/napari-ome-arrow)
|
|
76
78
|
[](https://napari.org/stable/plugins/index.html)
|
|
79
|
+
[](https://doi.org/10.5281/zenodo.17613571)
|
|
77
80
|
|
|
78
81
|
`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
82
|
|
|
@@ -82,6 +85,7 @@ It provides a single, explicit pathway for loading OME-style bioimage data:
|
|
|
82
85
|
- **OME-TIFF** (`.ome.tif`, `.ome.tiff`, `.tif`, `.tiff`)
|
|
83
86
|
- **OME-Zarr** (`.ome.zarr`, `.zarr` stores and URLs)
|
|
84
87
|
- **OME-Parquet** (`.ome.parquet`, `.parquet`, `.pq`)
|
|
88
|
+
- **OME-Vortex** (`.ome.vortex`, `.vortex`)
|
|
85
89
|
- **Bio-Formats–style stack patterns** (paths containing `<`, `>`, or `*`)
|
|
86
90
|
- A simple **`.npy` fallback** for quick testing / ad-hoc arrays
|
|
87
91
|
|
|
@@ -126,8 +130,11 @@ It provides a single, explicit pathway for loading OME-style bioimage data:
|
|
|
126
130
|
- respects `NAPARI_OME_ARROW_LAYER_TYPE`, and
|
|
127
131
|
- defaults to `"image"` if the variable is not set.
|
|
128
132
|
|
|
129
|
-
- ✅ **Grid view for multi-row OME-Parquet**
|
|
130
|
-
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.
|
|
133
|
+
- ✅ **Grid view for multi-row OME-Parquet / OME-Vortex**
|
|
134
|
+
When a Parquet or Vortex 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` or `NAPARI_OME_ARROW_VORTEX_COLUMN` to pick which image column to visualize.
|
|
135
|
+
|
|
136
|
+
- ✅ **Stack scale prompt + override**
|
|
137
|
+
When loading image stacks (stack patterns), napari prompts for voxel spacing only if no scale metadata or `NAPARI_OME_ARROW_STACK_SCALE` override is present and a Qt UI is available (headless runs skip the prompt). To avoid the prompt, set `NAPARI_OME_ARROW_STACK_SCALE` using `Z,Y,X` or `T,C,Z,Y,X`.
|
|
131
138
|
|
|
132
139
|
______________________________________________________________________
|
|
133
140
|
|
|
@@ -153,13 +160,19 @@ To install latest development version :
|
|
|
153
160
|
pip install git+https://github.com/wayscience/napari-ome-arrow.git
|
|
154
161
|
```
|
|
155
162
|
|
|
163
|
+
To enable OME-Vortex support via OME-Arrow, install the optional extra:
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
pip install "napari-ome-arrow[vortex]"
|
|
167
|
+
```
|
|
168
|
+
|
|
156
169
|
## Usage
|
|
157
170
|
|
|
158
171
|
### From the napari GUI
|
|
159
172
|
|
|
160
173
|
1. Install the plugin (see above).
|
|
161
174
|
1. Start napari.
|
|
162
|
-
1. Drag and drop an OME-TIFF, OME-Zarr, OME-Parquet file,
|
|
175
|
+
1. Drag and drop an OME-TIFF, OME-Zarr, OME-Parquet, OME-Vortex file, a stack pattern, or a multi-select stack (e.g. `img_000.tif` ... `img_123.tif`) into the viewer.
|
|
163
176
|
1. When prompted, choose **Image** or **Labels**.
|
|
164
177
|
|
|
165
178
|
The plugin will:
|
|
@@ -167,6 +180,7 @@ The plugin will:
|
|
|
167
180
|
- load the data through `OMEArrow`,
|
|
168
181
|
- map channels and axes appropriately, and
|
|
169
182
|
- automatically switch to 3D if there is a Z-stack.
|
|
183
|
+
- When multiple files look like a numbered stack, treat them as a single stack rather than independent layers.
|
|
170
184
|
|
|
171
185
|
### From the command line
|
|
172
186
|
|
|
@@ -183,6 +197,12 @@ NAPARI_OME_ARROW_LAYER_TYPE=labels napari my_labels.ome.parquet
|
|
|
183
197
|
NAPARI_OME_ARROW_LAYER_TYPE=image \\
|
|
184
198
|
NAPARI_OME_ARROW_PARQUET_COLUMN=Image_FileName_OrigDNA_OMEArrow_ORIG \\
|
|
185
199
|
napari tests/data/cytodataframe/BR00117006.ome.parquet
|
|
200
|
+
|
|
201
|
+
# Prefill stack voxel spacing for stack patterns (Z,Y,X or T,C,Z,Y,X)
|
|
202
|
+
NAPARI_OME_ARROW_STACK_SCALE=1.0,0.108,0.108 napari "stack/z<000-120>.tif"
|
|
203
|
+
|
|
204
|
+
# Prefill stack voxel spacing for multi-file stacks (use a pattern or glob on CLI)
|
|
205
|
+
NAPARI_OME_ARROW_STACK_SCALE=1.0,0.108,0.108 napari "stack/img_<000-120>.tif"
|
|
186
206
|
```
|
|
187
207
|
|
|
188
208
|
## Contributing
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
[](https://python.org)
|
|
6
6
|
[](https://napari-hub.org/plugins/napari-ome-arrow)
|
|
7
7
|
[](https://napari.org/stable/plugins/index.html)
|
|
8
|
+
[](https://doi.org/10.5281/zenodo.17613571)
|
|
8
9
|
|
|
9
10
|
`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.
|
|
10
11
|
|
|
@@ -13,6 +14,7 @@ It provides a single, explicit pathway for loading OME-style bioimage data:
|
|
|
13
14
|
- **OME-TIFF** (`.ome.tif`, `.ome.tiff`, `.tif`, `.tiff`)
|
|
14
15
|
- **OME-Zarr** (`.ome.zarr`, `.zarr` stores and URLs)
|
|
15
16
|
- **OME-Parquet** (`.ome.parquet`, `.parquet`, `.pq`)
|
|
17
|
+
- **OME-Vortex** (`.ome.vortex`, `.vortex`)
|
|
16
18
|
- **Bio-Formats–style stack patterns** (paths containing `<`, `>`, or `*`)
|
|
17
19
|
- A simple **`.npy` fallback** for quick testing / ad-hoc arrays
|
|
18
20
|
|
|
@@ -57,8 +59,11 @@ It provides a single, explicit pathway for loading OME-style bioimage data:
|
|
|
57
59
|
- respects `NAPARI_OME_ARROW_LAYER_TYPE`, and
|
|
58
60
|
- defaults to `"image"` if the variable is not set.
|
|
59
61
|
|
|
60
|
-
- ✅ **Grid view for multi-row OME-Parquet**
|
|
61
|
-
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.
|
|
62
|
+
- ✅ **Grid view for multi-row OME-Parquet / OME-Vortex**
|
|
63
|
+
When a Parquet or Vortex 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` or `NAPARI_OME_ARROW_VORTEX_COLUMN` to pick which image column to visualize.
|
|
64
|
+
|
|
65
|
+
- ✅ **Stack scale prompt + override**
|
|
66
|
+
When loading image stacks (stack patterns), napari prompts for voxel spacing only if no scale metadata or `NAPARI_OME_ARROW_STACK_SCALE` override is present and a Qt UI is available (headless runs skip the prompt). To avoid the prompt, set `NAPARI_OME_ARROW_STACK_SCALE` using `Z,Y,X` or `T,C,Z,Y,X`.
|
|
62
67
|
|
|
63
68
|
______________________________________________________________________
|
|
64
69
|
|
|
@@ -84,13 +89,19 @@ To install latest development version :
|
|
|
84
89
|
pip install git+https://github.com/wayscience/napari-ome-arrow.git
|
|
85
90
|
```
|
|
86
91
|
|
|
92
|
+
To enable OME-Vortex support via OME-Arrow, install the optional extra:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
pip install "napari-ome-arrow[vortex]"
|
|
96
|
+
```
|
|
97
|
+
|
|
87
98
|
## Usage
|
|
88
99
|
|
|
89
100
|
### From the napari GUI
|
|
90
101
|
|
|
91
102
|
1. Install the plugin (see above).
|
|
92
103
|
1. Start napari.
|
|
93
|
-
1. Drag and drop an OME-TIFF, OME-Zarr, OME-Parquet file,
|
|
104
|
+
1. Drag and drop an OME-TIFF, OME-Zarr, OME-Parquet, OME-Vortex file, a stack pattern, or a multi-select stack (e.g. `img_000.tif` ... `img_123.tif`) into the viewer.
|
|
94
105
|
1. When prompted, choose **Image** or **Labels**.
|
|
95
106
|
|
|
96
107
|
The plugin will:
|
|
@@ -98,6 +109,7 @@ The plugin will:
|
|
|
98
109
|
- load the data through `OMEArrow`,
|
|
99
110
|
- map channels and axes appropriately, and
|
|
100
111
|
- automatically switch to 3D if there is a Z-stack.
|
|
112
|
+
- When multiple files look like a numbered stack, treat them as a single stack rather than independent layers.
|
|
101
113
|
|
|
102
114
|
### From the command line
|
|
103
115
|
|
|
@@ -114,6 +126,12 @@ NAPARI_OME_ARROW_LAYER_TYPE=labels napari my_labels.ome.parquet
|
|
|
114
126
|
NAPARI_OME_ARROW_LAYER_TYPE=image \\
|
|
115
127
|
NAPARI_OME_ARROW_PARQUET_COLUMN=Image_FileName_OrigDNA_OMEArrow_ORIG \\
|
|
116
128
|
napari tests/data/cytodataframe/BR00117006.ome.parquet
|
|
129
|
+
|
|
130
|
+
# Prefill stack voxel spacing for stack patterns (Z,Y,X or T,C,Z,Y,X)
|
|
131
|
+
NAPARI_OME_ARROW_STACK_SCALE=1.0,0.108,0.108 napari "stack/z<000-120>.tif"
|
|
132
|
+
|
|
133
|
+
# Prefill stack voxel spacing for multi-file stacks (use a pattern or glob on CLI)
|
|
134
|
+
NAPARI_OME_ARROW_STACK_SCALE=1.0,0.108,0.108 napari "stack/img_<000-120>.tif"
|
|
117
135
|
```
|
|
118
136
|
|
|
119
137
|
## Contributing
|
|
@@ -29,7 +29,7 @@ dynamic = [ "version" ]
|
|
|
29
29
|
dependencies = [
|
|
30
30
|
"magicgui",
|
|
31
31
|
"numpy",
|
|
32
|
-
"ome-arrow>=0.0.
|
|
32
|
+
"ome-arrow>=0.0.3",
|
|
33
33
|
"qtpy>=2.4",
|
|
34
34
|
"scikit-image",
|
|
35
35
|
]
|
|
@@ -39,6 +39,7 @@ optional-dependencies.all = [ "napari", "qtpy>=2.4" ]
|
|
|
39
39
|
optional-dependencies.pyqt6 = [ "napari[pyqt6]", "pyqt6>=6.6", "qtpy>=2.4" ]
|
|
40
40
|
# Let users choose their Qt backend explicitly:
|
|
41
41
|
optional-dependencies.pyside6 = [ "napari[pyside6]", "pyside6>=6.6", "qtpy>=2.4" ]
|
|
42
|
+
optional-dependencies.vortex = [ "vortex-data" ]
|
|
42
43
|
urls."Bug Tracker" = "https://github.com/wayscience/napari-ome-arrow/issues"
|
|
43
44
|
urls."Documentation" = "https://github.com/wayscience/napari-ome-arrow#README.md"
|
|
44
45
|
urls."Source Code" = "https://github.com/wayscience/napari-ome-arrow"
|
|
@@ -58,13 +59,13 @@ dev = [
|
|
|
58
59
|
[tool.setuptools]
|
|
59
60
|
include-package-data = true
|
|
60
61
|
|
|
62
|
+
[tool.setuptools.package-data]
|
|
63
|
+
"*" = [ "*.yaml" ]
|
|
64
|
+
|
|
61
65
|
[tool.setuptools.packages.find]
|
|
62
66
|
where = [ "src" ]
|
|
63
67
|
exclude = [ "*.tests", "*.tests.*", "tests*" ]
|
|
64
68
|
|
|
65
|
-
[tool.setuptools.package-data]
|
|
66
|
-
"*" = [ "*.yaml" ]
|
|
67
|
-
|
|
68
69
|
[tool.setuptools_scm]
|
|
69
70
|
write_to = "src/napari_ome_arrow/_version.py"
|
|
70
71
|
fallback_version = "0.0.1+nogit"
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Minimal napari reader for OME-Arrow sources (stack patterns, OME-Zarr, OME-Parquet,
|
|
3
|
+
OME-Vortex, 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 Callable, Sequence
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
from ._reader_infer import ( # noqa: F401
|
|
20
|
+
_infer_layer_mode_from_record,
|
|
21
|
+
_infer_layer_mode_from_source,
|
|
22
|
+
_looks_like_ome_source,
|
|
23
|
+
_normalize_image_type,
|
|
24
|
+
)
|
|
25
|
+
from ._reader_napari import ( # noqa: F401
|
|
26
|
+
_maybe_set_viewer_3d,
|
|
27
|
+
_strip_channel_axis,
|
|
28
|
+
)
|
|
29
|
+
from ._reader_omearrow import ( # noqa: F401
|
|
30
|
+
_read_one,
|
|
31
|
+
_read_parquet_rows,
|
|
32
|
+
_read_vortex_rows,
|
|
33
|
+
)
|
|
34
|
+
from ._reader_stack import ( # noqa: F401
|
|
35
|
+
_channel_names_from_pattern,
|
|
36
|
+
_collect_stack_files,
|
|
37
|
+
_infer_stack_scale_from_pattern,
|
|
38
|
+
_parse_stack_scale,
|
|
39
|
+
_prompt_stack_pattern,
|
|
40
|
+
_prompt_stack_scale,
|
|
41
|
+
_read_rgb_stack_pattern,
|
|
42
|
+
_replace_channel_placeholder,
|
|
43
|
+
_stack_default_dim_for_pattern,
|
|
44
|
+
_suggest_stack_pattern,
|
|
45
|
+
)
|
|
46
|
+
from ._reader_types import LayerData, PathLike
|
|
47
|
+
|
|
48
|
+
__all__ = [
|
|
49
|
+
"napari_get_reader",
|
|
50
|
+
"reader_function",
|
|
51
|
+
"_infer_layer_mode_from_record",
|
|
52
|
+
"_infer_layer_mode_from_source",
|
|
53
|
+
"_looks_like_ome_source",
|
|
54
|
+
"_normalize_image_type",
|
|
55
|
+
"_maybe_set_viewer_3d",
|
|
56
|
+
"_strip_channel_axis",
|
|
57
|
+
"_read_one",
|
|
58
|
+
"_read_parquet_rows",
|
|
59
|
+
"_read_vortex_rows",
|
|
60
|
+
"_channel_names_from_pattern",
|
|
61
|
+
"_collect_stack_files",
|
|
62
|
+
"_infer_stack_scale_from_pattern",
|
|
63
|
+
"_parse_stack_scale",
|
|
64
|
+
"_prompt_stack_pattern",
|
|
65
|
+
"_prompt_stack_scale",
|
|
66
|
+
"_read_rgb_stack_pattern",
|
|
67
|
+
"_replace_channel_placeholder",
|
|
68
|
+
"_stack_default_dim_for_pattern",
|
|
69
|
+
"_suggest_stack_pattern",
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
# --------------------------------------------------------------------- #
|
|
73
|
+
# Mode selection (env var + GUI prompt)
|
|
74
|
+
# --------------------------------------------------------------------- #
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _get_layer_mode(
|
|
78
|
+
sample_path: str, *, image_type_hint: str | None = None
|
|
79
|
+
) -> str:
|
|
80
|
+
"""Decide whether to load as "image" or "labels".
|
|
81
|
+
|
|
82
|
+
Priority:
|
|
83
|
+
1. `NAPARI_OME_ARROW_LAYER_TYPE` env var (image/labels).
|
|
84
|
+
2. If in a Qt GUI context, show a modal dialog asking the user.
|
|
85
|
+
3. Otherwise, default to "image".
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
sample_path: Path used for prompt labeling.
|
|
89
|
+
image_type_hint: Optional inferred mode from metadata.
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
The selected mode, "image" or "labels".
|
|
93
|
+
|
|
94
|
+
Raises:
|
|
95
|
+
RuntimeError: If the environment variable is invalid or the user
|
|
96
|
+
cancels the dialog.
|
|
97
|
+
"""
|
|
98
|
+
# Env var override (used in headless/batch workflows).
|
|
99
|
+
mode = os.environ.get("NAPARI_OME_ARROW_LAYER_TYPE")
|
|
100
|
+
if mode is not None:
|
|
101
|
+
mode = mode.lower()
|
|
102
|
+
if mode in {"image", "labels"}:
|
|
103
|
+
return mode
|
|
104
|
+
raise RuntimeError(
|
|
105
|
+
f"Invalid NAPARI_OME_ARROW_LAYER_TYPE={mode!r}; expected 'image' or 'labels'."
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
# Metadata hint (best effort).
|
|
109
|
+
if image_type_hint in {"image", "labels"}:
|
|
110
|
+
return image_type_hint
|
|
111
|
+
|
|
112
|
+
# No env var → try to prompt in GUI context
|
|
113
|
+
try:
|
|
114
|
+
from qtpy import QtWidgets
|
|
115
|
+
except Exception:
|
|
116
|
+
# no Qt, probably headless: default to image
|
|
117
|
+
warnings.warn(
|
|
118
|
+
"NAPARI_OME_ARROW_LAYER_TYPE not set and Qt not available; "
|
|
119
|
+
"defaulting to 'image'.",
|
|
120
|
+
stacklevel=2,
|
|
121
|
+
)
|
|
122
|
+
return "image"
|
|
123
|
+
|
|
124
|
+
app = QtWidgets.QApplication.instance()
|
|
125
|
+
if app is None:
|
|
126
|
+
# Again, likely headless or non-Qt usage
|
|
127
|
+
warnings.warn(
|
|
128
|
+
"NAPARI_OME_ARROW_LAYER_TYPE not set and no QApplication instance; "
|
|
129
|
+
"defaulting to 'image'.",
|
|
130
|
+
stacklevel=2,
|
|
131
|
+
)
|
|
132
|
+
return "image"
|
|
133
|
+
|
|
134
|
+
# Build a simple modal choice dialog
|
|
135
|
+
msg = QtWidgets.QMessageBox()
|
|
136
|
+
msg.setWindowTitle("napari-ome-arrow: choose layer type")
|
|
137
|
+
msg.setText(
|
|
138
|
+
f"<p align='left'>How should '{Path(sample_path).name}' be loaded?<br><br>"
|
|
139
|
+
"You can also set NAPARI_OME_ARROW_LAYER_TYPE=image or labels to skip this prompt.</p>"
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
# Use ActionRole for ALL buttons so Qt does NOT reorder them
|
|
143
|
+
image_button = msg.addButton("Image", QtWidgets.QMessageBox.ActionRole)
|
|
144
|
+
labels_button = msg.addButton("Labels", QtWidgets.QMessageBox.ActionRole)
|
|
145
|
+
cancel_button = msg.addButton("Cancel", QtWidgets.QMessageBox.ActionRole)
|
|
146
|
+
|
|
147
|
+
# If you want Esc to behave as Cancel:
|
|
148
|
+
msg.setEscapeButton(cancel_button)
|
|
149
|
+
|
|
150
|
+
msg.exec_()
|
|
151
|
+
clicked = msg.clickedButton()
|
|
152
|
+
|
|
153
|
+
if clicked is labels_button:
|
|
154
|
+
return "labels"
|
|
155
|
+
if clicked is image_button:
|
|
156
|
+
return "image"
|
|
157
|
+
|
|
158
|
+
raise RuntimeError("User cancelled napari-ome-arrow load dialog.")
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
# --------------------------------------------------------------------- #
|
|
162
|
+
# napari entry point: napari_get_reader
|
|
163
|
+
# --------------------------------------------------------------------- #
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def napari_get_reader(
|
|
167
|
+
path: PathLike | Sequence[PathLike],
|
|
168
|
+
) -> Callable[[PathLike | Sequence[PathLike]], list[LayerData]] | None:
|
|
169
|
+
"""Return a reader callable for napari if the path is supported.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
path: A single path or a sequence of paths provided by napari.
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
Reader callable if the path is supported, otherwise None.
|
|
176
|
+
"""
|
|
177
|
+
# napari may pass a list/tuple or a single path
|
|
178
|
+
first = str(path[0] if isinstance(path, (list, tuple)) else path).strip()
|
|
179
|
+
|
|
180
|
+
if _looks_like_ome_source(first):
|
|
181
|
+
return reader_function
|
|
182
|
+
return None
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
# --------------------------------------------------------------------- #
|
|
186
|
+
# Reader implementation: reader_function
|
|
187
|
+
# --------------------------------------------------------------------- #
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def reader_function(
|
|
191
|
+
path: PathLike | Sequence[PathLike],
|
|
192
|
+
) -> list[LayerData]:
|
|
193
|
+
"""Read one or more paths into napari layer data.
|
|
194
|
+
|
|
195
|
+
The user may be prompted (or an env var used) to decide "image" vs
|
|
196
|
+
"labels".
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
path: A single path or a sequence of paths provided by napari.
|
|
200
|
+
|
|
201
|
+
Returns:
|
|
202
|
+
List of layer tuples for napari.
|
|
203
|
+
|
|
204
|
+
Raises:
|
|
205
|
+
ValueError: If no readable inputs are found.
|
|
206
|
+
"""
|
|
207
|
+
paths: list[str] = [
|
|
208
|
+
str(p) for p in (path if isinstance(path, (list, tuple)) else [path])
|
|
209
|
+
]
|
|
210
|
+
layers: list[LayerData] = []
|
|
211
|
+
|
|
212
|
+
# Offer a stack pattern prompt when multiple compatible files are present.
|
|
213
|
+
stack_selection = _collect_stack_files(paths)
|
|
214
|
+
stack_pattern = None
|
|
215
|
+
if stack_selection is not None:
|
|
216
|
+
stack_pattern = _prompt_stack_pattern(*stack_selection)
|
|
217
|
+
|
|
218
|
+
# Use the original first path as context for the dialog label
|
|
219
|
+
mode_hint = None
|
|
220
|
+
if stack_pattern is not None:
|
|
221
|
+
stack_default_dim = _stack_default_dim_for_pattern(stack_pattern)
|
|
222
|
+
mode_hint = _infer_layer_mode_from_source(
|
|
223
|
+
stack_pattern, stack_default_dim=stack_default_dim
|
|
224
|
+
)
|
|
225
|
+
elif paths:
|
|
226
|
+
mode_hint = _infer_layer_mode_from_source(paths[0])
|
|
227
|
+
try:
|
|
228
|
+
mode = _get_layer_mode(
|
|
229
|
+
sample_path=paths[0], image_type_hint=mode_hint
|
|
230
|
+
) # 'image' or 'labels'
|
|
231
|
+
except RuntimeError as e:
|
|
232
|
+
# If user canceled the dialog, propagate a clean error for napari
|
|
233
|
+
raise ValueError(str(e)) from e
|
|
234
|
+
|
|
235
|
+
if stack_pattern is not None:
|
|
236
|
+
try:
|
|
237
|
+
channel_names = _channel_names_from_pattern(
|
|
238
|
+
stack_pattern, stack_default_dim
|
|
239
|
+
)
|
|
240
|
+
resolved_stack_scale = None
|
|
241
|
+
if mode == "image" and channel_names and len(channel_names) > 1:
|
|
242
|
+
# Split each channel into separate layers for stack patterns.
|
|
243
|
+
inferred_stack_scale = _infer_stack_scale_from_pattern(
|
|
244
|
+
stack_pattern, stack_default_dim
|
|
245
|
+
)
|
|
246
|
+
env_scale = os.environ.get("NAPARI_OME_ARROW_STACK_SCALE")
|
|
247
|
+
if env_scale:
|
|
248
|
+
try:
|
|
249
|
+
resolved_stack_scale = _parse_stack_scale(env_scale)
|
|
250
|
+
except ValueError as exc:
|
|
251
|
+
warnings.warn(
|
|
252
|
+
f"Invalid NAPARI_OME_ARROW_STACK_SCALE '{env_scale}': {exc}.",
|
|
253
|
+
stacklevel=2,
|
|
254
|
+
)
|
|
255
|
+
resolved_stack_scale = None
|
|
256
|
+
if (
|
|
257
|
+
resolved_stack_scale is None
|
|
258
|
+
and inferred_stack_scale is None
|
|
259
|
+
):
|
|
260
|
+
resolved_stack_scale = _prompt_stack_scale(
|
|
261
|
+
sample_path=stack_pattern, default_scale=None
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
for label in channel_names:
|
|
265
|
+
channel_pattern = _replace_channel_placeholder(
|
|
266
|
+
stack_pattern, label, stack_default_dim
|
|
267
|
+
)
|
|
268
|
+
channel_default_dim = _stack_default_dim_for_pattern(
|
|
269
|
+
channel_pattern
|
|
270
|
+
)
|
|
271
|
+
try:
|
|
272
|
+
arr, add_kwargs, layer_type = _read_one(
|
|
273
|
+
channel_pattern,
|
|
274
|
+
mode=mode,
|
|
275
|
+
stack_default_dim=channel_default_dim,
|
|
276
|
+
stack_scale_override=resolved_stack_scale,
|
|
277
|
+
)
|
|
278
|
+
arr, add_kwargs = _strip_channel_axis(arr, add_kwargs)
|
|
279
|
+
except Exception as exc:
|
|
280
|
+
try:
|
|
281
|
+
arr, is_rgb = _read_rgb_stack_pattern(
|
|
282
|
+
channel_pattern
|
|
283
|
+
)
|
|
284
|
+
except Exception:
|
|
285
|
+
warnings.warn(
|
|
286
|
+
f"Failed to read channel '{label}' from stack "
|
|
287
|
+
f"pattern '{stack_pattern}': {exc}",
|
|
288
|
+
stacklevel=2,
|
|
289
|
+
)
|
|
290
|
+
continue
|
|
291
|
+
add_kwargs = {"name": Path(channel_pattern).name}
|
|
292
|
+
if is_rgb:
|
|
293
|
+
add_kwargs["rgb"] = True
|
|
294
|
+
layer_type = "image"
|
|
295
|
+
|
|
296
|
+
add_kwargs["name"] = (
|
|
297
|
+
f"{add_kwargs.get('name', label)}[{label}]"
|
|
298
|
+
)
|
|
299
|
+
if not add_kwargs.get("rgb"):
|
|
300
|
+
_maybe_set_viewer_3d(arr)
|
|
301
|
+
layers.append((arr, add_kwargs, layer_type))
|
|
302
|
+
else:
|
|
303
|
+
# Single-layer stack read.
|
|
304
|
+
arr, add_kwargs, layer_type = _read_one(
|
|
305
|
+
stack_pattern,
|
|
306
|
+
mode=mode,
|
|
307
|
+
stack_default_dim=stack_default_dim,
|
|
308
|
+
)
|
|
309
|
+
layers.append((arr, add_kwargs, layer_type))
|
|
310
|
+
except Exception as e:
|
|
311
|
+
warnings.warn(
|
|
312
|
+
f"Failed to read stack pattern '{stack_pattern}': {e}. "
|
|
313
|
+
"Loading files individually instead.",
|
|
314
|
+
stacklevel=2,
|
|
315
|
+
)
|
|
316
|
+
else:
|
|
317
|
+
return layers
|
|
318
|
+
elif stack_selection is not None and len(paths) == 1:
|
|
319
|
+
paths = [str(p) for p in stack_selection[0]]
|
|
320
|
+
|
|
321
|
+
for src in paths:
|
|
322
|
+
try:
|
|
323
|
+
parquet_layers = _read_parquet_rows(src, mode)
|
|
324
|
+
if parquet_layers is not None:
|
|
325
|
+
layers.extend(parquet_layers)
|
|
326
|
+
continue
|
|
327
|
+
vortex_layers = _read_vortex_rows(src, mode)
|
|
328
|
+
if vortex_layers is not None:
|
|
329
|
+
layers.extend(vortex_layers)
|
|
330
|
+
continue
|
|
331
|
+
layers.append(_read_one(src, mode=mode))
|
|
332
|
+
except Exception as e:
|
|
333
|
+
warnings.warn(
|
|
334
|
+
f"Failed to read '{src}' with napari-ome-arrow: {e}",
|
|
335
|
+
stacklevel=2,
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
if not layers:
|
|
339
|
+
raise ValueError("No readable inputs found for given path(s).")
|
|
340
|
+
return layers
|