yabplot 0.3.1__tar.gz → 0.5.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.
- {yabplot-0.3.1/yabplot.egg-info → yabplot-0.5.0}/PKG-INFO +48 -29
- yabplot-0.5.0/README.md +107 -0
- {yabplot-0.3.1 → yabplot-0.5.0}/pyproject.toml +2 -1
- {yabplot-0.3.1 → yabplot-0.5.0}/tests/test_smoke.py +32 -1
- {yabplot-0.3.1 → yabplot-0.5.0}/yabplot/__init__.py +6 -2
- {yabplot-0.3.1 → yabplot-0.5.0}/yabplot/atlas_builder.py +9 -3
- {yabplot-0.3.1 → yabplot-0.5.0}/yabplot/data/__init__.py +83 -17
- yabplot-0.5.0/yabplot/data/registry.txt +30 -0
- {yabplot-0.3.1 → yabplot-0.5.0}/yabplot/mesh.py +170 -115
- yabplot-0.5.0/yabplot/plotting.py +1458 -0
- yabplot-0.5.0/yabplot/projection.py +169 -0
- yabplot-0.5.0/yabplot/scene.py +336 -0
- {yabplot-0.3.1 → yabplot-0.5.0}/yabplot/utils.py +91 -5
- {yabplot-0.3.1 → yabplot-0.5.0/yabplot.egg-info}/PKG-INFO +48 -29
- {yabplot-0.3.1 → yabplot-0.5.0}/yabplot.egg-info/SOURCES.txt +1 -0
- {yabplot-0.3.1 → yabplot-0.5.0}/yabplot.egg-info/requires.txt +1 -0
- yabplot-0.3.1/README.md +0 -89
- yabplot-0.3.1/yabplot/data/registry.txt +0 -29
- yabplot-0.3.1/yabplot/plotting.py +0 -706
- yabplot-0.3.1/yabplot/scene.py +0 -184
- {yabplot-0.3.1 → yabplot-0.5.0}/LICENSE +0 -0
- {yabplot-0.3.1 → yabplot-0.5.0}/MANIFEST.in +0 -0
- {yabplot-0.3.1 → yabplot-0.5.0}/setup.cfg +0 -0
- {yabplot-0.3.1 → yabplot-0.5.0}/yabplot/wrappers.py +0 -0
- {yabplot-0.3.1 → yabplot-0.5.0}/yabplot.egg-info/dependency_links.txt +0 -0
- {yabplot-0.3.1 → yabplot-0.5.0}/yabplot.egg-info/top_level.txt +0 -0
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: yabplot
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
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
|
[](https://github.com/teanijarv/yabplot/actions/workflows/tests.yml)
|
|
32
33
|
[](https://doi.org/10.5281/zenodo.18237144)
|
|
33
34
|
|
|
34
|
-
**yabplot** is a Python library for creating
|
|
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
|
-
* **
|
|
41
|
-
*
|
|
42
|
-
* **
|
|
43
|
-
* **custom atlases:**
|
|
44
|
-
* **
|
|
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,36 +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
|
|
82
|
+
# plotting cortical surface regions
|
|
81
83
|
atlas = 'aparc'
|
|
82
|
-
dmap1 = {'L_lateraloccipital': 0.265, 'L_postcentral': 0.086
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
yab.plot_cortical(data=dmap2, atlas=atlas, vminmax=[-0.1, 0.3],
|
|
88
|
-
bmesh_type='swm', views=['left_lateral', 'left_medial'],
|
|
89
|
-
figsize=(1200, 600), cmap='viridis', proc_vertices='sharp')
|
|
90
|
-
|
|
91
|
-
# subcortical structures
|
|
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'])
|
|
87
|
+
|
|
88
|
+
# plotting subcortical regions
|
|
92
89
|
atlas = 'aseg'
|
|
93
90
|
regs = yab.get_atlas_regions(atlas=atlas, category='subcortical')
|
|
94
91
|
data = np.arange(1, len(regs)+1)
|
|
95
|
-
yab.plot_subcortical(data=data, atlas=atlas, vminmax=[2, 14],
|
|
96
|
-
|
|
97
|
-
|
|
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')
|
|
98
95
|
|
|
99
|
-
# white matter
|
|
100
|
-
atlas = '
|
|
96
|
+
# plotting white matter tracts
|
|
97
|
+
atlas = 'xtract_medium'
|
|
101
98
|
regs = yab.get_atlas_regions(atlas=atlas, category='tracts')
|
|
102
99
|
data = {reg: np.sin(i) for i, reg in enumerate(regs)}
|
|
103
|
-
yab.plot_tracts(data=data, atlas=atlas, style='matte',
|
|
104
|
-
|
|
105
|
-
|
|
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
|
|
111
|
+
threshold = 4
|
|
112
|
+
b_lh_path, b_rh_path = yab.data.get_surface_paths('midthickness', 'bmesh')
|
|
113
|
+
lh_data, rh_data = yab.project_vol2surf('path/to/yourdata.nii.gz', bmesh='midthickness')
|
|
114
|
+
lh_data = np.where(lh_data > threshold, lh_data, np.nan)
|
|
115
|
+
rh_data = np.where(rh_data > threshold, rh_data, np.nan)
|
|
116
|
+
lh_mesh, rh_mesh = yab.load_vertexwise_mesh(b_lh_path, b_rh_path, lh_data, rh_data)
|
|
117
|
+
ax = yab.plot_vertexwise(lh_mesh, rh_mesh, cmap='viridis', vminmax=[-10, 10],
|
|
118
|
+
views=['left_lateral', 'left_medial'])
|
|
119
|
+
|
|
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'])
|
|
106
125
|
|
|
107
126
|
```
|
|
108
127
|
|
|
109
|
-

|
|
110
129
|
|
|
111
130
|
## acknowledgements
|
|
112
131
|
|
yabplot-0.5.0/README.md
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# yabplot: yet another brain plot
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
[](https://pypi.org/project/yabplot/)
|
|
6
|
+
[](https://teanijarv.github.io/yabplot/)
|
|
7
|
+
[](https://github.com/teanijarv/yabplot/actions/workflows/tests.yml)
|
|
8
|
+
[](https://doi.org/10.5281/zenodo.18237144)
|
|
9
|
+
|
|
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
|
+
|
|
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
|
+
|
|
14
|
+
## features
|
|
15
|
+
|
|
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.
|
|
22
|
+
|
|
23
|
+
## installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
uv add yabplot # to install
|
|
27
|
+
uv sync --upgrade-package yabplot # to update
|
|
28
|
+
```
|
|
29
|
+
or
|
|
30
|
+
```bash
|
|
31
|
+
pip install yabplot # to install
|
|
32
|
+
pip install yabplot --upgrade # to update
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
dependencies: python 3.11 with ipywidgets, nibabel, matplotlib, pandas, pooch, pyvista, scikit-image, trame, trame-vtk, trame-vuetify
|
|
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)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
## quick start
|
|
41
|
+
|
|
42
|
+
please refer to the [documentation](https://teanijarv.github.io/yabplot/) for more comprehensive guides.
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
import yabplot as yab
|
|
46
|
+
import numpy as np
|
|
47
|
+
|
|
48
|
+
# check that you have the latest version
|
|
49
|
+
print(yab.__version__)
|
|
50
|
+
|
|
51
|
+
# see available atlases and brain meshes
|
|
52
|
+
print(yab.get_available_resources())
|
|
53
|
+
|
|
54
|
+
# see the region names for a specific atlas
|
|
55
|
+
print(yab.get_atlas_regions(atlas='aseg', category='subcortical'))
|
|
56
|
+
|
|
57
|
+
# plotting cortical surface regions
|
|
58
|
+
atlas = 'aparc'
|
|
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
|
+
|
|
63
|
+
# plotting subcortical regions
|
|
64
|
+
atlas = 'aseg'
|
|
65
|
+
regs = yab.get_atlas_regions(atlas=atlas, category='subcortical')
|
|
66
|
+
data = np.arange(1, len(regs)+1)
|
|
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
|
+
|
|
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
|
|
86
|
+
threshold = 4
|
|
87
|
+
b_lh_path, b_rh_path = yab.data.get_surface_paths('midthickness', 'bmesh')
|
|
88
|
+
lh_data, rh_data = yab.project_vol2surf('path/to/yourdata.nii.gz', bmesh='midthickness')
|
|
89
|
+
lh_data = np.where(lh_data > threshold, lh_data, np.nan)
|
|
90
|
+
rh_data = np.where(rh_data > threshold, rh_data, np.nan)
|
|
91
|
+
lh_mesh, rh_mesh = yab.load_vertexwise_mesh(b_lh_path, b_rh_path, lh_data, rh_data)
|
|
92
|
+
ax = yab.plot_vertexwise(lh_mesh, rh_mesh, cmap='viridis', vminmax=[-10, 10],
|
|
93
|
+
views=['left_lateral', 'left_medial'])
|
|
94
|
+
|
|
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'])
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+

|
|
104
|
+
|
|
105
|
+
## acknowledgements
|
|
106
|
+
|
|
107
|
+
yabplot relies on the extensive work of the neuroimaging community. if you use these atlases in your work, please cite the original authors. if you use this package for any scientific work, please cite the DOI (see more info on [Zenodo](https://doi.org/10.5281/zenodo.18237144)).
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "yabplot"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.5.0"
|
|
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",
|
|
@@ -10,6 +10,37 @@ def test_version():
|
|
|
10
10
|
"""Check that the package has a version string."""
|
|
11
11
|
assert yab.__version__ is not None
|
|
12
12
|
|
|
13
|
+
def test_none_returns_empty_dict():
|
|
14
|
+
"""
|
|
15
|
+
Unit test: Verify that passing None disables the background mesh
|
|
16
|
+
by correctly returning an empty dictionary.
|
|
17
|
+
"""
|
|
18
|
+
result = yab.mesh.load_bmesh(None)
|
|
19
|
+
assert result == {}
|
|
20
|
+
|
|
21
|
+
def test_dict_passthrough():
|
|
22
|
+
"""
|
|
23
|
+
Unit test: Verify that custom dictionary keys for hemispheres
|
|
24
|
+
are properly sanitized to strict 'L' and 'R' keys.
|
|
25
|
+
"""
|
|
26
|
+
mesh_l = pv.Sphere()
|
|
27
|
+
mesh_r = pv.Cube()
|
|
28
|
+
mesh_other = pv.Cone()
|
|
29
|
+
d = {'left': mesh_l, 'RIGHT': mesh_r, 'other': mesh_other}
|
|
30
|
+
result = yab.mesh.load_bmesh(d)
|
|
31
|
+
expected = {'L': mesh_l, 'R': mesh_r, 'other': mesh_other}
|
|
32
|
+
assert result == expected
|
|
33
|
+
|
|
34
|
+
def test_polydata_wrapped_in_both():
|
|
35
|
+
"""
|
|
36
|
+
Unit test: Verify that passing a single PyVista mesh (whole brain)
|
|
37
|
+
safely wraps it in a dictionary with the 'both' key.
|
|
38
|
+
"""
|
|
39
|
+
mesh = pv.Sphere()
|
|
40
|
+
result = yab.mesh.load_bmesh(mesh)
|
|
41
|
+
assert 'both' in result
|
|
42
|
+
assert result['both'] is mesh
|
|
43
|
+
|
|
13
44
|
def test_plotter_instantiation():
|
|
14
45
|
"""
|
|
15
46
|
Smoke test: Can we create a Plotter without crashing?
|
|
@@ -46,4 +77,4 @@ def test_plot_vertexwise():
|
|
|
46
77
|
rh = pv.Sphere()
|
|
47
78
|
lh['Data'] = np.random.rand(lh.n_points)
|
|
48
79
|
rh['Data'] = np.random.rand(rh.n_points)
|
|
49
|
-
yab.plot_vertexwise(lh, rh, display_type=None)
|
|
80
|
+
yab.plot_vertexwise(lh, rh, display_type=None)
|
|
@@ -2,7 +2,8 @@ from importlib.metadata import version, PackageNotFoundError
|
|
|
2
2
|
|
|
3
3
|
from .plotting import (
|
|
4
4
|
plot_cortical, plot_subcortical, plot_tracts,
|
|
5
|
-
|
|
5
|
+
clear_cache, plot_vertexwise, plot_connectome,
|
|
6
|
+
plot_voxelwise
|
|
6
7
|
)
|
|
7
8
|
from .data import (
|
|
8
9
|
get_available_resources, get_atlas_regions
|
|
@@ -11,7 +12,10 @@ from .atlas_builder import (
|
|
|
11
12
|
build_cortical_atlas, build_subcortical_atlas
|
|
12
13
|
)
|
|
13
14
|
from .mesh import (
|
|
14
|
-
load_vertexwise_mesh,
|
|
15
|
+
load_vertexwise_mesh, make_cortical_mesh, load_nii_as_mesh
|
|
16
|
+
)
|
|
17
|
+
from .projection import (
|
|
18
|
+
project_vol2surf, project_vol2tract, project_vol2tract_atlas
|
|
15
19
|
)
|
|
16
20
|
|
|
17
21
|
try:
|
|
@@ -224,10 +224,10 @@ def qc_custom_cortical_atlas(atlas_dir, atlasname='atlas'):
|
|
|
224
224
|
plot_file = os.path.join(qc_dir, f"{rid:03d}_{name}.png")
|
|
225
225
|
|
|
226
226
|
try:
|
|
227
|
-
plot_cortical(
|
|
227
|
+
ax = plot_cortical(
|
|
228
228
|
data={name: 1},
|
|
229
229
|
custom_atlas_path=atlas_dir,
|
|
230
|
-
cmap='binary',vminmax=[0, 1],
|
|
230
|
+
cmap='binary', vminmax=[0, 1],
|
|
231
231
|
export_path=plot_file
|
|
232
232
|
)
|
|
233
233
|
except Exception as e:
|
|
@@ -328,6 +328,12 @@ def build_subcortical_atlas(nii_path, labels_dict, out_dir, include_list=None, e
|
|
|
328
328
|
# save as a vtk file
|
|
329
329
|
out_file = os.path.join(out_dir, f"{name}.vtk")
|
|
330
330
|
mesh.save(out_file)
|
|
331
|
+
|
|
332
|
+
# file for ordering the labels
|
|
333
|
+
lut_path = os.path.join(out_dir, "atlas_LUT.txt")
|
|
334
|
+
with open(lut_path, 'w') as f:
|
|
335
|
+
for rid, name in targets.items():
|
|
336
|
+
f.write(f"{rid} {name}\n")
|
|
331
337
|
|
|
332
338
|
print(f"\nsubcortical atlas successfully saved to: {out_dir}")
|
|
333
339
|
|
|
@@ -391,7 +397,7 @@ def qc_custom_subcortical_atlas(atlas_dir):
|
|
|
391
397
|
plot_file = os.path.join(qc_dir, f"{region_name}.png")
|
|
392
398
|
|
|
393
399
|
try:
|
|
394
|
-
plot_subcortical(
|
|
400
|
+
ax = plot_subcortical(
|
|
395
401
|
data={region_name: 1},
|
|
396
402
|
custom_atlas_path=atlas_dir,
|
|
397
403
|
cmap='binary', vminmax=[0, 1],
|
|
@@ -6,6 +6,7 @@ import os
|
|
|
6
6
|
import glob
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
import pooch
|
|
9
|
+
import shutil
|
|
9
10
|
|
|
10
11
|
from ..utils import parse_lut
|
|
11
12
|
|
|
@@ -165,8 +166,7 @@ def get_atlas_regions(atlas, category, custom_atlas_path=None):
|
|
|
165
166
|
elif category == 'subcortical':
|
|
166
167
|
try:
|
|
167
168
|
file_map = _find_subcortical_files(atlas_dir)
|
|
168
|
-
|
|
169
|
-
return sorted(list(file_map.keys()))
|
|
169
|
+
return _get_ordered_names(atlas_dir, file_map)
|
|
170
170
|
except Exception as e:
|
|
171
171
|
print(f"Error listing subcortical regions: {e}")
|
|
172
172
|
return []
|
|
@@ -175,8 +175,7 @@ def get_atlas_regions(atlas, category, custom_atlas_path=None):
|
|
|
175
175
|
elif category == 'tracts':
|
|
176
176
|
try:
|
|
177
177
|
file_map = _find_tract_files(atlas_dir)
|
|
178
|
-
|
|
179
|
-
return sorted(list(file_map.keys()))
|
|
178
|
+
return _get_ordered_names(atlas_dir, file_map)
|
|
180
179
|
except Exception as e:
|
|
181
180
|
print(f"Error listing tracts: {e}")
|
|
182
181
|
return []
|
|
@@ -188,26 +187,44 @@ def get_atlas_regions(atlas, category, custom_atlas_path=None):
|
|
|
188
187
|
def _fetch_and_unpack(resource_key):
|
|
189
188
|
"""
|
|
190
189
|
Downloads zip, unpacks it, deletes the zip to save space,
|
|
191
|
-
and returns the extraction path.
|
|
190
|
+
and returns the extraction path. Forces a redownload if the
|
|
191
|
+
registry hash changes (indicating an update).
|
|
192
192
|
"""
|
|
193
193
|
extract_dir_name = resource_key.replace(".zip", "")
|
|
194
194
|
extract_path = os.path.join(GOODBOY.path, extract_dir_name)
|
|
195
|
+
hash_file = os.path.join(extract_path, ".registry_hash")
|
|
195
196
|
|
|
196
|
-
#
|
|
197
|
-
|
|
198
|
-
if
|
|
199
|
-
|
|
197
|
+
# get the expected hash from the registry
|
|
198
|
+
expected_hash = GOODBOY.registry.get(resource_key)
|
|
199
|
+
if not expected_hash:
|
|
200
|
+
raise ValueError(f"Resource '{resource_key}' not found in registry.")
|
|
200
201
|
|
|
201
|
-
#
|
|
202
|
+
# check if unpacked folder already exists and is up-to-date
|
|
203
|
+
is_up_to_date = False
|
|
204
|
+
if os.path.isdir(extract_path) and os.path.exists(hash_file):
|
|
205
|
+
with open(hash_file, 'r') as f:
|
|
206
|
+
local_hash = f.read().strip()
|
|
207
|
+
if local_hash == expected_hash:
|
|
208
|
+
is_up_to_date = True
|
|
209
|
+
if is_up_to_date:
|
|
210
|
+
return extract_path
|
|
211
|
+
# if folder exists but hash is wrong (outdated), wipe it clean
|
|
212
|
+
elif os.path.exists(extract_path):
|
|
213
|
+
print(f"Update found for '{extract_dir_name}'. Removing legacy data...")
|
|
214
|
+
shutil.rmtree(extract_path)
|
|
215
|
+
|
|
216
|
+
# fetch and unzip new data
|
|
202
217
|
try:
|
|
203
218
|
GOODBOY.fetch(
|
|
204
219
|
resource_key,
|
|
205
220
|
processor=pooch.Unzip(extract_dir=extract_dir_name)
|
|
206
221
|
)
|
|
207
|
-
except
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
222
|
+
except Exception as e:
|
|
223
|
+
raise RuntimeError(f"Failed to fetch '{resource_key}': {e}")
|
|
224
|
+
|
|
225
|
+
# stamp the new folder with the updated hash
|
|
226
|
+
with open(hash_file, 'w') as f:
|
|
227
|
+
f.write(expected_hash)
|
|
211
228
|
|
|
212
229
|
# cleanup: delete the source zip to save space
|
|
213
230
|
zip_path = os.path.join(GOODBOY.path, resource_key)
|
|
@@ -225,7 +242,9 @@ def _resolve_resource_path(name, category, custom_path=None):
|
|
|
225
242
|
if custom_path:
|
|
226
243
|
if os.path.isdir(custom_path):
|
|
227
244
|
return custom_path
|
|
228
|
-
|
|
245
|
+
if os.path.isfile(custom_path):
|
|
246
|
+
return custom_path
|
|
247
|
+
raise FileNotFoundError(f"Custom atlas directory/file not found: {custom_path}")
|
|
229
248
|
|
|
230
249
|
# 2. standard download logic
|
|
231
250
|
resource_key = f"{category}-{name}.zip"
|
|
@@ -326,6 +345,46 @@ def _find_cortical_files(atlas_dir, strict_name=None):
|
|
|
326
345
|
|
|
327
346
|
return csv_path, lut_path
|
|
328
347
|
|
|
348
|
+
def _get_ordered_names(atlas_dir, file_map):
|
|
349
|
+
"""
|
|
350
|
+
Attempts to read the strict region order from a LUT or order text file.
|
|
351
|
+
Falls back to alphabetical sorting if no file exists.
|
|
352
|
+
"""
|
|
353
|
+
txt_files = []
|
|
354
|
+
|
|
355
|
+
# look for a LUT or order file, ignoring qc reports
|
|
356
|
+
for root, dirs, files in os.walk(atlas_dir):
|
|
357
|
+
dirs[:] = [d for d in dirs if not d.startswith(('.', '__')) and 'qc_report' not in d]
|
|
358
|
+
for file in files:
|
|
359
|
+
if file.endswith('.txt') and 'registry' not in file:
|
|
360
|
+
txt_files.append(os.path.join(root, file))
|
|
361
|
+
def file_priority(filepath):
|
|
362
|
+
name = filepath.lower()
|
|
363
|
+
if 'lut' in name: return 0
|
|
364
|
+
if 'order' in name: return 1
|
|
365
|
+
return 2
|
|
366
|
+
txt_files.sort(key=file_priority)
|
|
367
|
+
|
|
368
|
+
if txt_files:
|
|
369
|
+
ordered_names = []
|
|
370
|
+
with open(txt_files[0], 'r') as f:
|
|
371
|
+
for line in f:
|
|
372
|
+
parts = line.strip().split()
|
|
373
|
+
# assuming standard LUT format (ID Name ...) or simple list (ID Name)
|
|
374
|
+
if len(parts) >= 2:
|
|
375
|
+
name = parts[1]
|
|
376
|
+
if name in file_map and name not in ordered_names:
|
|
377
|
+
ordered_names.append(name)
|
|
378
|
+
|
|
379
|
+
# append any stray files that exist in the directory but weren't in the text file
|
|
380
|
+
for name in sorted(file_map.keys()):
|
|
381
|
+
if name not in ordered_names:
|
|
382
|
+
ordered_names.append(name)
|
|
383
|
+
|
|
384
|
+
return ordered_names
|
|
385
|
+
|
|
386
|
+
# legacy fallback: alphabetical
|
|
387
|
+
return sorted(list(file_map.keys()))
|
|
329
388
|
|
|
330
389
|
def _find_subcortical_files(atlas_dir):
|
|
331
390
|
"""
|
|
@@ -403,8 +462,15 @@ def _find_tract_files(atlas_dir):
|
|
|
403
462
|
|
|
404
463
|
return candidates
|
|
405
464
|
|
|
406
|
-
|
|
407
|
-
|
|
465
|
+
if os.path.isdir(atlas_dir):
|
|
466
|
+
# scan for both .trk and .tck
|
|
467
|
+
found_files = _scan_for_ext(atlas_dir, ".trk") + _scan_for_ext(atlas_dir, ".tck")
|
|
468
|
+
elif os.path.isfile(atlas_dir):
|
|
469
|
+
if atlas_dir.endswith((".trk", ".tck")):
|
|
470
|
+
found_files = [atlas_dir]
|
|
471
|
+
else:
|
|
472
|
+
raise ValueError(f"Invalid atlas directory/file path: {atlas_dir}, no valid tck or trk file found.")
|
|
473
|
+
|
|
408
474
|
|
|
409
475
|
if not found_files:
|
|
410
476
|
raise FileNotFoundError(f"No .trk or .tck files found in {atlas_dir}")
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
cortical-aparc.zip sha256:9e3e4853c580b6ed7c70a2b398b8ddacfc30e05600f04f722d3b5e8ee28ba119 https://osf.io/q5a2v/download
|
|
2
|
+
cortical-aal3.zip sha256:da90797ab768cb9b0ad1acf1a9035d62debdb823d0bcc2b96405b7e86c75a7dc https://osf.io/rcjqk/download
|
|
3
|
+
cortical-brainnetome.zip sha256:42b828d5dd5734a34fb61282b1ee92c1961c4c3e3cc35fa73d3f48f6c12c0fcd https://osf.io/5nr4x/download
|
|
4
|
+
cortical-schaefer100.zip sha256:79358c491ee2d9400e8166552a7e4d3f8ad7332e65192f0edf215d657f317896 https://osf.io/782gu/download
|
|
5
|
+
cortical-schaefer200.zip sha256:e24ab0fe4acec59604ce2a98ef9f4c14cc3c3d6dea73fb31c6900bd3879ea138 https://osf.io/9uzc8/download
|
|
6
|
+
cortical-schaefer300.zip sha256:ef096c2b96b0a8e1998417c23162f0e69f0693852911e55cb03f9681cc398669 https://osf.io/jcu8a/download
|
|
7
|
+
cortical-schaefer400.zip sha256:000c1a3b71926d83ea8c73fed63c8688d34c3cd8f0b8f91bd5c7f4c80edf7554 https://osf.io/k8dvw/download
|
|
8
|
+
cortical-schaefer1000.zip sha256:369827006e724e5ac74ef6a3761e2f77b5b30f2825e09d9a02c169dc5d3fbe90 https://osf.io/m3ze7/download
|
|
9
|
+
subcortical-aal3.zip sha256:7d0965016a393e20f86bb4b20761854e5fc74731a83aff51e56eddec9f2d6f21 https://osf.io/yvheg/download
|
|
10
|
+
subcortical-aal3_nocer.zip sha256:742e88052056dae851916dfaaac87173eeda5e74ec17bd44639140a34d70be04 https://osf.io/a59sj/download
|
|
11
|
+
subcortical-aseg.zip sha256:083ba39d80bd42e92344ac5bb46cf520e60ddb31a4cce6f5bcb9dc54892d4e27 https://osf.io/8akre/download
|
|
12
|
+
subcortical-brainnetome_sc.zip sha256:e008a3310bd8b80477af3685097800aa923487519274214eb2e0488109730051 https://osf.io/f9u3m/download
|
|
13
|
+
subcortical-musus100.zip sha256:61ed78eafc9f407e625a0288c0ed2cf9273b833a5341e9712d30a26974976140 https://osf.io/ak3hb/download
|
|
14
|
+
subcortical-musus100_dbn.zip sha256:8c79864c0cf37b2bc23752931742061be8a583a9064974bd52368ca657320555 https://osf.io/9g5z7/download
|
|
15
|
+
subcortical-musus100_tha.zip sha256:14f44f5b7b7ac24eb3870600f87fa66b862d3b5a7403039b3b9c219f2bf28cdf https://osf.io/xrd9j/download
|
|
16
|
+
subcortical-tian2020_s1.zip sha256:90c5c78a3d4636aea94944d54b24a3c0510580f6ca62fdb9192a316fd8e1a9cf https://osf.io/9z7wm/download
|
|
17
|
+
tracts-hcp1065_medium.zip sha256:366bb100074cf7b1e55586e5468653f0c342e42cfdd91dca6d99b6fde82f06ff https://osf.io/kjf8e/download
|
|
18
|
+
tracts-hcp1065_small.zip sha256:020f23059c3a20ee0dda8ad12cd3df99b9fe5d3b37e7a6b9ae34b8a52b4b4346 https://osf.io/ynpa5/download
|
|
19
|
+
tracts-hcp1065_tiny.zip sha256:54380d82f5cd234029c6fc011910e00f0ad859fdb6c22c6aaa6452d055449ae9 https://osf.io/jzk7p/download
|
|
20
|
+
tracts-xtract_large.zip sha256:e2d59e9f90b024018788af87e389f93b2fa9ac213604601966a44d7f81c9d89f https://osf.io/pktq6/download
|
|
21
|
+
tracts-xtract_medium.zip sha256:f095028d3dfc0f974ebef1feffe4006b5bac1366325a784ea2301c56f583b1e7 https://osf.io/7tbjg/download
|
|
22
|
+
tracts-xtract_small.zip sha256:9d3fe2c57acb87e8d13eb40a49a6c2c354dbe1a020122b693e9bf713e57cad5b https://osf.io/bmn3a/download
|
|
23
|
+
tracts-xtract_tiny.zip sha256:469f9ed8ed5ceb7f8e17c9a0a92f1491d540814f832ef56441a7539532111f41 https://osf.io/a73x2/download
|
|
24
|
+
bmesh-inflated.zip sha256:ba72f9e75f16fe767f6bbcec5c98381f6a20b2f5e2092505b4692844a1686461 https://osf.io/kfzjg/download
|
|
25
|
+
bmesh-midthickness.zip sha256:ef8209853e1dac8804a60b5cde0b653ef2f6c77b6c7536f7acaa69a46dbb06c0 https://osf.io/xaktf/download
|
|
26
|
+
bmesh-pial.zip sha256:5e36ba7883dd2a88485fc45c9166ed48e22db607d45655242961242b51f9f126 https://osf.io/knpg8/download
|
|
27
|
+
bmesh-swm.zip sha256:1a516c070cb63557751d841d5cf75772660872b89ec749ee19c23298d77fa807 https://osf.io/hpbc9/download
|
|
28
|
+
bmesh-very_inflated.zip sha256:8c33a00c718a47f0af118ce2c5804a2adf13d79be05c4665ca26582e3e8dd977 https://osf.io/xp9jr/download
|
|
29
|
+
bmesh-white.zip sha256:b885ac1a4dcdf9e241a97e8d3ca59d7b1c86d8a3ebe17df512993bc5c1c0354a https://osf.io/wfc5t/download
|
|
30
|
+
label-nomedialwall.zip sha256:03bd589b151814a1fc5e48236a78decf1a51791f04c3f4a521d2ed4fb214317b https://osf.io/2rgmc/download
|