Skip to content

Commit 63dc0e5

Browse files
committed
more tests + joss manuscript
1 parent db568f9 commit 63dc0e5

14 files changed

Lines changed: 571 additions & 132 deletions

File tree

.github/workflows/tests.yml

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,7 @@ jobs:
2020
- name: Cache downloaded data
2121
uses: actions/cache@v4
2222
with:
23-
# The folder where pooch saves files on Linux
2423
path: ~/.cache/yabplot
25-
# Check registry.txt to decide if we need a fresh download
26-
# (using **/ finds the file even if your folder structure varies)
2724
key: yabplot-data-${{ hashFiles('**/registry.txt') }}
2825
restore-keys: |
2926
yabplot-data-
@@ -33,21 +30,18 @@ jobs:
3330
with:
3431
python-version: ${{ matrix.python-version }}
3532

36-
# 1. Install System Libraries for 3D Graphics (Headless)
3733
- name: Install system dependencies (Linux)
3834
run: |
3935
sudo apt-get update
4036
sudo apt-get install -y libgl1 xvfb
4137
42-
# 2. Install your package and testing tools
4338
- name: Install dependencies
4439
run: |
4540
python -m pip install --upgrade pip
4641
pip install pytest
4742
pip install ".[docs]" # Installs yabplot + deps
4843
49-
# 3. Run Pytest inside a virtual monitor
5044
- name: Run tests
5145
run: |
5246
# xvfb-run allows graphical apps to run without a real monitor
53-
xvfb-run -a pytest
47+
PYTHONPATH=. xvfb-run -a pytest tests/

.gitignore

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,18 @@ examples_/
55
docs/tutorials/tutorial_data/
66
dev/
77
site/
8-
paper/
9-
paperprep/
108
tutorial_data/
119
test.ipynb
1210
todo.md
1311
test_data/
1412
# *.ipynb
1513
test.py
14+
# paper/figures/
1615

1716
.DS_Store
1817
__pycache__/
1918
.venv
19+
.pytest_cache/
2020

2121
# Byte-compiled / optimized / DLL files
2222
__pycache__/

paper/figures/overview_joss.pdf

3.86 MB
Binary file not shown.

paper/paper.bib

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
@software{Brett:2024,
2+
title = {nipy/nibabel: 5.3.2},
3+
author = {Brett, Matthew and Markiewicz, Christopher J. and Hanke, Michael and C{\^o}t{\'e}, Marc-Alexandre and Cipollini, Ben and McCarthy, Paul and Ghosh, Satrajit and Wassermann, Demian and Halchenko, Yaroslav O. and Forbes, Jessica and others},
4+
year = {2024},
5+
publisher = {Zenodo},
6+
version = {5.3.2},
7+
doi = {10.5281/zenodo.17833216},
8+
url = {https://doi.org/10.5281/zenodo.17833216}
9+
}
10+
11+
@article{Harris:2020,
12+
title = {Array programming with {NumPy}},
13+
author = {Harris, Charles R. and Millman, K. Jarrod and van der Walt, St{\'e}fan J. and Gommers, Ralf and Virtanen, Pauli and Cournapeau, David and Wieser, Eric and Taylor, Julian and Berg, Sebastian and Smith, Nathaniel J. and Kern, Robert and Picus, Matti and Hoyer, Stephan and van Kerkwijk, Marten H. and Brett, Matthew and Haldane, Allan and del R{\'i}o, Jaime Fern{\'a}ndez and Wiebe, Mark and Peterson, Pearu and G{\'e}rard-Marchant, Pierre and Sheppard, Kevin and Reddy, Tyler and Weckesser, Warren and Abbasi, Hameer and Gohlke, Christoph and Oliphant, Travis E.},
14+
journal = {Nature},
15+
volume = {585},
16+
pages = {357--362},
17+
year = {2020},
18+
doi = {10.1038/s41586-020-2649-2},
19+
url = {https://doi.org/10.1038/s41586-020-2649-2}
20+
}
21+
22+
@article{Virtanen:2020,
23+
title = {{SciPy} 1.0: Fundamental Algorithms for Scientific Computing in Python},
24+
author = {Virtanen, Pauli and Gommers, Ralf and Oliphant, Travis E. and Haberland, Matt and Reddy, Tyler and Cournapeau, David and Burovski, Evgeni and Peterson, Pearu and Weckesser, Warren and Bright, Jonathan and van der Walt, St{\'e}fan J. and Brett, Matthew and Wilson, Joshua and Millman, K. Jarrod and Mayorov, Nikolay and Nelson, Andrew R. J. and Jones, Eric and Kern, Robert and Larson, Eric and Carey, C. J. and Polat, {\.I}lhan and Feng, Yu and Moore, Eric W. and VanderPlas, Jake and Laxalde, Denis and Perktold, Josef and Cimrman, Robert and Henriksen, Ian and Quintero, E. A. and Harris, Charles R. and Archibald, Anne M. and Ribeiro, Ant{\^o}nio H. and Pedregosa, Fabian and van Mulbregt, Paul and SciPy 1.0 Contributors},
25+
journal = {Nature Methods},
26+
volume = {17},
27+
pages = {261--272},
28+
year = {2020},
29+
doi = {10.1038/s41592-019-0686-2},
30+
url = {https://doi.org/10.1038/s41592-019-0686-2}
31+
}
32+
33+
@inproceedings{McKinney:2010,
34+
title = {Data Structures for Statistical Computing in Python},
35+
author = {McKinney, Wes},
36+
journal = {SciPy 2010},
37+
year = {2010},
38+
doi = {10.25080/Majora-92bf1922-00a},
39+
url = {https://doi.org/10.25080/Majora-92bf1922-00a}
40+
}
41+
42+
@article{Uieda:2020,
43+
title = {Pooch: A friend to fetch your data files},
44+
author = {Uieda, Leonardo and Soler, Santiago Rub{\'e}n and Rampin, R{\'e}mi and van Kemenade, Hugo and Turk, Matthew and Shapero, Daniel and Banihirwe, Anderson and Leeman, John R.},
45+
journal = {Journal of Open Source Software},
46+
volume = {5},
47+
number = {45},
48+
pages = {1943},
49+
year = {2020},
50+
publisher = {The Open Journal},
51+
doi = {10.21105/joss.01943},
52+
url = {https://doi.org/10.21105/joss.01943}
53+
}
54+
55+
@article{vanDerWalt:2014,
56+
title = {scikit-image: image processing in Python},
57+
author = {van der Walt, St{\'e}fan and Sch{\"o}nberger, Johannes L. and Nunez-Iglesias, Juan and Boulogne, Fran{\c{c}}ois and Warner, Joshua D. and Yager, Neil and Gouillart, Emmanuelle and Yu, Tony and the scikit-image contributors},
58+
journal = {PeerJ},
59+
volume = {2},
60+
pages = {e453},
61+
year = {2014},
62+
doi = {10.7717/peerj.453},
63+
url = {https://doi.org/10.7717/peerj.453}
64+
}
65+
66+
@article{Sullivan:2019,
67+
title = {{PyVista}: {3D} plotting and mesh analysis through a streamlined interface for the {Visualization Toolkit} ({VTK})},
68+
author = {Sullivan, C. Bane and Kaszynski, Alexander A.},
69+
journal = {Journal of Open Source Software},
70+
volume = {4},
71+
number = {37},
72+
pages = {1450},
73+
year = {2019},
74+
publisher = {The Open Journal},
75+
doi = {10.21105/joss.01450},
76+
url = {https://doi.org/10.21105/joss.01450}
77+
}
78+
79+
@software{Jourdain:2025,
80+
title = {Kitware/trame: v3.10.0},
81+
author = {Jourdain, Sebastien and Avery, Patrick and Finet, Julien and Bhasin, Krishan and Harris, Chris and Bourdais, Jules and Vrabec, Karel and Macron, Lucie and others},
82+
year = {2025},
83+
publisher = {Zenodo},
84+
version = {v3.10.0},
85+
doi = {10.5281/zenodo.15540324},
86+
url = {https://doi.org/10.5281/zenodo.15540324}
87+
}
88+
89+
@article{Hunter:2007,
90+
title = {Matplotlib: A 2D Graphics Environment},
91+
author = {Hunter, John D.},
92+
journal = {Computing in Science \& Engineering},
93+
volume = {9},
94+
number = {3},
95+
pages = {90--95},
96+
year = {2007},
97+
publisher = {IEEE Computer Society},
98+
doi = {10.1109/MCSE.2007.55},
99+
url = {https://doi.org/10.1109/MCSE.2007.55}
100+
}
101+
102+
@article{Marcus:2011,
103+
title = {Informatics and Data Mining Tools and Strategies for the Human Connectome Project},
104+
author = {Marcus, Daniel S. and Harwell, John and Olsen, Timothy and Hodge, Michael and Glasser, Matthew F. and Prior, Fred and Jenkinson, Mark and Laumann, Timothy and Curtiss, Sandra W. and Van Essen, David C.},
105+
journal = {Frontiers in Neuroinformatics},
106+
volume = {5},
107+
pages = {4},
108+
year = {2011},
109+
doi = {10.3389/fninf.2011.00004},
110+
url = {https://doi.org/10.3389/fninf.2011.00004}
111+
}
112+
113+
@article{Tournier:2019,
114+
title = {{MRtrix3}: A fast, flexible and open software framework for medical image processing and visualisation},
115+
author = {Tournier, J.-D. and Smith, Robert E. and Raffelt, David and Tabbara, Rami and Dhollander, Thijs and Pietsch, Maximilian and Christiaens, Daan and Jeurissen, Ben and Yeh, Chun-Hung and Connelly, Alan},
116+
journal = {NeuroImage},
117+
volume = {202},
118+
pages = {116137},
119+
year = {2019},
120+
doi = {10.1016/j.neuroimage.2019.116137},
121+
url = {https://doi.org/10.1016/j.neuroimage.2019.116137}
122+
}
123+
124+
@article{Xia:2013,
125+
title = {BrainNet Viewer: A Network Visualization Tool for Human Brain Connectomics},
126+
author = {Xia, Mingrui and Wang, Jinhui and He, Yong},
127+
journal = {PLOS ONE},
128+
volume = {8},
129+
number = {7},
130+
pages = {e68910},
131+
year = {2013},
132+
doi = {10.1371/journal.pone.0068910},
133+
url = {https://doi.org/10.1371/journal.pone.0068910}
134+
}
135+
136+
@article{Abraham:2014,
137+
title = {Machine learning for neuroimaging with scikit-learn},
138+
author = {Abraham, Alexandre and Pedregosa, Fabian and Eickenberg, Michael and Gervais, Philippe and Mueller, Andreas and Kossaifi, Jean and Gramfort, Alexandre and Thirion, Bertrand and Varoquaux, Ga{\"e}l},
139+
journal = {Frontiers in Neuroinformatics},
140+
volume = {8},
141+
pages = {14},
142+
year = {2014},
143+
doi = {10.3389/fninf.2014.00014},
144+
url = {https://doi.org/10.3389/fninf.2014.00014}
145+
}
146+
147+
@article{VosdeWael:2020,
148+
title = {{BrainSpace}: a toolbox for the analysis of macroscale gradients in neuroimaging and connectomics datasets},
149+
author = {Vos de Wael, Reinder and Benkarim, Oualid and Paquola, Casey and Lariviere, Sara and Royer, Jessica and Tavakol, Shahin and Xu, Ting and Hong, Seok-Jun and Langs, Georg and Valk, Sofie L. and Mi{\v{s}}i{\'c}, Bratislav and Milham, Michael P. and Margulies, Daniel S. and Smallwood, Jonathan and Bernhardt, Boris C.},
150+
journal = {Communications Biology},
151+
volume = {3},
152+
number = {1},
153+
pages = {103},
154+
year = {2020},
155+
doi = {10.1038/s42003-020-0794-7},
156+
url = {https://doi.org/10.1038/s42003-020-0794-7}
157+
}

paper/paper.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
---
2+
title: 'yabplot: yet another brain plot for unified neuroimaging visualization in Python'
3+
tags:
4+
- Python
5+
- neuroimaging
6+
- visualization
7+
- neuroscience
8+
- 3D
9+
authors:
10+
- name: Toomas Erik Anijärv
11+
orcid: 0000-0002-3650-4230
12+
affiliation: 1
13+
affiliations:
14+
- name: Clinical Memory Research Unit, Department of Clinical Sciences Malmö, Faculty of Medicine, Lund University, Lund, Sweden
15+
index: 1
16+
date: 11 May 2026
17+
bibliography: paper.bib
18+
---
19+
20+
# Summary
21+
22+
Neuroimaging analyses often produce results in several anatomical representations. A single study may include values defined in atlas-based regions, such as cortical parcels, subcortical structures, white-matter bundles, or connectivity matrices, as well as continuous voxel-wise and vertex-wise maps. Communicating these results requires figures that are anatomically interpretable, visually consistent, and reproducible from the same computational workflow used to generate the data.
23+
24+
`yabplot` (yet another brain plot) is an open-source Python package for creating three-dimensional neuroimaging visualizations across these representations. It provides a unified interface for plotting cortical parcellations, vertex-wise cortical data, voxel-wise volumetric data, subcortical structures, white-matter bundles, tractometry results, and connectome graphs. The package is intended for researchers who want to create publication-ready figures directly from Python scripts or Jupyter notebooks, without switching between multiple specialized graphical applications. Inputs depend on the function, but include NIfTI images, surface files, tractography files, arrays, dictionaries, and matrices, which are mapped to standard or user-defined anatomical resources.
25+
26+
The package builds on established scientific Python libraries. It uses `nibabel` for neuroimaging file input/output [@Brett:2024], `numpy` for numerical array operations [@Harris:2020], `pandas` for tabular and labelled data inputs [@McKinney:2010], `scipy` for interpolation, filtering, and sparse matrix operations [@Virtanen:2020], `pooch` for resource retrieval and caching [@Uieda:2020], `scikit-image` for extracting surface meshes from volumetric masks [@vanDerWalt:2014], `pyvista` for three-dimensional mesh representation, scene construction, and rendering [@Sullivan:2019], `trame` for interactive browser-based visualization [@Jourdain:2025], and `matplotlib` for static figure composition and customization [@Hunter:2007]. By combining these tools behind a compact plotting API, `yabplot` handles data mapping, mesh creation, camera placement, lighting, and rendering while retaining compatibility with standard Python plotting workflows.
27+
28+
![Overview of `yabplot` functionality. The package provides utilities for resource access, atlas construction, volume projection, and mesh handling, and high-level plotting functions for cortical parcellations, vertex-wise surface maps, subcortical structures, voxel-wise volumes, white-matter tracts, and connectome graphs.](figures/overview_joss.pdf)
29+
30+
# Statement of need
31+
32+
The neuroimaging visualization ecosystem includes many mature and widely used tools. Connectome Workbench supports surface-based visualization and Human Connectome Project resources [@Marcus:2011], MRtrix3 provides diffusion MRI processing and tractography visualization [@Tournier:2019], and BrainNet Viewer supports graph-based visualization of human connectomes [@Xia:2013]. In Python, Nilearn provides accessible statistical neuroimaging visualization, especially for slice-based, glass-brain, and machine-learning workflows [@Abraham:2014], while BrainSpace supports surface-based visualization in the context of macroscale gradients and connectomics [@VosdeWael:2020].
33+
34+
These tools cover important parts of the visualization workflow, but producing a coherent figure that combines different representations of neuroimaging data can still require several packages or applications. This introduces practical barriers. Researchers may need to translate data between different input conventions; manual screenshot-based workflows make figure generation harder to reproduce; and differences in lighting style and camera perspective can make results appear visually inconsistent within the same research study.
35+
36+
`yabplot` addresses this gap by focusing on unified, scriptable, publication-oriented 3D rendering of common neuroimaging result types within a single Python environment. Its plotting functions follow a shared pattern: users provide data values and an anatomical target, choose views and visual styling, and receive a rendered figure or interactive object. High-level functions are provided for regional cortical data (`plot_cortical`), vertex-wise cortical data (`plot_vertexwise`), voxel-wise volumes (`plot_voxelwise`), subcortical structures (`plot_subcortical`), white-matter bundles (`plot_tracts`), and connectivity matrices (`plot_connectome`). These functions share common building blocks for data mapping, mesh construction, camera presets, color handling, background anatomy for context, and output generation, while still exposing modality-specific options where needed.
37+
38+
A further design goal is to separate the Python package from large anatomical resources. `yabplot` retrieves supported atlases, meshes, and tract resources on demand and caches them locally, keeping the core installation lightweight while making resource use explicit. The package also supports custom user-defined anatomical resources through atlas-building and mesh-construction utilities, allowing users to adapt the workflow to study-specific parcellations, segmentations, and tractography datasets. By bringing these visualization tasks into a consistent interface, the package lowers the technical barrier to high-quality 3D neuroimaging figures and supports reproducible scientific communication.
39+
40+
# AI usage disclosure
41+
42+
Generative AI models have been utilized in the development process of `yabplot` and in the drafting of this manuscript. The author reviewed and verified all AI-generated content.
43+
44+
# Acknowledgements
45+
46+
The author would like to thank the early contributors, including Anthony J Barrows, Jannis Denecke, and Anthony Gagnon, for their valuable code improvements and feedback during the development of this package. `yabplot` relies on the work of the broader neuroimaging and scientific Python communities, including the developers and maintainers of `nibabel`, `numpy`, `scipy`, `pandas`, `pyvista`, `trame`, `matplotlib`, `pooch`, `scikit-image`, and the atlas resources supported by the package. Users should cite the original publications for any atlases used in their analyses.
47+
48+
# References

tests/conftest.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import pytest
2+
import numpy as np
3+
import nibabel as nib
4+
import pyvista as pv
5+
6+
pv.OFF_SCREEN = True
7+
8+
@pytest.fixture
9+
def synthetic_nifti(tmp_path):
10+
"""Generates a simple 3D NIfTI file with a sphere of high intensity."""
11+
shape = (20, 20, 20)
12+
data = np.zeros(shape)
13+
14+
# create a simple sphere
15+
cz, cy, cx = 10, 10, 10
16+
r = 5
17+
z, y, x = np.ogrid[:shape[0], :shape[1], :shape[2]]
18+
mask = (z - cz)**2 + (y - cy)**2 + (x - cx)**2 <= r**2
19+
data[mask] = 10.0
20+
21+
affine = np.eye(4)
22+
img = nib.Nifti1Image(data, affine)
23+
file_path = tmp_path / "synthetic.nii.gz"
24+
nib.save(img, file_path)
25+
return str(file_path)
26+
27+
@pytest.fixture
28+
def synthetic_nifti_4d(tmp_path):
29+
"""Generates a 4D NIfTI file."""
30+
shape = (20, 20, 20, 3)
31+
data = np.random.rand(*shape)
32+
affine = np.eye(4)
33+
img = nib.Nifti1Image(data, affine)
34+
file_path = tmp_path / "synthetic_4d.nii.gz"
35+
nib.save(img, file_path)
36+
return str(file_path)
37+
38+
@pytest.fixture
39+
def synthetic_tractogram(tmp_path):
40+
"""Generates a simple tractogram."""
41+
from nibabel.streamlines.tractogram import Tractogram
42+
from nibabel.streamlines.trk import TrkFile
43+
44+
streamlines = [
45+
np.array([[0, 0, 0], [1, 1, 1], [2, 2, 2]], dtype=np.float32),
46+
np.array([[10, 10, 10], [11, 10, 10], [12, 10, 10]], dtype=np.float32)
47+
]
48+
tractogram = Tractogram(streamlines, affine_to_rasmm=np.eye(4))
49+
file_path = tmp_path / "synthetic.trk"
50+
TrkFile(tractogram).save(file_path)
51+
return str(file_path)

tests/test_data.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import pytest
2+
from yabplot.data import get_available_resources, get_atlas_regions
3+
4+
def test_get_available_resources():
5+
"""Verify that all resource categories exist and contain data."""
6+
# all categories
7+
res_all = get_available_resources(None)
8+
assert isinstance(res_all, dict)
9+
10+
expected_categories = ['cortical', 'subcortical', 'tracts', 'bmesh']
11+
for cat in expected_categories:
12+
assert cat in res_all, f"expected category {cat} to be in resources"
13+
assert len(res_all[cat]) > 0
14+
15+
# specific category
16+
res_cortical = get_available_resources('cortical')
17+
assert isinstance(res_cortical, list)
18+
assert 'aparc' in res_cortical
19+
20+
def test_get_atlas_regions_cortical():
21+
"""Verify that cortical atlas regions are correctly retrieved."""
22+
regions = get_atlas_regions('aparc', 'cortical')
23+
assert isinstance(regions, list)
24+
assert len(regions) > 0
25+
26+
def test_get_atlas_regions_subcortical():
27+
"""Verify that subcortical atlas regions are correctly retrieved."""
28+
regions = get_atlas_regions('aseg', 'subcortical')
29+
assert isinstance(regions, list)
30+
assert len(regions) > 0
31+
32+
def test_get_atlas_regions_tracts():
33+
"""Verify that tract atlas regions are correctly retrieved."""
34+
regions = get_atlas_regions('xtract_tiny', 'tracts')
35+
assert isinstance(regions, list)
36+
assert len(regions) > 0
37+
38+
def test_get_atlas_regions_invalid():
39+
"""Verify that invalid categories return an empty list gracefully."""
40+
regions = get_atlas_regions('aparc', 'invalid_category')
41+
assert regions == []

0 commit comments

Comments
 (0)