neuroviewer 0.1.0__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.
- neuroviewer-0.1.0/.gitignore +37 -0
- neuroviewer-0.1.0/CHANGELOG.md +26 -0
- neuroviewer-0.1.0/LICENSE +21 -0
- neuroviewer-0.1.0/PKG-INFO +197 -0
- neuroviewer-0.1.0/README.md +153 -0
- neuroviewer-0.1.0/RELEASING.md +45 -0
- neuroviewer-0.1.0/packaging/appimage/README.md +28 -0
- neuroviewer-0.1.0/packaging/appimage/build_appimage.sh +79 -0
- neuroviewer-0.1.0/packaging/appimage/entrypoint.sh +8 -0
- neuroviewer-0.1.0/packaging/appimage/neuroviewer.appdata.xml +34 -0
- neuroviewer-0.1.0/pyproject.toml +80 -0
- neuroviewer-0.1.0/src/neuroviewer/__init__.py +8 -0
- neuroviewer-0.1.0/src/neuroviewer/__main__.py +5 -0
- neuroviewer-0.1.0/src/neuroviewer/app.py +1192 -0
- neuroviewer-0.1.0/src/neuroviewer/cli.py +130 -0
- neuroviewer-0.1.0/src/neuroviewer/config.py +156 -0
- neuroviewer-0.1.0/src/neuroviewer/loaders.py +353 -0
- neuroviewer-0.1.0/src/neuroviewer/resources/neuroviewer.desktop +11 -0
- neuroviewer-0.1.0/src/neuroviewer/resources/neuroviewer.png +0 -0
- neuroviewer-0.1.0/src/neuroviewer/resources/neuroviewer.svg +22 -0
- neuroviewer-0.1.0/src/neuroviewer/slicing.py +339 -0
- neuroviewer-0.1.0/src/neuroviewer/tools.py +312 -0
- neuroviewer-0.1.0/src/neuroviewer/views.py +452 -0
- neuroviewer-0.1.0/tests/test_cli.py +43 -0
- neuroviewer-0.1.0/tests/test_core.py +78 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.so
|
|
5
|
+
|
|
6
|
+
# Build / packaging
|
|
7
|
+
build/
|
|
8
|
+
dist/
|
|
9
|
+
*.egg-info/
|
|
10
|
+
.eggs/
|
|
11
|
+
*.whl
|
|
12
|
+
*.tar.gz
|
|
13
|
+
|
|
14
|
+
# AppImage build
|
|
15
|
+
packaging/appimage/build/
|
|
16
|
+
*.AppImage
|
|
17
|
+
appimagetool*
|
|
18
|
+
|
|
19
|
+
# Virtual environments
|
|
20
|
+
.venv/
|
|
21
|
+
venv/
|
|
22
|
+
env/
|
|
23
|
+
|
|
24
|
+
# Testing
|
|
25
|
+
.pytest_cache/
|
|
26
|
+
.coverage
|
|
27
|
+
htmlcov/
|
|
28
|
+
|
|
29
|
+
# IDE
|
|
30
|
+
.vscode/
|
|
31
|
+
.idea/
|
|
32
|
+
.spyproject/
|
|
33
|
+
*.swp
|
|
34
|
+
|
|
35
|
+
# Screenshots saved while testing
|
|
36
|
+
neuroviewer_*.png
|
|
37
|
+
.ruff_cache/
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. The format follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project uses
|
|
5
|
+
[Semantic Versioning](https://semver.org/).
|
|
6
|
+
|
|
7
|
+
## [0.1.0] - 2026-10-15
|
|
8
|
+
|
|
9
|
+
First public release.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- Desktop viewer (PySide6 + vedo/VTK) with one main view and three side views
|
|
13
|
+
(layouts 1+3, 2x2, 1x1); click a panel title or Ctrl+1..4 to swap.
|
|
14
|
+
- File panel with a filtered folder tree and recursive name search.
|
|
15
|
+
- Volumes: NIfTI, MGH/MGZ, Analyze, MINC, PAR/REC, reoriented to RAS+ with
|
|
16
|
+
their full affine; layers with colormap, window, opacity, threshold and
|
|
17
|
+
4D frame; FreeSurfer LUT colours for label maps.
|
|
18
|
+
- 3D scene: slices, bounding box, volume rendering, FreeSurfer/GIfTI surfaces
|
|
19
|
+
(shifted to scanner RAS) and .trk/.tck streamlines.
|
|
20
|
+
- Tools registry: isosurface, label surfaces, cortex and threshold point
|
|
21
|
+
clouds, point cloud from a surface, Open3D downsampling and Poisson
|
|
22
|
+
reconstruction (optional extra).
|
|
23
|
+
- Time course of 4D layers at the cursor; cursor readout with label names.
|
|
24
|
+
- `neuroviewer` command with `--write-config`, `--config-path`,
|
|
25
|
+
`--install-desktop` and `--version`; user config file that overrides the
|
|
26
|
+
packaged defaults.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Rodrigo Debona
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: neuroviewer
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Freeview-like neuroimaging viewer in pure Python (PySide6 + vedo/VTK + nibabel)
|
|
5
|
+
Project-URL: Homepage, https://github.com/rdneuro/neuroviewer
|
|
6
|
+
Project-URL: Repository, https://github.com/rdneuro/neuroviewer
|
|
7
|
+
Project-URL: Issues, https://github.com/rdneuro/neuroviewer/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/rdneuro/neuroviewer/blob/main/CHANGELOG.md
|
|
9
|
+
Author-email: Rodrigo Debona <r.debona@ufrj.br>
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: brain,freesurfer,medical-imaging,mri,neuroimaging,nifti,tractography,vedo,viewer,vtk
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Environment :: X11 Applications :: Qt
|
|
15
|
+
Classifier: Intended Audience :: Healthcare Industry
|
|
16
|
+
Classifier: Intended Audience :: Science/Research
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
25
|
+
Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
|
|
26
|
+
Classifier: Topic :: Scientific/Engineering :: Visualization
|
|
27
|
+
Requires-Python: >=3.9
|
|
28
|
+
Requires-Dist: matplotlib>=3.7
|
|
29
|
+
Requires-Dist: nibabel>=5.0
|
|
30
|
+
Requires-Dist: numpy>=1.24
|
|
31
|
+
Requires-Dist: pyside6-essentials>=6.5
|
|
32
|
+
Requires-Dist: vedo>=2024.5
|
|
33
|
+
Requires-Dist: vtk>=9.2
|
|
34
|
+
Provides-Extra: dev
|
|
35
|
+
Requires-Dist: build>=1.0; extra == 'dev'
|
|
36
|
+
Requires-Dist: pytest>=7; extra == 'dev'
|
|
37
|
+
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
38
|
+
Requires-Dist: twine>=5; extra == 'dev'
|
|
39
|
+
Provides-Extra: open3d
|
|
40
|
+
Requires-Dist: open3d>=0.17; extra == 'open3d'
|
|
41
|
+
Provides-Extra: test
|
|
42
|
+
Requires-Dist: pytest>=7; extra == 'test'
|
|
43
|
+
Description-Content-Type: text/markdown
|
|
44
|
+
|
|
45
|
+
<p align="center">
|
|
46
|
+
<img src="https://raw.githubusercontent.com/rdneuro/neuroviewer/main/src/neuroviewer/resources/neuroviewer.png" width="128" alt="Neuroviewer icon">
|
|
47
|
+
</p>
|
|
48
|
+
|
|
49
|
+
<h1 align="center">Neuroviewer</h1>
|
|
50
|
+
|
|
51
|
+
<p align="center">
|
|
52
|
+
A freeview-like neuroimaging viewer written in pure Python.<br>
|
|
53
|
+
PySide6 interface, vedo/VTK rendering, nibabel reading. Native desktop app, no browser, no server.
|
|
54
|
+
</p>
|
|
55
|
+
|
|
56
|
+
<p align="center">
|
|
57
|
+
<a href="https://pypi.org/project/neuroviewer/"><img src="https://img.shields.io/pypi/v/neuroviewer.svg" alt="PyPI"></a>
|
|
58
|
+
<a href="https://pypi.org/project/neuroviewer/"><img src="https://img.shields.io/pypi/pyversions/neuroviewer.svg" alt="Python versions"></a>
|
|
59
|
+
<a href="https://github.com/rdneuro/neuroviewer/actions/workflows/tests.yml"><img src="https://github.com/rdneuro/neuroviewer/actions/workflows/tests.yml/badge.svg" alt="Tests"></a>
|
|
60
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="MIT license"></a>
|
|
61
|
+
</p>
|
|
62
|
+
|
|
63
|
+
<!--
|
|
64
|
+
Screenshot: save one with the toolbar "Screenshot" button (real T1 + aseg,
|
|
65
|
+
1+3 layout) as docs/screenshot.png, commit it, then uncomment:
|
|
66
|
+
<p align="center"><img src="https://raw.githubusercontent.com/rdneuro/neuroviewer/main/docs/screenshot.png" width="900" alt="Neuroviewer"></p>
|
|
67
|
+
-->
|
|
68
|
+
|
|
69
|
+
## Install
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
pip install neuroviewer # core viewer
|
|
73
|
+
pip install "neuroviewer[open3d]" # + Open3D point-cloud tools
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Then:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
neuroviewer # empty window
|
|
80
|
+
neuroviewer T1.mgz aseg.mgz lh.pial # open files at start
|
|
81
|
+
python -m neuroviewer # same thing
|
|
82
|
+
neuroviewer --install-desktop # Linux: add a menu entry with icon
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Requirements: Python 3.9+, an OpenGL-capable display. On Linux, Qt 6.5+
|
|
86
|
+
needs `libxcb-cursor0` (`sudo apt install libxcb-cursor0`). A self-contained
|
|
87
|
+
Linux AppImage is attached to each GitHub release.
|
|
88
|
+
|
|
89
|
+
## What it does
|
|
90
|
+
|
|
91
|
+
- **Files** (left): folder tree filtered to neuroimaging files, plus a
|
|
92
|
+
recursive search under the root folder (name filter such as `aseg`,
|
|
93
|
+
`*T1*.nii.gz`; blank = everything). Double click opens a file.
|
|
94
|
+
- **Views** (centre): one main view and three side views (layout `1+3`,
|
|
95
|
+
`2x2` or `1x1`). Click a panel title, a toolbar button or `Ctrl+1..4` to put
|
|
96
|
+
3D / sagittal / coronal / axial in the main position.
|
|
97
|
+
- **Layers** (right): one entry per volume, top of the list drawn on top.
|
|
98
|
+
Colormap, window min/max, opacity, "transparent below min", frame (4D).
|
|
99
|
+
- **3D scene** (right, second tab): slices / bounding box / volume rendering
|
|
100
|
+
toggles, the **Tools** menu and the list of 3D objects (surfaces,
|
|
101
|
+
streamlines, isosurfaces, point clouds) with visibility, colour, opacity.
|
|
102
|
+
- **Time course** (bottom): signal of the active 4D layer at the cursor.
|
|
103
|
+
- **Status bar**: cursor in RAS mm, voxel index and value of every visible
|
|
104
|
+
layer (with the FreeSurfer label name for label maps).
|
|
105
|
+
|
|
106
|
+
### Mouse and keys
|
|
107
|
+
|
|
108
|
+
| where | action |
|
|
109
|
+
|---|---|
|
|
110
|
+
| 2D | left click / drag: move cursor; shift+left or middle drag: pan; right drag or wheel: zoom |
|
|
111
|
+
| 2D | Up / PageUp, Down / PageDown: next / previous slice; `r`: fit slice |
|
|
112
|
+
| 3D | left drag: rotate; middle: pan; right / wheel: zoom; **double click** on a slice or surface: move cursor; `r`: reset camera |
|
|
113
|
+
|
|
114
|
+
### Formats
|
|
115
|
+
|
|
116
|
+
- Volumes: `.nii`, `.nii.gz`, `.mgz`, `.mgh`, Analyze `.img/.hdr`, MINC `.mnc`, PAR/REC.
|
|
117
|
+
- Surfaces: FreeSurfer `lh.pial`, `rh.white`, ... (shifted to scanner RAS with
|
|
118
|
+
`c_ras`) and GIfTI `.surf.gii`.
|
|
119
|
+
- Streamlines: `.trk`, `.tck` (direction-coloured, subsampled to
|
|
120
|
+
`TRACT_MAX_STREAMLINES`).
|
|
121
|
+
|
|
122
|
+
### Coordinates
|
|
123
|
+
|
|
124
|
+
Everything is kept in world coordinates (scanner RAS, mm). Volumes are
|
|
125
|
+
reoriented to RAS+ voxel order on load (FreeSurfer conformed `.mgz` is LIA on
|
|
126
|
+
disk) and keep their full affine. The first loaded volume defines the display
|
|
127
|
+
grid; every other layer is sampled onto it through its own affine, so layers
|
|
128
|
+
with different resolutions or fields of view overlay correctly. Oblique
|
|
129
|
+
volumes are resampled for display.
|
|
130
|
+
|
|
131
|
+
Label maps are detected by file name (`aseg`, `aparc`, `ribbon`, ...) plus
|
|
132
|
+
integer values. Their colours come from `$FREESURFER_HOME/FreeSurferColorLUT.txt`
|
|
133
|
+
(or `FREESURFER_LUT_PATH` in the config), with generated colours as fallback.
|
|
134
|
+
|
|
135
|
+
## Configuration
|
|
136
|
+
|
|
137
|
+
The packaged defaults live in `neuroviewer/config.py`. Do not edit that copy;
|
|
138
|
+
create your own once and edit it:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
neuroviewer --write-config # writes ~/.config/neuroviewer/config.py (Linux/macOS)
|
|
142
|
+
neuroviewer --config-path # shows where it is (%APPDATA%\neuroviewer on Windows)
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
It is plain Python with the same names (radiological convention, colours,
|
|
146
|
+
layout, label hints, point-cloud size, Open3D parameters, screenshot scale,
|
|
147
|
+
...). Keep only the lines you change. Set `NEUROVIEWER_CONFIG` to use another
|
|
148
|
+
file. An existing file is never overwritten unless you pass `--force`.
|
|
149
|
+
|
|
150
|
+
## Tools (extension point)
|
|
151
|
+
|
|
152
|
+
`neuroviewer/tools.py` holds a registry. A tool is one function that receives
|
|
153
|
+
a context (layers, active layer, cursor, grid, dialogs) and returns 3D objects:
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
from neuroviewer.tools import register_tool
|
|
157
|
+
|
|
158
|
+
@register_tool("My tool", "What it does.")
|
|
159
|
+
def my_tool(ctx):
|
|
160
|
+
layer = ctx.require_active()
|
|
161
|
+
arr, origin, spacing = ctx.aligned(layer)
|
|
162
|
+
...
|
|
163
|
+
return [("result name", vedo_object, "mesh")]
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Built-in tools: isosurface, label surfaces (e.g. `17 53`), cortex point cloud
|
|
167
|
+
from FreeSurfer labels, threshold point cloud, point cloud from the last
|
|
168
|
+
surface, Open3D voxel downsampling and Open3D Poisson reconstruction. Open3D
|
|
169
|
+
is optional; it is used as a compute engine and the result is shown in vedo.
|
|
170
|
+
|
|
171
|
+
## Troubleshooting
|
|
172
|
+
|
|
173
|
+
- **`Could not find the Qt platform plugin "xcb"`**: another Qt install is
|
|
174
|
+
exporting its own plugin path (Spyder/PyQt5, opencv-python, conda qt).
|
|
175
|
+
Neuroviewer resets it to the PySide6 plugins and prints the folder it used
|
|
176
|
+
(`[qt] ...`). Start it from a terminal, not from the Spyder/IPython console.
|
|
177
|
+
If it still fails, run `QT_DEBUG_PLUGINS=1 neuroviewer`.
|
|
178
|
+
- **Black views on Wayland**: `QT_QPA_PLATFORM=xcb neuroviewer`.
|
|
179
|
+
- **Missing `libxcb-cursor0`** (Qt 6.5+ on Linux): install it with your
|
|
180
|
+
package manager.
|
|
181
|
+
|
|
182
|
+
## Development
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
git clone https://github.com/rdneuro/neuroviewer.git
|
|
186
|
+
cd neuroviewer
|
|
187
|
+
pip install -e ".[dev]"
|
|
188
|
+
pytest
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
See [RELEASING.md](RELEASING.md) for the PyPI and AppImage release steps.
|
|
192
|
+
|
|
193
|
+
## License
|
|
194
|
+
|
|
195
|
+
MIT. See [LICENSE](LICENSE). Neuroviewer depends on PySide6 (LGPLv3), VTK
|
|
196
|
+
(BSD), vedo (MIT), nibabel (MIT) and matplotlib (PSF-based); the AppImage
|
|
197
|
+
redistributes them under their own licenses.
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/rdneuro/neuroviewer/main/src/neuroviewer/resources/neuroviewer.png" width="128" alt="Neuroviewer icon">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">Neuroviewer</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
A freeview-like neuroimaging viewer written in pure Python.<br>
|
|
9
|
+
PySide6 interface, vedo/VTK rendering, nibabel reading. Native desktop app, no browser, no server.
|
|
10
|
+
</p>
|
|
11
|
+
|
|
12
|
+
<p align="center">
|
|
13
|
+
<a href="https://pypi.org/project/neuroviewer/"><img src="https://img.shields.io/pypi/v/neuroviewer.svg" alt="PyPI"></a>
|
|
14
|
+
<a href="https://pypi.org/project/neuroviewer/"><img src="https://img.shields.io/pypi/pyversions/neuroviewer.svg" alt="Python versions"></a>
|
|
15
|
+
<a href="https://github.com/rdneuro/neuroviewer/actions/workflows/tests.yml"><img src="https://github.com/rdneuro/neuroviewer/actions/workflows/tests.yml/badge.svg" alt="Tests"></a>
|
|
16
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="MIT license"></a>
|
|
17
|
+
</p>
|
|
18
|
+
|
|
19
|
+
<!--
|
|
20
|
+
Screenshot: save one with the toolbar "Screenshot" button (real T1 + aseg,
|
|
21
|
+
1+3 layout) as docs/screenshot.png, commit it, then uncomment:
|
|
22
|
+
<p align="center"><img src="https://raw.githubusercontent.com/rdneuro/neuroviewer/main/docs/screenshot.png" width="900" alt="Neuroviewer"></p>
|
|
23
|
+
-->
|
|
24
|
+
|
|
25
|
+
## Install
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install neuroviewer # core viewer
|
|
29
|
+
pip install "neuroviewer[open3d]" # + Open3D point-cloud tools
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Then:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
neuroviewer # empty window
|
|
36
|
+
neuroviewer T1.mgz aseg.mgz lh.pial # open files at start
|
|
37
|
+
python -m neuroviewer # same thing
|
|
38
|
+
neuroviewer --install-desktop # Linux: add a menu entry with icon
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Requirements: Python 3.9+, an OpenGL-capable display. On Linux, Qt 6.5+
|
|
42
|
+
needs `libxcb-cursor0` (`sudo apt install libxcb-cursor0`). A self-contained
|
|
43
|
+
Linux AppImage is attached to each GitHub release.
|
|
44
|
+
|
|
45
|
+
## What it does
|
|
46
|
+
|
|
47
|
+
- **Files** (left): folder tree filtered to neuroimaging files, plus a
|
|
48
|
+
recursive search under the root folder (name filter such as `aseg`,
|
|
49
|
+
`*T1*.nii.gz`; blank = everything). Double click opens a file.
|
|
50
|
+
- **Views** (centre): one main view and three side views (layout `1+3`,
|
|
51
|
+
`2x2` or `1x1`). Click a panel title, a toolbar button or `Ctrl+1..4` to put
|
|
52
|
+
3D / sagittal / coronal / axial in the main position.
|
|
53
|
+
- **Layers** (right): one entry per volume, top of the list drawn on top.
|
|
54
|
+
Colormap, window min/max, opacity, "transparent below min", frame (4D).
|
|
55
|
+
- **3D scene** (right, second tab): slices / bounding box / volume rendering
|
|
56
|
+
toggles, the **Tools** menu and the list of 3D objects (surfaces,
|
|
57
|
+
streamlines, isosurfaces, point clouds) with visibility, colour, opacity.
|
|
58
|
+
- **Time course** (bottom): signal of the active 4D layer at the cursor.
|
|
59
|
+
- **Status bar**: cursor in RAS mm, voxel index and value of every visible
|
|
60
|
+
layer (with the FreeSurfer label name for label maps).
|
|
61
|
+
|
|
62
|
+
### Mouse and keys
|
|
63
|
+
|
|
64
|
+
| where | action |
|
|
65
|
+
|---|---|
|
|
66
|
+
| 2D | left click / drag: move cursor; shift+left or middle drag: pan; right drag or wheel: zoom |
|
|
67
|
+
| 2D | Up / PageUp, Down / PageDown: next / previous slice; `r`: fit slice |
|
|
68
|
+
| 3D | left drag: rotate; middle: pan; right / wheel: zoom; **double click** on a slice or surface: move cursor; `r`: reset camera |
|
|
69
|
+
|
|
70
|
+
### Formats
|
|
71
|
+
|
|
72
|
+
- Volumes: `.nii`, `.nii.gz`, `.mgz`, `.mgh`, Analyze `.img/.hdr`, MINC `.mnc`, PAR/REC.
|
|
73
|
+
- Surfaces: FreeSurfer `lh.pial`, `rh.white`, ... (shifted to scanner RAS with
|
|
74
|
+
`c_ras`) and GIfTI `.surf.gii`.
|
|
75
|
+
- Streamlines: `.trk`, `.tck` (direction-coloured, subsampled to
|
|
76
|
+
`TRACT_MAX_STREAMLINES`).
|
|
77
|
+
|
|
78
|
+
### Coordinates
|
|
79
|
+
|
|
80
|
+
Everything is kept in world coordinates (scanner RAS, mm). Volumes are
|
|
81
|
+
reoriented to RAS+ voxel order on load (FreeSurfer conformed `.mgz` is LIA on
|
|
82
|
+
disk) and keep their full affine. The first loaded volume defines the display
|
|
83
|
+
grid; every other layer is sampled onto it through its own affine, so layers
|
|
84
|
+
with different resolutions or fields of view overlay correctly. Oblique
|
|
85
|
+
volumes are resampled for display.
|
|
86
|
+
|
|
87
|
+
Label maps are detected by file name (`aseg`, `aparc`, `ribbon`, ...) plus
|
|
88
|
+
integer values. Their colours come from `$FREESURFER_HOME/FreeSurferColorLUT.txt`
|
|
89
|
+
(or `FREESURFER_LUT_PATH` in the config), with generated colours as fallback.
|
|
90
|
+
|
|
91
|
+
## Configuration
|
|
92
|
+
|
|
93
|
+
The packaged defaults live in `neuroviewer/config.py`. Do not edit that copy;
|
|
94
|
+
create your own once and edit it:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
neuroviewer --write-config # writes ~/.config/neuroviewer/config.py (Linux/macOS)
|
|
98
|
+
neuroviewer --config-path # shows where it is (%APPDATA%\neuroviewer on Windows)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
It is plain Python with the same names (radiological convention, colours,
|
|
102
|
+
layout, label hints, point-cloud size, Open3D parameters, screenshot scale,
|
|
103
|
+
...). Keep only the lines you change. Set `NEUROVIEWER_CONFIG` to use another
|
|
104
|
+
file. An existing file is never overwritten unless you pass `--force`.
|
|
105
|
+
|
|
106
|
+
## Tools (extension point)
|
|
107
|
+
|
|
108
|
+
`neuroviewer/tools.py` holds a registry. A tool is one function that receives
|
|
109
|
+
a context (layers, active layer, cursor, grid, dialogs) and returns 3D objects:
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
from neuroviewer.tools import register_tool
|
|
113
|
+
|
|
114
|
+
@register_tool("My tool", "What it does.")
|
|
115
|
+
def my_tool(ctx):
|
|
116
|
+
layer = ctx.require_active()
|
|
117
|
+
arr, origin, spacing = ctx.aligned(layer)
|
|
118
|
+
...
|
|
119
|
+
return [("result name", vedo_object, "mesh")]
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Built-in tools: isosurface, label surfaces (e.g. `17 53`), cortex point cloud
|
|
123
|
+
from FreeSurfer labels, threshold point cloud, point cloud from the last
|
|
124
|
+
surface, Open3D voxel downsampling and Open3D Poisson reconstruction. Open3D
|
|
125
|
+
is optional; it is used as a compute engine and the result is shown in vedo.
|
|
126
|
+
|
|
127
|
+
## Troubleshooting
|
|
128
|
+
|
|
129
|
+
- **`Could not find the Qt platform plugin "xcb"`**: another Qt install is
|
|
130
|
+
exporting its own plugin path (Spyder/PyQt5, opencv-python, conda qt).
|
|
131
|
+
Neuroviewer resets it to the PySide6 plugins and prints the folder it used
|
|
132
|
+
(`[qt] ...`). Start it from a terminal, not from the Spyder/IPython console.
|
|
133
|
+
If it still fails, run `QT_DEBUG_PLUGINS=1 neuroviewer`.
|
|
134
|
+
- **Black views on Wayland**: `QT_QPA_PLATFORM=xcb neuroviewer`.
|
|
135
|
+
- **Missing `libxcb-cursor0`** (Qt 6.5+ on Linux): install it with your
|
|
136
|
+
package manager.
|
|
137
|
+
|
|
138
|
+
## Development
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
git clone https://github.com/rdneuro/neuroviewer.git
|
|
142
|
+
cd neuroviewer
|
|
143
|
+
pip install -e ".[dev]"
|
|
144
|
+
pytest
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
See [RELEASING.md](RELEASING.md) for the PyPI and AppImage release steps.
|
|
148
|
+
|
|
149
|
+
## License
|
|
150
|
+
|
|
151
|
+
MIT. See [LICENSE](LICENSE). Neuroviewer depends on PySide6 (LGPLv3), VTK
|
|
152
|
+
(BSD), vedo (MIT), nibabel (MIT) and matplotlib (PSF-based); the AppImage
|
|
153
|
+
redistributes them under their own licenses.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Releasing
|
|
2
|
+
|
|
3
|
+
## One-time setup
|
|
4
|
+
|
|
5
|
+
1. **Create the GitHub repository** `rdneuro/neuroviewer` and push `main`.
|
|
6
|
+
2. **Reserve the PyPI name with trusted publishing** (no API token needed):
|
|
7
|
+
on <https://pypi.org/manage/account/publishing/>, add a *pending
|
|
8
|
+
publisher* with
|
|
9
|
+
- PyPI project name: `neuroviewer`
|
|
10
|
+
- Owner: `rdneuro`, Repository: `neuroviewer`
|
|
11
|
+
- Workflow name: `release.yml`
|
|
12
|
+
- Environment name: `pypi`
|
|
13
|
+
3. On GitHub, *Settings > Environments*, create an environment called `pypi`
|
|
14
|
+
(optionally require your approval before each upload).
|
|
15
|
+
|
|
16
|
+
## Every release
|
|
17
|
+
|
|
18
|
+
1. Bump `__version__` in `src/neuroviewer/__init__.py`.
|
|
19
|
+
2. Add the section to `CHANGELOG.md`.
|
|
20
|
+
3. Check locally:
|
|
21
|
+
```bash
|
|
22
|
+
pip install -e ".[dev]"
|
|
23
|
+
pytest
|
|
24
|
+
python -m build && twine check --strict dist/*
|
|
25
|
+
```
|
|
26
|
+
4. Commit, tag and push:
|
|
27
|
+
```bash
|
|
28
|
+
git commit -am "Release 0.1.1"
|
|
29
|
+
git tag v0.1.1
|
|
30
|
+
git push origin main --tags
|
|
31
|
+
```
|
|
32
|
+
5. The `Release` workflow checks that the tag matches `__version__`,
|
|
33
|
+
publishes the wheel and sdist to PyPI and attaches
|
|
34
|
+
`Neuroviewer-<version>-x86_64.AppImage` to the GitHub release.
|
|
35
|
+
|
|
36
|
+
## Manual upload (fallback)
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
python -m build
|
|
40
|
+
twine upload --repository testpypi dist/* # optional dry run on TestPyPI
|
|
41
|
+
twine upload dist/*
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
PyPI never accepts the same version twice, so a broken upload needs a new
|
|
45
|
+
version number (for example 0.1.1), not a re-upload.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# AppImage
|
|
2
|
+
|
|
3
|
+
A single-file Linux build that bundles its own Python and every dependency.
|
|
4
|
+
Users download it, `chmod +x`, and run it; nothing is installed.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
bash packaging/appimage/build_appimage.sh # from this source tree
|
|
8
|
+
bash packaging/appimage/build_appimage.sh --pypi # from the PyPI release
|
|
9
|
+
PY_VERSION=3.11 bash packaging/appimage/build_appimage.sh
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
How it works: [python-appimage](https://github.com/niess/python-appimage)
|
|
13
|
+
downloads a relocatable manylinux Python AppImage, `pip install`s the
|
|
14
|
+
Neuroviewer wheel into it (PySide6-Essentials, VTK, vedo, nibabel, numpy,
|
|
15
|
+
matplotlib come along as binary wheels), and the script adds the small X11
|
|
16
|
+
libraries Qt 6.5+ needs (`libxcb-cursor` and three helpers) before packing
|
|
17
|
+
with appimagetool.
|
|
18
|
+
|
|
19
|
+
Notes:
|
|
20
|
+
|
|
21
|
+
- The GitHub workflow `.github/workflows/release.yml` builds it on Ubuntu
|
|
22
|
+
22.04 for every `v*` tag and attaches it to the GitHub release.
|
|
23
|
+
- Target machines need glibc 2.28 or newer (Ubuntu 20.04+, Debian 10+,
|
|
24
|
+
RHEL/Rocky 8+) and a working OpenGL driver. The driver always comes from the
|
|
25
|
+
host; it is never bundled.
|
|
26
|
+
- Running an AppImage needs FUSE 2 (`libfuse2` / `libfuse2t64` on Ubuntu).
|
|
27
|
+
Without it: `./Neuroviewer-*.AppImage --appimage-extract-and-run`.
|
|
28
|
+
- Expected size: roughly 200-300 MB (VTK and Qt are most of it).
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Build Neuroviewer-<version>-x86_64.AppImage with python-appimage.
|
|
3
|
+
#
|
|
4
|
+
# bash packaging/appimage/build_appimage.sh # from the local source tree
|
|
5
|
+
# bash packaging/appimage/build_appimage.sh --pypi # from the published PyPI release
|
|
6
|
+
#
|
|
7
|
+
# Environment:
|
|
8
|
+
# PY_VERSION Python inside the AppImage (default 3.12)
|
|
9
|
+
#
|
|
10
|
+
# Needs: python3 with pip, internet access (GitHub for the python-appimage base
|
|
11
|
+
# image and appimagetool, PyPI for the wheels). Build on the OLDEST distro you
|
|
12
|
+
# want to support (e.g. Ubuntu 22.04): the bundled X11 fallback libraries are
|
|
13
|
+
# copied from the build machine. The result needs glibc >= 2.28 on the target
|
|
14
|
+
# (PySide6 wheels are manylinux_2_28) and the host's OpenGL driver.
|
|
15
|
+
set -euo pipefail
|
|
16
|
+
|
|
17
|
+
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
18
|
+
ROOT="$(cd "${HERE}/../.." && pwd)"
|
|
19
|
+
PY_VERSION="${PY_VERSION:-3.12}"
|
|
20
|
+
FROM_PYPI=0
|
|
21
|
+
[[ "${1:-}" == "--pypi" ]] && FROM_PYPI=1
|
|
22
|
+
|
|
23
|
+
VERSION="$(sed -n 's/^__version__ = "\(.*\)"/\1/p' "${ROOT}/src/neuroviewer/__init__.py")"
|
|
24
|
+
BUILD="${HERE}/build"
|
|
25
|
+
RECIPE="${BUILD}/neuroviewer"
|
|
26
|
+
echo "[appimage] Neuroviewer ${VERSION} | Python ${PY_VERSION} | source: $([[ ${FROM_PYPI} == 1 ]] && echo PyPI || echo local tree)"
|
|
27
|
+
|
|
28
|
+
rm -rf "${BUILD}"
|
|
29
|
+
mkdir -p "${RECIPE}"
|
|
30
|
+
|
|
31
|
+
python3 -m pip install --quiet --upgrade "python-appimage>=1.4" build
|
|
32
|
+
|
|
33
|
+
# ------------------------------------------------------------------ recipe
|
|
34
|
+
if [[ ${FROM_PYPI} == 1 ]]; then
|
|
35
|
+
echo "neuroviewer==${VERSION}" > "${RECIPE}/requirements.txt"
|
|
36
|
+
else
|
|
37
|
+
python3 -m build --wheel --outdir "${BUILD}/dist" "${ROOT}"
|
|
38
|
+
WHEEL="$(ls "${BUILD}"/dist/neuroviewer-"${VERSION}"-*.whl)"
|
|
39
|
+
echo "${WHEEL}" > "${RECIPE}/requirements.txt"
|
|
40
|
+
fi
|
|
41
|
+
cp "${HERE}/entrypoint.sh" "${RECIPE}/entrypoint.sh"
|
|
42
|
+
cp "${ROOT}/src/neuroviewer/resources/neuroviewer.desktop" "${RECIPE}/neuroviewer.desktop"
|
|
43
|
+
cp "${ROOT}/src/neuroviewer/resources/neuroviewer.png" "${RECIPE}/neuroviewer.png"
|
|
44
|
+
sed -e "s/@VERSION@/${VERSION}/" -e "s/@DATE@/$(date +%Y-%m-%d)/" \
|
|
45
|
+
"${HERE}/neuroviewer.appdata.xml" > "${RECIPE}/neuroviewer.appdata.xml"
|
|
46
|
+
|
|
47
|
+
# ------------------------------------------------------------------ AppDir
|
|
48
|
+
cd "${BUILD}"
|
|
49
|
+
python3 -m python_appimage build app --no-packaging -p "${PY_VERSION}" "${RECIPE}"
|
|
50
|
+
APPDIR="${BUILD}/Neuroviewer-$(uname -m)"
|
|
51
|
+
[[ -d "${APPDIR}" ]] || { echo "[appimage] AppDir not found: ${APPDIR}"; exit 1; }
|
|
52
|
+
|
|
53
|
+
# X11 fallback libraries for Qt >= 6.5 (often missing on minimal installs).
|
|
54
|
+
EXTRA="${APPDIR}/usr/lib/neuroviewer-extra"
|
|
55
|
+
mkdir -p "${EXTRA}"
|
|
56
|
+
for lib in libxcb-cursor.so.0 libxcb-image.so.0 libxcb-render-util.so.0 libxcb-util.so.1; do
|
|
57
|
+
src="$(ldconfig -p | awk -v l="${lib}" '$1 == l && tolower($0) ~ /x86-64|aarch64/ {print $NF; exit}')"
|
|
58
|
+
if [[ -n "${src}" ]]; then
|
|
59
|
+
cp -L "${src}" "${EXTRA}/"
|
|
60
|
+
echo "[appimage] bundled ${lib}"
|
|
61
|
+
else
|
|
62
|
+
echo "[appimage] WARNING: ${lib} not found on the build machine (apt install libxcb-cursor0); not bundled"
|
|
63
|
+
fi
|
|
64
|
+
done
|
|
65
|
+
|
|
66
|
+
# Trim what a viewer never needs (tests, caches, Qt translations stay).
|
|
67
|
+
find "${APPDIR}/opt" -type d -name "__pycache__" -prune -exec rm -rf {} +
|
|
68
|
+
find "${APPDIR}/opt" -type d \( -name "tests" -o -name "test" \) -path "*site-packages/*" -prune -exec rm -rf {} + || true
|
|
69
|
+
|
|
70
|
+
# ------------------------------------------------------------------ pack
|
|
71
|
+
OUT="Neuroviewer-${VERSION}-$(uname -m).AppImage"
|
|
72
|
+
python3 - "${APPDIR}" "${OUT}" <<'EOF'
|
|
73
|
+
import sys
|
|
74
|
+
from python_appimage.appimage import build_appimage
|
|
75
|
+
build_appimage(sys.argv[1], destination=sys.argv[2])
|
|
76
|
+
EOF
|
|
77
|
+
mv "${OUT}" "${ROOT}/${OUT}"
|
|
78
|
+
chmod +x "${ROOT}/${OUT}"
|
|
79
|
+
echo "[appimage] done: ${ROOT}/${OUT} ($(du -h "${ROOT}/${OUT}" | cut -f1))"
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#! /bin/bash
|
|
2
|
+
# AppImage entry point (python-appimage template; {{ ... }} is filled at build time).
|
|
3
|
+
# The fallback libraries (libxcb-cursor and friends, needed by Qt >= 6.5 on X11)
|
|
4
|
+
# go first in the search path only for this process; -I isolates the bundled
|
|
5
|
+
# Python from the user's site-packages and PYTHONPATH (no clash with a Spyder
|
|
6
|
+
# or conda Qt installation).
|
|
7
|
+
export LD_LIBRARY_PATH="${APPDIR}/usr/lib/neuroviewer-extra${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}"
|
|
8
|
+
{{ python-executable }} -I -m neuroviewer "$@"
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<component type="desktop-application">
|
|
3
|
+
<id>io.github.rdneuro.neuroviewer</id>
|
|
4
|
+
<metadata_license>CC0-1.0</metadata_license>
|
|
5
|
+
<project_license>MIT</project_license>
|
|
6
|
+
<name>Neuroviewer</name>
|
|
7
|
+
<summary>Freeview-like neuroimaging viewer in pure Python</summary>
|
|
8
|
+
<description>
|
|
9
|
+
<p>
|
|
10
|
+
Neuroviewer opens NIfTI, MGH/MGZ, Analyze, MINC and PAR/REC volumes,
|
|
11
|
+
FreeSurfer and GIfTI surfaces and .trk/.tck streamlines, with a main 3D
|
|
12
|
+
view and sagittal, coronal and axial views that swap on click.
|
|
13
|
+
</p>
|
|
14
|
+
<p>
|
|
15
|
+
Layers keep their full affine in scanner RAS coordinates, label maps use
|
|
16
|
+
the FreeSurfer colour table, and a tools menu builds isosurfaces, label
|
|
17
|
+
surfaces and point clouds.
|
|
18
|
+
</p>
|
|
19
|
+
</description>
|
|
20
|
+
<launchable type="desktop-id">neuroviewer.desktop</launchable>
|
|
21
|
+
<url type="homepage">https://github.com/rdneuro/neuroviewer</url>
|
|
22
|
+
<url type="bugtracker">https://github.com/rdneuro/neuroviewer/issues</url>
|
|
23
|
+
<developer id="io.github.rdneuro">
|
|
24
|
+
<name>Rodrigo Debona</name>
|
|
25
|
+
</developer>
|
|
26
|
+
<categories>
|
|
27
|
+
<category>Science</category>
|
|
28
|
+
<category>MedicalSoftware</category>
|
|
29
|
+
</categories>
|
|
30
|
+
<content_rating type="oars-1.1"/>
|
|
31
|
+
<releases>
|
|
32
|
+
<release version="@VERSION@" date="@DATE@"/>
|
|
33
|
+
</releases>
|
|
34
|
+
</component>
|