yabplot 0.4.0__tar.gz → 0.5.1__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.
Files changed (34) hide show
  1. yabplot-0.5.1/CITATION.cff +21 -0
  2. yabplot-0.5.1/CODE_OF_CONDUCT.md +27 -0
  3. yabplot-0.5.1/CONTRIBUTING.md +34 -0
  4. yabplot-0.5.1/MANIFEST.in +9 -0
  5. {yabplot-0.4.0/yabplot.egg-info → yabplot-0.5.1}/PKG-INFO +42 -29
  6. {yabplot-0.4.0 → yabplot-0.5.1}/README.md +40 -28
  7. {yabplot-0.4.0 → yabplot-0.5.1}/pyproject.toml +7 -1
  8. yabplot-0.5.1/tests/conftest.py +51 -0
  9. yabplot-0.5.1/tests/test_data.py +41 -0
  10. yabplot-0.5.1/tests/test_mapping.py +78 -0
  11. yabplot-0.5.1/tests/test_mesh.py +24 -0
  12. yabplot-0.5.1/tests/test_plotting.py +93 -0
  13. yabplot-0.5.1/tests/test_projection.py +25 -0
  14. {yabplot-0.4.0 → yabplot-0.5.1}/yabplot/__init__.py +3 -2
  15. {yabplot-0.4.0 → yabplot-0.5.1}/yabplot/atlas_builder.py +3 -3
  16. {yabplot-0.4.0 → yabplot-0.5.1}/yabplot/mesh.py +125 -24
  17. yabplot-0.5.1/yabplot/plotting.py +1458 -0
  18. {yabplot-0.4.0 → yabplot-0.5.1}/yabplot/projection.py +34 -28
  19. yabplot-0.5.1/yabplot/scene.py +336 -0
  20. {yabplot-0.4.0 → yabplot-0.5.1}/yabplot/utils.py +17 -15
  21. {yabplot-0.4.0 → yabplot-0.5.1/yabplot.egg-info}/PKG-INFO +42 -29
  22. {yabplot-0.4.0 → yabplot-0.5.1}/yabplot.egg-info/SOURCES.txt +9 -1
  23. {yabplot-0.4.0 → yabplot-0.5.1}/yabplot.egg-info/requires.txt +1 -0
  24. yabplot-0.4.0/MANIFEST.in +0 -4
  25. yabplot-0.4.0/tests/test_smoke.py +0 -80
  26. yabplot-0.4.0/yabplot/plotting.py +0 -726
  27. yabplot-0.4.0/yabplot/scene.py +0 -184
  28. {yabplot-0.4.0 → yabplot-0.5.1}/LICENSE +0 -0
  29. {yabplot-0.4.0 → yabplot-0.5.1}/setup.cfg +0 -0
  30. {yabplot-0.4.0 → yabplot-0.5.1}/yabplot/data/__init__.py +0 -0
  31. {yabplot-0.4.0 → yabplot-0.5.1}/yabplot/data/registry.txt +0 -0
  32. {yabplot-0.4.0 → yabplot-0.5.1}/yabplot/wrappers.py +0 -0
  33. {yabplot-0.4.0 → yabplot-0.5.1}/yabplot.egg-info/dependency_links.txt +0 -0
  34. {yabplot-0.4.0 → yabplot-0.5.1}/yabplot.egg-info/top_level.txt +0 -0
@@ -0,0 +1,21 @@
1
+ cff-version: 1.2.0
2
+ message: "If you use yabplot in your research, please cite it as below."
3
+ authors:
4
+ - family-names: "Anijärv"
5
+ given-names: "Toomas Erik"
6
+ orcid: "https://orcid.org/0000-0002-3650-4230"
7
+ title: "yabplot: yet another brain plot for unified neuroimaging visualization in Python"
8
+ doi: 10.5281/zenodo.18237144
9
+ version: 0.5.1
10
+ url: "https://github.com/teanijarv/yabplot"
11
+ preferred-citation:
12
+ type: software
13
+ authors:
14
+ - family-names: "Anijärv"
15
+ given-names: "Toomas Erik"
16
+ orcid: "https://orcid.org/0000-0002-3650-4230"
17
+ title: "yabplot: yet another brain plot for unified neuroimaging visualization in Python"
18
+ year: 2026
19
+ doi: 10.5281/zenodo.18237144
20
+ url: "https://github.com/teanijarv/yabplot"
21
+ abstract: "An open-source Python package for 3D neuroimaging visualization."
@@ -0,0 +1,27 @@
1
+ # code of conduct
2
+
3
+ ## our pledge
4
+ in the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
5
+
6
+ ## our standards
7
+ examples of behavior that contributes to creating a positive environment include:
8
+ * using welcoming and inclusive language
9
+ * being respectful of differing viewpoints and experiences
10
+ * gracefully accepting constructive criticism
11
+ * focusing on what is best for the community
12
+ * showing empathy towards other community members
13
+
14
+ examples of unacceptable behavior by participants include:
15
+ * the use of sexualized language or imagery and unwelcome sexual attention or advances
16
+ * trolling, insulting/derogatory comments, and personal or political attacks
17
+ * public or private harassment
18
+ * publishing others' private information, such as a physical or electronic address, without explicit permission
19
+ * other conduct which could reasonably be considered inappropriate in a professional setting
20
+
21
+ ## our responsibilities
22
+ as the maintainers of this project, we are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
23
+
24
+ we have the right and responsibility to remove, edit, or reject comments, commits, code, docs edits, issues, and other contributions that are not aligned to this code of conduct, or to ban temporarily or permanently any contributor for other behaviors that we deem inappropriate, threatening, offensive, or harmful.
25
+
26
+ ## enforcement
27
+ instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting us at teanijarv@pm.me. all complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. we are obligated to maintain confidentiality with regard to the reporter of an incident.
@@ -0,0 +1,34 @@
1
+ # contributing to yabplot
2
+
3
+ thank you for your interest in contributing to `yabplot`! we welcome contributions from the neuroimaging and open-source communities.
4
+
5
+ ## how to contribute
6
+
7
+ ### reporting bugs
8
+ - use the [github issue tracker](https://github.com/teanijarv/yabplot/issues) to report bugs.
9
+ - include a minimal reproducible example and information about your environment (os, python version, package versions).
10
+
11
+ ### suggesting enhancements
12
+ - open an issue describing the proposed feature and why it would be useful for the community.
13
+
14
+ ### pull requests
15
+ 1. fork the repository and create your branch from `main`.
16
+ 2. if you've added code that should be tested, add tests in the `tests/` directory.
17
+ 3. if you've changed API, update the documentation.
18
+ 4. ensure the test suite passes: `uv run pytest tests/`
19
+ 5. submit a pull request with a clear description of the changes.
20
+
21
+ ## development setup
22
+
23
+ we use `uv` for dependency management. to set up your development environment:
24
+
25
+ ```bash
26
+ git clone https://github.com/teanijarv/yabplot.git
27
+ cd yabplot
28
+ uv venv
29
+ uv pip install -e ".[docs]"
30
+ uv pip install pytest
31
+ ```
32
+
33
+ ## community
34
+ by contributing, you agree to abide by our [code of conduct](CODE_OF_CONDUCT.md).
@@ -0,0 +1,9 @@
1
+ include pyproject.toml
2
+ include README.md
3
+ include LICENSE
4
+ include CITATION.cff
5
+ include CONTRIBUTING.md
6
+ include CODE_OF_CONDUCT.md
7
+
8
+ recursive-include yabplot *.txt *.json *.md
9
+ recursive-include tests *.py
@@ -1,12 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: yabplot
3
- Version: 0.4.0
3
+ Version: 0.5.1
4
4
  Summary: yet another brain plot
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
7
7
  License-File: LICENSE
8
8
  Requires-Dist: cmocean>=4.0.3
9
9
  Requires-Dist: ipywidgets>=8.1.8
10
+ Requires-Dist: matplotlib>=3.10.7
10
11
  Requires-Dist: nibabel>=5.3.2
11
12
  Requires-Dist: pandas>=2.3.3
12
13
  Requires-Dist: pooch>=1.8.2
@@ -31,17 +32,18 @@ Dynamic: license-file
31
32
  [![Tests](https://github.com/teanijarv/yabplot/actions/workflows/tests.yml/badge.svg)](https://github.com/teanijarv/yabplot/actions/workflows/tests.yml)
32
33
  [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.18237144.svg)](https://doi.org/10.5281/zenodo.18237144)
33
34
 
34
- **yabplot** is a Python library for creating beautiful, publication-quality 3D brain visualizations. it supports plotting cortical regions (+ vertexwise), subcortical structures, and white matter bundles.
35
+ **yabplot** is a Python library for creating publication-quality 3D brain visualizations. it provides a unified interface for cortical regions, subcortical structures, white matter bundles, connectomes, voxel-wise maps, and vertex-wise maps.
35
36
 
36
- the idea is simple. while there are already amazing visualization tools available, they often focus on specific domains—using one tool for white matter tracts and another for cortical surfaces inevitably leads to inconsistent styles. i wanted a unified, simple-to-use tool that enables me (and hopefully others) to perform most brain visualizations in a single place. recognizing that neuroscience evolves daily, i designed **yabplot** to be modular: it supports standard pre-packaged atlases out of the box, but easily accepts any custom parcellation or tractography dataset you might need.
37
+ the idea is simple. while there are already amazing visualization tools available, they often focus on specific domains—using one tool for white matter tracts and another for cortical surfaces inevitably leads to inconsistent styles. i wanted a unified, simple-to-use tool that enables me (and hopefully others) to perform most brain visualizations in a single place. recognizing that neuroscience evolves daily, i designed **yabplot** to be modular: it supports standard pre-packaged atlases out of the box, but easily accepts any custom parcellation or tractography dataset you might need. moreover, it enables to plot volumetric data either voxel-wise or by projecting the data to cortical surface or white matter tracts.
37
38
 
38
39
  ## features
39
40
 
40
- * **pre-existing atlases:** access many commonly used atlases (schaefer2018, brainnetome, aparc, aseg, musus100, xtract, etc) on demand.
41
- * **vertexwise plotting:** project volume (.nii) to cortical surface and plot.
42
- * **simple to use:** plug-n-play functions for cortex, subcortex, and tracts with a unified API.
43
- * **custom atlases:** easily use your own parcellations, segmentations (.nii/.gii), or tractograms (.trk).
44
- * **flexible inputs:** accepts data as dictionaries (for partial mapping) or arrays (for strict mapping).
41
+ * **unified plotting API:** plot cortical regions, vertex-wise maps, subcortical structures, voxel-wise maps, white-matter tracts, and connectomes with a consistent interface.
42
+ * **pre-packaged resources:** access commonly used atlases and meshes on demand, including schaefer, brainnetome, aparc, aseg, musus100, and xtract atlases.
43
+ * **flexible data mapping:** pass data as arrays for strict ordering or dictionaries for partial/name-based mapping.
44
+ * **custom atlases:** build and use custom cortical parcellations, subcortical segmentations, and tractography datasets.
45
+ * **volume projection:** project nifti images to cortical surfaces or tractograms for vertex-wise and tractometry visualizations, or plot the volumes voxel-wise.
46
+ * **publication-oriented output:** generate static figures, saved images, and interactive 3D views.
45
47
 
46
48
  ## installation
47
49
 
@@ -55,7 +57,7 @@ pip install yabplot # to install
55
57
  pip install yabplot --upgrade # to update
56
58
  ```
57
59
 
58
- dependencies: python 3.11 with ipywidgets, nibabel, pandas, pooch, pyvista, scikit-image, trame, trame-vtk, trame-vuetify
60
+ dependencies: python 3.11 with ipywidgets, nibabel, matplotlib, pandas, pooch, pyvista, scikit-image, trame, trame-vtk, trame-vuetify
59
61
 
60
62
  (Connectome Workbench (`wb_command`) is a requirement to create custom cortical atlases unless you plan to only use pre-loaded atlases; see more in docs)
61
63
 
@@ -77,42 +79,53 @@ print(yab.get_available_resources())
77
79
  # see the region names for a specific atlas
78
80
  print(yab.get_atlas_regions(atlas='aseg', category='subcortical'))
79
81
 
80
- # cortical surface regions
82
+ # plotting cortical surface regions
81
83
  atlas = 'aparc'
82
- dmap1 = {'L_lateraloccipital': 0.265, 'L_postcentral': 0.086, ...}
83
- yab.plot_cortical(data=dmap1, atlas=atlas, vminmax=[-0.1, 0.3],
84
- bmesh='midthickness', views=['left_lateral', 'left_medial'],
85
- figsize=(600, 300), cmap='viridis')
84
+ dmap1 = {'L_lateraloccipital': 0.265, 'L_postcentral': 0.086}
85
+ ax = yab.plot_cortical(data=dmap1, atlas=atlas, vminmax=[0.0, 0.3], cmap='viridis',
86
+ bmesh='midthickness', views=['left_lateral', 'left_medial'])
86
87
 
87
- # subcortical structures
88
+ # plotting subcortical regions
88
89
  atlas = 'aseg'
89
90
  regs = yab.get_atlas_regions(atlas=atlas, category='subcortical')
90
91
  data = np.arange(1, len(regs)+1)
91
- yab.plot_subcortical(data=data, atlas=atlas, vminmax=[2, 14],
92
- views=['left_lateral', 'superior', 'right_lateral'],
93
- bmesh_alpha=0.1, figsize=(600, 300), cmap='viridis')
92
+ ax = yab.plot_subcortical(data=data, atlas=atlas, vminmax=[2, 14],
93
+ views=['left_lateral', 'superior', 'right_lateral'],
94
+ bmesh_alpha=0.1, cmap='plasma')
94
95
 
95
- # vertex-wise surface
96
+ # plotting white matter tracts
97
+ atlas = 'xtract_medium'
98
+ regs = yab.get_atlas_regions(atlas=atlas, category='tracts')
99
+ data = {reg: np.sin(i) for i, reg in enumerate(regs)}
100
+ ax = yab.plot_tracts(data=data, atlas=atlas, style='matte', cmap='coolwarm',
101
+ views=['left_lateral', 'anterior', 'superior'], bmesh='pial')
102
+
103
+ # plotting connectome
104
+ data = np.random.rand(400, 400)
105
+ ax = yab.plot_connectome(matrix=data, atlas='schaefer400',
106
+ edge_cmap='dense', node_cmap='binary', edge_threshold='95%',
107
+ views=['left_lateral', 'superior', 'posterior']
108
+ )
109
+
110
+ # volume projection to cortical surface and vertexwise plotting
96
111
  threshold = 4
97
112
  b_lh_path, b_rh_path = yab.data.get_surface_paths('midthickness', 'bmesh')
98
113
  lh_data, rh_data = yab.project_vol2surf('path/to/yourdata.nii.gz', bmesh='midthickness')
99
114
  lh_data = np.where(lh_data > threshold, lh_data, np.nan)
100
115
  rh_data = np.where(rh_data > threshold, rh_data, np.nan)
101
116
  lh_mesh, rh_mesh = yab.load_vertexwise_mesh(b_lh_path, b_rh_path, lh_data, rh_data)
102
- yab.plot_vertexwise(lh_mesh, rh_mesh, cmap='viridis', vminmax=[-4, 9],
103
- views=['left_lateral', 'left_medial'], figsize=(600, 300))
117
+ ax = yab.plot_vertexwise(lh_mesh, rh_mesh, cmap='viridis', vminmax=[-10, 10],
118
+ views=['left_lateral', 'left_medial'])
104
119
 
105
- # white matter bundles
106
- atlas = 'xtract_tiny'
107
- regs = yab.get_atlas_regions(atlas=atlas, category='tracts')
108
- data = {reg: np.sin(i) for i, reg in enumerate(regs)}
109
- yab.plot_tracts(data=data, atlas=atlas, style='matte',
110
- views=['left_lateral', 'anterior', 'superior'], bmesh='pial',
111
- bmesh_alpha=0.1, figsize=(1600, 800), cmap='viridis')
120
+ # plotting volume voxel-wise
121
+ threshold = '99.5%'
122
+ nii_path = 'path/to/yourdata.nii.gz'
123
+ ax = yab.plot_voxelwise(nii_path, threshold='99%', cmap='Reds',
124
+ views=['left_lateral', 'superior', 'anterior'])
112
125
 
113
126
  ```
114
127
 
115
- ![examples](docs/assets/examples.png)
128
+ ![examples](docs/assets/overview.png)
116
129
 
117
130
  ## acknowledgements
118
131
 
@@ -7,17 +7,18 @@
7
7
  [![Tests](https://github.com/teanijarv/yabplot/actions/workflows/tests.yml/badge.svg)](https://github.com/teanijarv/yabplot/actions/workflows/tests.yml)
8
8
  [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.18237144.svg)](https://doi.org/10.5281/zenodo.18237144)
9
9
 
10
- **yabplot** is a Python library for creating beautiful, publication-quality 3D brain visualizations. it supports plotting cortical regions (+ vertexwise), subcortical structures, and white matter bundles.
10
+ **yabplot** is a Python library for creating publication-quality 3D brain visualizations. it provides a unified interface for cortical regions, subcortical structures, white matter bundles, connectomes, voxel-wise maps, and vertex-wise maps.
11
11
 
12
- the idea is simple. while there are already amazing visualization tools available, they often focus on specific domains—using one tool for white matter tracts and another for cortical surfaces inevitably leads to inconsistent styles. i wanted a unified, simple-to-use tool that enables me (and hopefully others) to perform most brain visualizations in a single place. recognizing that neuroscience evolves daily, i designed **yabplot** to be modular: it supports standard pre-packaged atlases out of the box, but easily accepts any custom parcellation or tractography dataset you might need.
12
+ the idea is simple. while there are already amazing visualization tools available, they often focus on specific domains—using one tool for white matter tracts and another for cortical surfaces inevitably leads to inconsistent styles. i wanted a unified, simple-to-use tool that enables me (and hopefully others) to perform most brain visualizations in a single place. recognizing that neuroscience evolves daily, i designed **yabplot** to be modular: it supports standard pre-packaged atlases out of the box, but easily accepts any custom parcellation or tractography dataset you might need. moreover, it enables to plot volumetric data either voxel-wise or by projecting the data to cortical surface or white matter tracts.
13
13
 
14
14
  ## features
15
15
 
16
- * **pre-existing atlases:** access many commonly used atlases (schaefer2018, brainnetome, aparc, aseg, musus100, xtract, etc) on demand.
17
- * **vertexwise plotting:** project volume (.nii) to cortical surface and plot.
18
- * **simple to use:** plug-n-play functions for cortex, subcortex, and tracts with a unified API.
19
- * **custom atlases:** easily use your own parcellations, segmentations (.nii/.gii), or tractograms (.trk).
20
- * **flexible inputs:** accepts data as dictionaries (for partial mapping) or arrays (for strict mapping).
16
+ * **unified plotting API:** plot cortical regions, vertex-wise maps, subcortical structures, voxel-wise maps, white-matter tracts, and connectomes with a consistent interface.
17
+ * **pre-packaged resources:** access commonly used atlases and meshes on demand, including schaefer, brainnetome, aparc, aseg, musus100, and xtract atlases.
18
+ * **flexible data mapping:** pass data as arrays for strict ordering or dictionaries for partial/name-based mapping.
19
+ * **custom atlases:** build and use custom cortical parcellations, subcortical segmentations, and tractography datasets.
20
+ * **volume projection:** project nifti images to cortical surfaces or tractograms for vertex-wise and tractometry visualizations, or plot the volumes voxel-wise.
21
+ * **publication-oriented output:** generate static figures, saved images, and interactive 3D views.
21
22
 
22
23
  ## installation
23
24
 
@@ -31,7 +32,7 @@ pip install yabplot # to install
31
32
  pip install yabplot --upgrade # to update
32
33
  ```
33
34
 
34
- dependencies: python 3.11 with ipywidgets, nibabel, pandas, pooch, pyvista, scikit-image, trame, trame-vtk, trame-vuetify
35
+ dependencies: python 3.11 with ipywidgets, nibabel, matplotlib, pandas, pooch, pyvista, scikit-image, trame, trame-vtk, trame-vuetify
35
36
 
36
37
  (Connectome Workbench (`wb_command`) is a requirement to create custom cortical atlases unless you plan to only use pre-loaded atlases; see more in docs)
37
38
 
@@ -53,42 +54,53 @@ print(yab.get_available_resources())
53
54
  # see the region names for a specific atlas
54
55
  print(yab.get_atlas_regions(atlas='aseg', category='subcortical'))
55
56
 
56
- # cortical surface regions
57
+ # plotting cortical surface regions
57
58
  atlas = 'aparc'
58
- dmap1 = {'L_lateraloccipital': 0.265, 'L_postcentral': 0.086, ...}
59
- yab.plot_cortical(data=dmap1, atlas=atlas, vminmax=[-0.1, 0.3],
60
- bmesh='midthickness', views=['left_lateral', 'left_medial'],
61
- figsize=(600, 300), cmap='viridis')
59
+ dmap1 = {'L_lateraloccipital': 0.265, 'L_postcentral': 0.086}
60
+ ax = yab.plot_cortical(data=dmap1, atlas=atlas, vminmax=[0.0, 0.3], cmap='viridis',
61
+ bmesh='midthickness', views=['left_lateral', 'left_medial'])
62
62
 
63
- # subcortical structures
63
+ # plotting subcortical regions
64
64
  atlas = 'aseg'
65
65
  regs = yab.get_atlas_regions(atlas=atlas, category='subcortical')
66
66
  data = np.arange(1, len(regs)+1)
67
- yab.plot_subcortical(data=data, atlas=atlas, vminmax=[2, 14],
68
- views=['left_lateral', 'superior', 'right_lateral'],
69
- bmesh_alpha=0.1, figsize=(600, 300), cmap='viridis')
67
+ ax = yab.plot_subcortical(data=data, atlas=atlas, vminmax=[2, 14],
68
+ views=['left_lateral', 'superior', 'right_lateral'],
69
+ bmesh_alpha=0.1, cmap='plasma')
70
70
 
71
- # vertex-wise surface
71
+ # plotting white matter tracts
72
+ atlas = 'xtract_medium'
73
+ regs = yab.get_atlas_regions(atlas=atlas, category='tracts')
74
+ data = {reg: np.sin(i) for i, reg in enumerate(regs)}
75
+ ax = yab.plot_tracts(data=data, atlas=atlas, style='matte', cmap='coolwarm',
76
+ views=['left_lateral', 'anterior', 'superior'], bmesh='pial')
77
+
78
+ # plotting connectome
79
+ data = np.random.rand(400, 400)
80
+ ax = yab.plot_connectome(matrix=data, atlas='schaefer400',
81
+ edge_cmap='dense', node_cmap='binary', edge_threshold='95%',
82
+ views=['left_lateral', 'superior', 'posterior']
83
+ )
84
+
85
+ # volume projection to cortical surface and vertexwise plotting
72
86
  threshold = 4
73
87
  b_lh_path, b_rh_path = yab.data.get_surface_paths('midthickness', 'bmesh')
74
88
  lh_data, rh_data = yab.project_vol2surf('path/to/yourdata.nii.gz', bmesh='midthickness')
75
89
  lh_data = np.where(lh_data > threshold, lh_data, np.nan)
76
90
  rh_data = np.where(rh_data > threshold, rh_data, np.nan)
77
91
  lh_mesh, rh_mesh = yab.load_vertexwise_mesh(b_lh_path, b_rh_path, lh_data, rh_data)
78
- yab.plot_vertexwise(lh_mesh, rh_mesh, cmap='viridis', vminmax=[-4, 9],
79
- views=['left_lateral', 'left_medial'], figsize=(600, 300))
92
+ ax = yab.plot_vertexwise(lh_mesh, rh_mesh, cmap='viridis', vminmax=[-10, 10],
93
+ views=['left_lateral', 'left_medial'])
80
94
 
81
- # white matter bundles
82
- atlas = 'xtract_tiny'
83
- regs = yab.get_atlas_regions(atlas=atlas, category='tracts')
84
- data = {reg: np.sin(i) for i, reg in enumerate(regs)}
85
- yab.plot_tracts(data=data, atlas=atlas, style='matte',
86
- views=['left_lateral', 'anterior', 'superior'], bmesh='pial',
87
- bmesh_alpha=0.1, figsize=(1600, 800), cmap='viridis')
95
+ # plotting volume voxel-wise
96
+ threshold = '99.5%'
97
+ nii_path = 'path/to/yourdata.nii.gz'
98
+ ax = yab.plot_voxelwise(nii_path, threshold='99%', cmap='Reds',
99
+ views=['left_lateral', 'superior', 'anterior'])
88
100
 
89
101
  ```
90
102
 
91
- ![examples](docs/assets/examples.png)
103
+ ![examples](docs/assets/overview.png)
92
104
 
93
105
  ## acknowledgements
94
106
 
@@ -1,12 +1,13 @@
1
1
  [project]
2
2
  name = "yabplot"
3
- version = "0.4.0"
3
+ version = "0.5.1"
4
4
  description = "yet another brain plot"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
7
7
  dependencies = [
8
8
  "cmocean>=4.0.3",
9
9
  "ipywidgets>=8.1.8",
10
+ "matplotlib>=3.10.7",
10
11
  "nibabel>=5.3.2",
11
12
  "pandas>=2.3.3",
12
13
  "pooch>=1.8.2",
@@ -31,3 +32,8 @@ include = ["yabplot*"]
31
32
 
32
33
  [tool.setuptools.package-data]
33
34
  "yabplot" = ["*.txt", "*.json"]
35
+
36
+ [dependency-groups]
37
+ dev = [
38
+ "pytest>=9.0.3",
39
+ ]
@@ -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)
@@ -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,0 +1,78 @@
1
+ import pytest
2
+ import numpy as np
3
+ import pandas as pd
4
+ from yabplot.mesh import map_values_to_surface
5
+ from yabplot.utils import prep_data
6
+
7
+ def test_prep_data_array():
8
+ """Verify that 1D arrays are correctly mapped to a dictionary by position."""
9
+ regions = ['A', 'B', 'C']
10
+ data = [1, 2, 3]
11
+ result = prep_data(data, regions, 'test_atlas', 'cortical')
12
+ assert isinstance(result, dict)
13
+ assert result['A'] == 1
14
+ assert result['C'] == 3
15
+
16
+ def test_prep_data_dict():
17
+ """Verify that dictionaries are passed through correctly."""
18
+ regions = ['A', 'B', 'C']
19
+ data = {'A': 10, 'B': 20}
20
+ result = prep_data(data, regions, 'test_atlas', 'cortical')
21
+ assert isinstance(result, dict)
22
+ assert result['A'] == 10
23
+ assert result['B'] == 20
24
+
25
+ def test_prep_data_series():
26
+ """Verify that pandas Series are correctly converted using their index."""
27
+ regions = ['A', 'B', 'C']
28
+ data = pd.Series([100, 200], index=['A', 'C'])
29
+ result = prep_data(data, regions, 'test_atlas', 'cortical')
30
+ assert result['A'] == 100
31
+ assert result['C'] == 200
32
+
33
+ def test_prep_data_dataframe():
34
+ """Verify that pandas DataFrames are correctly handled by index or column mapping."""
35
+ regions = ['A', 'B', 'C']
36
+ # 1-col dataframe will use index as labels
37
+ data_1col = pd.DataFrame({'val': [5, 6]}, index=['B', 'A'])
38
+ result_1col = prep_data(data_1col, regions, 'test_atlas', 'cortical')
39
+ assert result_1col['A'] == 6
40
+ assert result_1col['B'] == 5
41
+
42
+ # 2-col dataframe will use first column as labels and second as values
43
+ df2 = pd.DataFrame({'Region': ['B', 'A'], 'Value': [50, 60]})
44
+ result_2col = prep_data(df2, regions, 'test_atlas', 'cortical')
45
+ assert result_2col['A'] == 60
46
+ assert result_2col['B'] == 50
47
+
48
+ def test_prep_data_length_mismatch():
49
+ """Verify that an array length mismatch raises a ValueError."""
50
+ regions = ['A', 'B', 'C']
51
+ data = [1, 2] # length mismatch
52
+ with pytest.raises(ValueError, match="Data length mismatch"):
53
+ prep_data(data, regions, 'test_atlas', 'cortical')
54
+
55
+ def test_map_values_to_surface():
56
+ """Verify that atlas scalar mapping correctly handles data injection and NaNs."""
57
+ target_labels = np.array([0, 1, 2, 1, 0, 3])
58
+ lut_ids = np.array([0, 1, 2, 3])
59
+ dense_lut_names = ['Region0', 'Region1', 'Region2', 'Region3']
60
+
61
+ # dict input
62
+ data_dict = {'Region1': 10.0, 'Region2': 20.0}
63
+ res_dict = map_values_to_surface(data_dict, target_labels, lut_ids, dense_lut_names)
64
+ assert np.isnan(res_dict[0]) # region 0 mapped to NaN
65
+ assert res_dict[1] == 10.0 # region 1 mapped to 10
66
+ assert res_dict[2] == 20.0 # region 2 mapped to 20
67
+ assert np.isnan(res_dict[5]) # target 3 -> region 3 mapped to NaN
68
+
69
+ # array input
70
+ data_arr = [0.0, 1.0, 2.0, 3.0]
71
+ res_arr = map_values_to_surface(data_arr, target_labels, lut_ids, dense_lut_names)
72
+ assert res_arr[0] == 0.0
73
+ assert res_arr[1] == 1.0
74
+ assert res_arr[5] == 3.0
75
+
76
+ # mismatch length array
77
+ with pytest.raises(ValueError):
78
+ map_values_to_surface([1.0, 2.0], target_labels, lut_ids, dense_lut_names)
@@ -0,0 +1,24 @@
1
+ import pytest
2
+ import numpy as np
3
+ import pyvista as pv
4
+ from yabplot.mesh import load_nii_as_mesh, make_cortical_mesh
5
+
6
+ def test_load_nii_as_mesh(synthetic_nifti):
7
+ """Verify that marching cubes mesh extraction works on a NIfTI volume."""
8
+ mesh = load_nii_as_mesh(synthetic_nifti, threshold=5.0, blur_sigma=0, smooth_i=0)
9
+ assert isinstance(mesh, pv.PolyData)
10
+ assert mesh.n_points > 0
11
+ assert mesh.n_cells > 0
12
+
13
+ def test_make_cortical_mesh():
14
+ """Verify that a manual mesh can be constructed from vertex and face arrays."""
15
+ verts = np.array([[0,0,0], [1,0,0], [0,1,0], [0,0,1]], dtype=np.float32)
16
+ faces = np.array([[0,1,2], [0,1,3], [0,2,3], [1,2,3]], dtype=np.int64)
17
+ scalars = np.array([1, 2, 3, 4], dtype=np.float32)
18
+ mesh = make_cortical_mesh(verts, faces, scalars, scalar_name='TestData')
19
+
20
+ assert isinstance(mesh, pv.PolyData)
21
+ assert mesh.n_points == 4
22
+ assert mesh.n_cells == 4
23
+ assert 'TestData' in mesh.point_data
24
+ assert np.all(mesh.point_data['TestData'] == scalars)
@@ -0,0 +1,93 @@
1
+ import pytest
2
+ import os
3
+ import numpy as np
4
+ import matplotlib.pyplot as plt
5
+ import yabplot as yab
6
+ import pyvista as pv
7
+
8
+ # tell PyVista to run in "off-screen" mode so it doesn't try to open a real window
9
+ pv.OFF_SCREEN = True
10
+
11
+ def test_version():
12
+ """Check that the package has a version string."""
13
+ assert yab.__version__ is not None
14
+
15
+ def test_none_returns_empty_dict():
16
+ """Verify that passing None disables the background mesh."""
17
+ result = yab.mesh.load_bmesh(None)
18
+ assert result == {}
19
+
20
+ def test_dict_passthrough():
21
+ """Verify that custom dictionary keys for hemispheres are properly sanitized."""
22
+ mesh_l = pv.Sphere()
23
+ mesh_r = pv.Cube()
24
+ mesh_other = pv.Cone()
25
+ d = {'left': mesh_l, 'RIGHT': mesh_r, 'other': mesh_other}
26
+ result = yab.mesh.load_bmesh(d)
27
+ expected = {'L': mesh_l, 'R': mesh_r, 'other': mesh_other}
28
+ assert result == expected
29
+
30
+ def test_polydata_wrapped_in_both():
31
+ """Verify that passing a single PyVista mesh safely wraps it."""
32
+ mesh = pv.Sphere()
33
+ result = yab.mesh.load_bmesh(mesh)
34
+ assert 'both' in result
35
+ assert result['both'] is mesh
36
+
37
+ def test_plotter_instantiation():
38
+ """Smoke test: can we create a Plotter without crashing?"""
39
+ plotter = pv.Plotter(off_screen=True)
40
+ plotter.add_mesh(pv.Sphere())
41
+ plotter.show()
42
+ plotter.close()
43
+
44
+ def test_plot_cortical():
45
+ """Smoke test: verify that plot_cortical renders with standard atlas."""
46
+ yab.plot_cortical(atlas='aparc', display_type='matplotlib')
47
+
48
+ def test_plot_subcortical():
49
+ """Smoke test: verify that plot_subcortical renders with standard atlas."""
50
+ yab.plot_subcortical(atlas='aseg', display_type='matplotlib')
51
+
52
+ def test_plot_tracts():
53
+ """Smoke test: verify that plot_tracts renders with standard atlas."""
54
+ yab.plot_tracts(atlas='xtract_tiny', display_type='matplotlib')
55
+
56
+ def test_plot_vertexwise():
57
+ """Smoke test: verify that plot_vertexwise renders with custom meshes."""
58
+ lh = pv.Sphere()
59
+ rh = pv.Sphere()
60
+ lh['Data'] = np.random.rand(lh.n_points)
61
+ rh['Data'] = np.random.rand(rh.n_points)
62
+ yab.plot_vertexwise(lh, rh, display_type='matplotlib')
63
+
64
+ def test_plot_voxelwise(synthetic_nifti):
65
+ """Smoke test: verify that plot_voxelwise renders with synthetic volume."""
66
+ yab.plot_voxelwise(synthetic_nifti, threshold=5.0, blur_sigma=0, display_type='matplotlib')
67
+
68
+ def test_plot_connectome():
69
+ """Smoke test: verify that plot_connectome renders both nodes-only and matrix modes."""
70
+ yab.plot_connectome(atlas='aparc', display_type='matplotlib')
71
+ # plotting with a dummy matrix
72
+ regions = yab.get_atlas_regions('aparc', 'cortical')
73
+ n = len(regions)
74
+ matrix = np.random.rand(n, n)
75
+ yab.plot_connectome(matrix=matrix, atlas='aparc', edge_threshold=0.5, display_type='matplotlib')
76
+
77
+
78
+ def test_matplotlib_ax_compatibility():
79
+ """Verify that plotting on a provided Matplotlib axis works correctly."""
80
+ fig, ax = plt.subplots()
81
+ ret_ax = yab.plot_cortical(atlas='aparc', ax=ax, display_type='matplotlib')
82
+ assert ret_ax is ax
83
+ # verify ax is usable
84
+ ret_ax.set_title("Test Title")
85
+ assert ret_ax.get_title() == "Test Title"
86
+ plt.close(fig)
87
+
88
+ def test_export_path(tmp_path):
89
+ """Verify that export_path correctly saves a file to disk."""
90
+ out_file = tmp_path / "test_export.png"
91
+ yab.plot_cortical(atlas='aparc', display_type='matplotlib', export_path=str(out_file))
92
+ assert out_file.exists()
93
+ assert out_file.stat().st_size > 0