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.
@@ -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>