yabplot 0.1.4__tar.gz → 0.1.5__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.1.4/yabplot.egg-info → yabplot-0.1.5}/PKG-INFO +36 -27
- {yabplot-0.1.4 → yabplot-0.1.5}/README.md +35 -26
- {yabplot-0.1.4 → yabplot-0.1.5}/pyproject.toml +1 -1
- {yabplot-0.1.4 → yabplot-0.1.5}/tests/test_smoke.py +3 -3
- {yabplot-0.1.4 → yabplot-0.1.5}/yabplot/data/registry.txt +6 -2
- {yabplot-0.1.4 → yabplot-0.1.5}/yabplot/plotting.py +79 -56
- {yabplot-0.1.4 → yabplot-0.1.5}/yabplot/scene.py +17 -8
- {yabplot-0.1.4 → yabplot-0.1.5}/yabplot/utils.py +89 -1
- {yabplot-0.1.4 → yabplot-0.1.5/yabplot.egg-info}/PKG-INFO +36 -27
- {yabplot-0.1.4 → yabplot-0.1.5}/LICENSE +0 -0
- {yabplot-0.1.4 → yabplot-0.1.5}/MANIFEST.in +0 -0
- {yabplot-0.1.4 → yabplot-0.1.5}/setup.cfg +0 -0
- {yabplot-0.1.4 → yabplot-0.1.5}/yabplot/__init__.py +0 -0
- {yabplot-0.1.4 → yabplot-0.1.5}/yabplot/data/__init__.py +0 -0
- {yabplot-0.1.4 → yabplot-0.1.5}/yabplot.egg-info/SOURCES.txt +0 -0
- {yabplot-0.1.4 → yabplot-0.1.5}/yabplot.egg-info/dependency_links.txt +0 -0
- {yabplot-0.1.4 → yabplot-0.1.5}/yabplot.egg-info/requires.txt +0 -0
- {yabplot-0.1.4 → yabplot-0.1.5}/yabplot.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: yabplot
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.5
|
|
4
4
|
Summary: yet another brain plot
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -63,32 +63,41 @@ please refer to the [documentation](https://teanijarv.github.io/yabplot/) for mo
|
|
|
63
63
|
import yabplot as yab
|
|
64
64
|
import numpy as np
|
|
65
65
|
|
|
66
|
-
#
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
# see
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
#
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
yab.
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
#
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
66
|
+
# check that you have the latest version
|
|
67
|
+
print(yab.__version__)
|
|
68
|
+
|
|
69
|
+
# see available atlases and brain meshes
|
|
70
|
+
print(yab.get_available_resources())
|
|
71
|
+
|
|
72
|
+
# see the region names for a specific atlas
|
|
73
|
+
print(yab.get_atlas_regions(atlas='aseg', category='subcortical'))
|
|
74
|
+
|
|
75
|
+
# cortical surfaces
|
|
76
|
+
atlas = 'aparc'
|
|
77
|
+
dmap1 = {'L_lateraloccipital': 0.265, 'L_postcentral': 0.086, ...}
|
|
78
|
+
dmap2 = {'L_fusiform': 0.218, 'L_supramarginal': 0.119, ...}
|
|
79
|
+
yab.plot_cortical(data=dmap1, atlas=atlas, vminmax=[-0.1, 0.3],
|
|
80
|
+
bmesh_type='midthickness', views=['left_lateral', 'left_medial'],
|
|
81
|
+
figsize=(600, 300), cmap='viridis', proc_vertices='sharp')
|
|
82
|
+
yab.plot_cortical(data=dmap2, atlas=atlas, vminmax=[-0.1, 0.3],
|
|
83
|
+
bmesh_type='swm', views=['left_lateral', 'left_medial'],
|
|
84
|
+
figsize=(1200, 600), cmap='viridis', proc_vertices='sharp')
|
|
85
|
+
|
|
86
|
+
# subcortical structures
|
|
87
|
+
atlas = 'aseg'
|
|
88
|
+
regs = yab.get_atlas_regions(atlas=atlas, category='subcortical')
|
|
89
|
+
data = np.arange(1, len(regs)+1)
|
|
90
|
+
yab.plot_subcortical(data=data, atlas=atlas, vminmax=[2, 14],
|
|
91
|
+
views=['left_lateral', 'superior', 'right_lateral'],
|
|
92
|
+
bmesh_alpha=0.1, figsize=(600, 300), cmap='viridis')
|
|
93
|
+
|
|
94
|
+
# white matter bundles
|
|
95
|
+
atlas = 'xtract_tiny'
|
|
96
|
+
regs = yab.get_atlas_regions(atlas=atlas, category='tracts')
|
|
97
|
+
data = {reg: np.sin(i) for i, reg in enumerate(regs)}
|
|
98
|
+
yab.plot_tracts(data=data, atlas=atlas, style='matte',
|
|
99
|
+
views=['left_lateral', 'anterior', 'superior'], bmesh_type='pial',
|
|
100
|
+
bmesh_alpha=0.1, figsize=(1600, 800), cmap='viridis')
|
|
92
101
|
|
|
93
102
|
```
|
|
94
103
|
|
|
@@ -40,32 +40,41 @@ please refer to the [documentation](https://teanijarv.github.io/yabplot/) for mo
|
|
|
40
40
|
import yabplot as yab
|
|
41
41
|
import numpy as np
|
|
42
42
|
|
|
43
|
-
#
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
# see
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
#
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
yab.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
#
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
43
|
+
# check that you have the latest version
|
|
44
|
+
print(yab.__version__)
|
|
45
|
+
|
|
46
|
+
# see available atlases and brain meshes
|
|
47
|
+
print(yab.get_available_resources())
|
|
48
|
+
|
|
49
|
+
# see the region names for a specific atlas
|
|
50
|
+
print(yab.get_atlas_regions(atlas='aseg', category='subcortical'))
|
|
51
|
+
|
|
52
|
+
# cortical surfaces
|
|
53
|
+
atlas = 'aparc'
|
|
54
|
+
dmap1 = {'L_lateraloccipital': 0.265, 'L_postcentral': 0.086, ...}
|
|
55
|
+
dmap2 = {'L_fusiform': 0.218, 'L_supramarginal': 0.119, ...}
|
|
56
|
+
yab.plot_cortical(data=dmap1, atlas=atlas, vminmax=[-0.1, 0.3],
|
|
57
|
+
bmesh_type='midthickness', views=['left_lateral', 'left_medial'],
|
|
58
|
+
figsize=(600, 300), cmap='viridis', proc_vertices='sharp')
|
|
59
|
+
yab.plot_cortical(data=dmap2, atlas=atlas, vminmax=[-0.1, 0.3],
|
|
60
|
+
bmesh_type='swm', views=['left_lateral', 'left_medial'],
|
|
61
|
+
figsize=(1200, 600), cmap='viridis', proc_vertices='sharp')
|
|
62
|
+
|
|
63
|
+
# subcortical structures
|
|
64
|
+
atlas = 'aseg'
|
|
65
|
+
regs = yab.get_atlas_regions(atlas=atlas, category='subcortical')
|
|
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')
|
|
70
|
+
|
|
71
|
+
# white matter bundles
|
|
72
|
+
atlas = 'xtract_tiny'
|
|
73
|
+
regs = yab.get_atlas_regions(atlas=atlas, category='tracts')
|
|
74
|
+
data = {reg: np.sin(i) for i, reg in enumerate(regs)}
|
|
75
|
+
yab.plot_tracts(data=data, atlas=atlas, style='matte',
|
|
76
|
+
views=['left_lateral', 'anterior', 'superior'], bmesh_type='pial',
|
|
77
|
+
bmesh_alpha=0.1, figsize=(1600, 800), cmap='viridis')
|
|
69
78
|
|
|
70
79
|
```
|
|
71
80
|
|
|
@@ -23,16 +23,16 @@ def test_plot_cortical():
|
|
|
23
23
|
"""
|
|
24
24
|
Integration test: Downloads 'aparc' and plots it.
|
|
25
25
|
"""
|
|
26
|
-
yab.plot_cortical(atlas='aparc', display_type=
|
|
26
|
+
yab.plot_cortical(atlas='aparc', display_type=None)
|
|
27
27
|
|
|
28
28
|
def test_plot_subcortical():
|
|
29
29
|
"""
|
|
30
30
|
Integration test: Downloads 'aseg' and plots it.
|
|
31
31
|
"""
|
|
32
|
-
yab.plot_subcortical(atlas='aseg', display_type=
|
|
32
|
+
yab.plot_subcortical(atlas='aseg', display_type=None)
|
|
33
33
|
|
|
34
34
|
def test_plot_tracts():
|
|
35
35
|
"""
|
|
36
36
|
Integration test: Downloads 'xtract_tiny' and plots it.
|
|
37
37
|
"""
|
|
38
|
-
yab.plot_tracts(atlas='xtract_tiny', display_type=
|
|
38
|
+
yab.plot_tracts(atlas='xtract_tiny', display_type=None)
|
|
@@ -17,5 +17,9 @@ tracts-xtract_large.zip sha256:e2d59e9f90b024018788af87e389f93b2fa9ac21360460196
|
|
|
17
17
|
tracts-xtract_medium.zip sha256:f095028d3dfc0f974ebef1feffe4006b5bac1366325a784ea2301c56f583b1e7 https://osf.io/7tbjg/download
|
|
18
18
|
tracts-xtract_small.zip sha256:9d3fe2c57acb87e8d13eb40a49a6c2c354dbe1a020122b693e9bf713e57cad5b https://osf.io/bmn3a/download
|
|
19
19
|
tracts-xtract_tiny.zip sha256:469f9ed8ed5ceb7f8e17c9a0a92f1491d540814f832ef56441a7539532111f41 https://osf.io/a73x2/download
|
|
20
|
-
bmesh-
|
|
21
|
-
bmesh-
|
|
20
|
+
bmesh-inflated.zip sha256:ba72f9e75f16fe767f6bbcec5c98381f6a20b2f5e2092505b4692844a1686461 https://osf.io/kfzjg/download
|
|
21
|
+
bmesh-midthickness.zip sha256:ef8209853e1dac8804a60b5cde0b653ef2f6c77b6c7536f7acaa69a46dbb06c0 https://osf.io/xaktf/download
|
|
22
|
+
bmesh-pial.zip sha256:5e36ba7883dd2a88485fc45c9166ed48e22db607d45655242961242b51f9f126 https://osf.io/knpg8/download
|
|
23
|
+
bmesh-swm.zip sha256:1a516c070cb63557751d841d5cf75772660872b89ec749ee19c23298d77fa807 https://osf.io/hpbc9/download
|
|
24
|
+
bmesh-very_inflated.zip sha256:8c33a00c718a47f0af118ce2c5804a2adf13d79be05c4665ca26582e3e8dd977 https://osf.io/xp9jr/download
|
|
25
|
+
bmesh-white.zip sha256:b885ac1a4dcdf9e241a97e8d3ca59d7b1c86d8a3ebe17df512993bc5c1c0354a https://osf.io/wfc5t/download
|
|
@@ -14,7 +14,8 @@ from .data import (
|
|
|
14
14
|
from .utils import (
|
|
15
15
|
load_gii, load_gii2pv, prep_data,
|
|
16
16
|
generate_distinct_colors, parse_lut, map_values_to_surface,
|
|
17
|
-
|
|
17
|
+
get_puzzle_pieces, apply_internal_blur, apply_dilation,
|
|
18
|
+
get_smooth_mask, lines_from_streamlines, make_cortical_mesh
|
|
18
19
|
)
|
|
19
20
|
|
|
20
21
|
from .scene import (
|
|
@@ -26,8 +27,8 @@ from .scene import (
|
|
|
26
27
|
# --- plot for cortical surface ---
|
|
27
28
|
|
|
28
29
|
def plot_cortical(data=None, atlas=None, custom_atlas_path=None, views=None, layout=None,
|
|
29
|
-
figsize=(1000, 600), cmap='
|
|
30
|
-
nan_color=(1.0, 1.0, 1.0), style='default', zoom=1.2,
|
|
30
|
+
bmesh_type='midthickness', figsize=(1000, 600), cmap='coolwarm', vminmax=[None, None],
|
|
31
|
+
nan_color=(1.0, 1.0, 1.0), style='default', zoom=1.2, proc_vertices=None,
|
|
31
32
|
display_type='static', export_path=None):
|
|
32
33
|
"""
|
|
33
34
|
Visualize data on the cortical surface using a specified atlas.
|
|
@@ -54,6 +55,9 @@ def plot_cortical(data=None, atlas=None, custom_atlas_path=None, views=None, lay
|
|
|
54
55
|
or a dictionary of camera configurations. Defaults to all views.
|
|
55
56
|
layout : tuple (rows, cols), optional
|
|
56
57
|
Grid layout for subplots. If None, automatically calculated based on the number of views.
|
|
58
|
+
bmesh_type : str
|
|
59
|
+
Name of the background context brain mesh (e.g., 'midthickness', 'white', 'swm', etc).
|
|
60
|
+
Default is 'midthickness'.
|
|
57
61
|
figsize : tuple (width, height), optional
|
|
58
62
|
Window size in pixels. Default is (1000, 600).
|
|
59
63
|
cmap : str or matplotlib.colors.Colormap, optional
|
|
@@ -67,6 +71,11 @@ def plot_cortical(data=None, atlas=None, custom_atlas_path=None, views=None, lay
|
|
|
67
71
|
Lighting preset ('default', 'matte', 'glossy', 'sculpted', 'flat').
|
|
68
72
|
zoom : float, optional
|
|
69
73
|
Camera zoom level. >1.0 zooms in, <1.0 zooms out. Default is 1.2.
|
|
74
|
+
proc_vertices : str or None, optional
|
|
75
|
+
Whether to process the vertices edges according to geometry of bmesh.
|
|
76
|
+
Set to None to not perform (default).
|
|
77
|
+
'blur': Applies simple blurring between different color vertices (low performance impact).
|
|
78
|
+
'sharp': Applies sharpening of the resolution of different color vertices (high performance impact).
|
|
70
79
|
display_type : {'static', 'interactive', 'none'}, optional
|
|
71
80
|
'static': Returns a static image (good for notebooks).
|
|
72
81
|
'interactive': Opens an interactive viewer.
|
|
@@ -80,19 +89,18 @@ def plot_cortical(data=None, atlas=None, custom_atlas_path=None, views=None, lay
|
|
|
80
89
|
The plotter instance used for rendering.
|
|
81
90
|
"""
|
|
82
91
|
|
|
83
|
-
#
|
|
92
|
+
# atlas and categorical check
|
|
84
93
|
if atlas is None and custom_atlas_path is None:
|
|
85
94
|
atlas = 'aparc'
|
|
95
|
+
is_cat = (data is None)
|
|
86
96
|
|
|
87
97
|
# load brain mesh
|
|
88
|
-
bmesh_path = _resolve_resource_path(
|
|
89
|
-
lh_v, lh_f = load_gii(os.path.join(bmesh_path, '
|
|
90
|
-
rh_v, rh_f = load_gii(os.path.join(bmesh_path, '
|
|
98
|
+
bmesh_path = _resolve_resource_path(bmesh_type, 'bmesh')
|
|
99
|
+
lh_v, lh_f = load_gii(os.path.join(bmesh_path, f'fs_LR.32k.L.{bmesh_type}.surf.gii'))
|
|
100
|
+
rh_v, rh_f = load_gii(os.path.join(bmesh_path, f'fs_LR.32k.R.{bmesh_type}.surf.gii'))
|
|
91
101
|
|
|
92
|
-
# resolve atlas
|
|
102
|
+
# resolve atlas
|
|
93
103
|
atlas_dir = _resolve_resource_path(atlas, 'cortical', custom_path=custom_atlas_path)
|
|
94
|
-
|
|
95
|
-
# locate files
|
|
96
104
|
check_name = None if custom_atlas_path else atlas
|
|
97
105
|
csv_path, lut_path = _find_cortical_files(atlas_dir, strict_name=check_name)
|
|
98
106
|
|
|
@@ -102,28 +110,43 @@ def plot_cortical(data=None, atlas=None, custom_atlas_path=None, views=None, lay
|
|
|
102
110
|
|
|
103
111
|
# map data
|
|
104
112
|
all_vals = map_values_to_surface(data, tar_labels, lut_ids, lut_names)
|
|
105
|
-
|
|
106
|
-
|
|
113
|
+
lh_vals_raw = all_vals[:len(lh_v)]
|
|
114
|
+
rh_vals_raw = all_vals[len(lh_v):]
|
|
115
|
+
|
|
116
|
+
# vertices processing
|
|
117
|
+
results = []
|
|
118
|
+
for v, f, raw in [(lh_v, lh_f, lh_vals_raw), (rh_v, rh_f, rh_vals_raw)]:
|
|
119
|
+
if proc_vertices == 'sharp':
|
|
120
|
+
# razor-sharp puzzle mode
|
|
121
|
+
base, pieces = get_puzzle_pieces(v, f, raw)
|
|
122
|
+
results.append((base, pieces))
|
|
123
|
+
else:
|
|
124
|
+
# single clipper mode (blur or none)
|
|
125
|
+
v_proc = apply_internal_blur(f, raw, iterations=3, weight=0.3) if proc_vertices == 'blur' else raw
|
|
126
|
+
dilated = apply_dilation(f, v_proc, iterations=4)
|
|
127
|
+
o_guide = get_smooth_mask(f, np.where(np.isnan(raw), 0.0, 1.0), iterations=4)
|
|
128
|
+
|
|
129
|
+
mesh = make_cortical_mesh(v, f, dilated)
|
|
130
|
+
mesh['Slice_Mask'] = o_guide
|
|
131
|
+
data_p = mesh.clip_scalar(scalars='Slice_Mask', value=0.5, invert=False)
|
|
132
|
+
base_p = mesh.clip_scalar(scalars='Slice_Mask', value=0.5, invert=True)
|
|
133
|
+
if base_p.n_points > 0: base_p['Data'] = np.full(base_p.n_points, np.nan)
|
|
134
|
+
results.append((base_p, [data_p]))
|
|
135
|
+
(lh_base, lh_parts), (rh_base, rh_parts) = results
|
|
107
136
|
|
|
108
137
|
# setup colors
|
|
109
|
-
is_categorical = (data is None)
|
|
110
138
|
n_colors = 256
|
|
111
|
-
if
|
|
139
|
+
if is_cat:
|
|
112
140
|
_lut_colors = lut_colors.copy()
|
|
113
|
-
_lut_colors[0] = nan_color
|
|
141
|
+
_lut_colors[0] = nan_color # TODO: check nancolor
|
|
114
142
|
cmap = ListedColormap(_lut_colors)
|
|
115
143
|
n_colors = len(_lut_colors)
|
|
116
144
|
vmin, vmax = 0, max_id
|
|
117
145
|
else:
|
|
118
|
-
if cmap is None: cmap = 'RdYlBu_r'
|
|
119
146
|
vmin = vminmax[0] if vminmax[0] is not None else np.nanmin(all_vals)
|
|
120
147
|
vmax = vminmax[1] if vminmax[1] is not None else np.nanmax(all_vals)
|
|
121
148
|
|
|
122
|
-
#
|
|
123
|
-
lh_mesh = make_cortical_mesh(lh_v, lh_f, lh_vals)
|
|
124
|
-
rh_mesh = make_cortical_mesh(rh_v, rh_f, rh_vals)
|
|
125
|
-
|
|
126
|
-
# setup plotter
|
|
149
|
+
# plotter setup
|
|
127
150
|
sel_views = get_view_configs(views)
|
|
128
151
|
plotter, ncols, nrows = setup_plotter(sel_views, layout, figsize, display_type)
|
|
129
152
|
shading_params = get_shading_preset(style)
|
|
@@ -132,46 +155,46 @@ def plot_cortical(data=None, atlas=None, custom_atlas_path=None, views=None, lay
|
|
|
132
155
|
for i, (name, cfg) in enumerate(sel_views.items()):
|
|
133
156
|
plotter.subplot(i // ncols, i % ncols)
|
|
134
157
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
if cfg['side'] in ['
|
|
138
|
-
|
|
139
|
-
|
|
158
|
+
view_bases = []
|
|
159
|
+
view_pieces = []
|
|
160
|
+
if cfg['side'] in ['L', 'both']:
|
|
161
|
+
if lh_base.n_points > 0: view_bases.append(lh_base)
|
|
162
|
+
view_pieces.extend(lh_parts)
|
|
163
|
+
if cfg['side'] in ['R', 'both']:
|
|
164
|
+
if rh_base.n_points > 0: view_bases.append(rh_base)
|
|
165
|
+
view_pieces.extend(rh_parts)
|
|
166
|
+
|
|
167
|
+
# brain meshes
|
|
168
|
+
for b_mesh in view_bases:
|
|
169
|
+
plotter.add_mesh(b_mesh, color=nan_color, smooth_shading=True, **shading_params)
|
|
170
|
+
|
|
171
|
+
# data vertices
|
|
172
|
+
for p_mesh in view_pieces:
|
|
173
|
+
if p_mesh.n_points == 0: continue
|
|
174
|
+
interp = (proc_vertices == 'blur') # only blur mode uses interpolation
|
|
175
|
+
|
|
140
176
|
actor = plotter.add_mesh(
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
n_colors=n_colors,
|
|
146
|
-
nan_color=nan_color,
|
|
147
|
-
show_scalar_bar=False,
|
|
148
|
-
rgb=False,
|
|
149
|
-
smooth_shading=True,
|
|
150
|
-
show_edges=False,
|
|
151
|
-
interpolate_before_map=False,
|
|
152
|
-
**shading_params
|
|
153
|
-
)
|
|
177
|
+
p_mesh, scalars='Data', cmap=cmap, clim=(vmin, vmax),
|
|
178
|
+
n_colors=n_colors, nan_color=nan_color, show_scalar_bar=False,
|
|
179
|
+
smooth_shading=True, interpolate_before_map=interp, **shading_params
|
|
180
|
+
) # rgb=False, show_edges=False, interpolate_before_map=False,
|
|
154
181
|
if scalar_bar_mapper is None: scalar_bar_mapper = actor.mapper
|
|
155
182
|
|
|
156
183
|
set_camera(plotter, cfg, zoom=zoom)
|
|
157
184
|
plotter.hide_axes()
|
|
158
|
-
|
|
159
|
-
if not
|
|
185
|
+
|
|
186
|
+
if not is_cat and scalar_bar_mapper:
|
|
160
187
|
plotter.subplot(nrows - 1, 0)
|
|
161
|
-
plotter.add_scalar_bar(mapper=scalar_bar_mapper,
|
|
162
|
-
vertical=False, position_x=0.3, position_y=0.25,
|
|
163
|
-
height=0.5, width=0.4,color='black',
|
|
164
|
-
label_font_size=20)
|
|
188
|
+
plotter.add_scalar_bar(mapper=scalar_bar_mapper, vertical=False, position_x=0.3, position_y=0.25, height=0.5, width=0.4)
|
|
165
189
|
|
|
166
190
|
return finalize_plot(plotter, export_path, display_type)
|
|
167
191
|
|
|
168
192
|
|
|
169
|
-
|
|
170
193
|
# --- plot for subcortical structures ---
|
|
171
194
|
|
|
172
195
|
def plot_subcortical(data=None, atlas=None, custom_atlas_path=None, views=None, layout=None,
|
|
173
196
|
figsize=(1000, 600), cmap='coolwarm', vminmax=[None, None], nan_color='#cccccc',
|
|
174
|
-
nan_alpha=1.0, legend=False, style='default', bmesh_type='
|
|
197
|
+
nan_alpha=1.0, legend=False, style='default', bmesh_type='midthickness',
|
|
175
198
|
bmesh_alpha=0.1, bmesh_color='lightgray', zoom=1.2, display_type='static',
|
|
176
199
|
export_path=None, custom_atlas_proc=dict(smooth_i=15, smooth_f=0.6)):
|
|
177
200
|
"""
|
|
@@ -213,8 +236,8 @@ def plot_subcortical(data=None, atlas=None, custom_atlas_path=None, views=None,
|
|
|
213
236
|
style : str, optional
|
|
214
237
|
Lighting preset ('default', 'matte', 'glossy', 'sculpted', 'flat').
|
|
215
238
|
bmesh_type : str or None, optional
|
|
216
|
-
Name of the background context brain mesh (e.g., '
|
|
217
|
-
Set to None to hide the context brain.
|
|
239
|
+
Name of the background context brain mesh (e.g., 'midthickness', 'white', 'swm', etc).
|
|
240
|
+
Set to None to hide the context brain. Default is 'midthickness'.
|
|
218
241
|
bmesh_alpha : float, optional
|
|
219
242
|
Opacity of the context brain mesh. Default is 0.1.
|
|
220
243
|
bmesh_color : str, optional
|
|
@@ -246,8 +269,8 @@ def plot_subcortical(data=None, atlas=None, custom_atlas_path=None, views=None,
|
|
|
246
269
|
bmesh = {}
|
|
247
270
|
if bmesh_type:
|
|
248
271
|
bmesh_path = _resolve_resource_path(bmesh_type, 'bmesh')
|
|
249
|
-
for h in ['
|
|
250
|
-
fpath = os.path.join(bmesh_path, f'{
|
|
272
|
+
for h in ['L', 'R']:
|
|
273
|
+
fpath = os.path.join(bmesh_path, f'fs_LR.32k.{h}.{bmesh_type}.surf.gii')
|
|
251
274
|
if os.path.exists(fpath):
|
|
252
275
|
bmesh[h] = load_gii2pv(fpath)
|
|
253
276
|
|
|
@@ -371,7 +394,7 @@ def clear_tract_cache():
|
|
|
371
394
|
def plot_tracts(data=None, atlas=None, custom_atlas_path=None, views=None, layout=None,
|
|
372
395
|
figsize=(1000, 800), cmap='coolwarm', alpha=1.0, vminmax=[None, None],
|
|
373
396
|
nan_color='#BDBDBD', nan_alpha=1.0, legend=False, style='default',
|
|
374
|
-
bmesh_type='
|
|
397
|
+
bmesh_type='midthickness', bmesh_alpha=0.2, bmesh_color='lightgray',
|
|
375
398
|
zoom=1.2, orientation_coloring=False, display_type='static',
|
|
376
399
|
tract_kwargs=dict(render_lines_as_tubes=True, line_width=1.2),
|
|
377
400
|
export_path=None):
|
|
@@ -417,8 +440,8 @@ def plot_tracts(data=None, atlas=None, custom_atlas_path=None, views=None, layou
|
|
|
417
440
|
style : str, optional
|
|
418
441
|
Lighting preset ('default', 'matte', 'glossy', 'sculpted', 'flat').
|
|
419
442
|
bmesh_type : str or None, optional
|
|
420
|
-
Name of the background context brain mesh (e.g., '
|
|
421
|
-
Set to None to hide the context brain.
|
|
443
|
+
Name of the background context brain mesh (e.g., 'midthickness', 'white', 'swm', etc).
|
|
444
|
+
Set to None to hide the context brain. Default is 'midthickness'.
|
|
422
445
|
bmesh_alpha : float, optional
|
|
423
446
|
Opacity of the context brain mesh. Default is 0.2.
|
|
424
447
|
bmesh_color : str, optional
|
|
@@ -472,8 +495,8 @@ def plot_tracts(data=None, atlas=None, custom_atlas_path=None, views=None, layou
|
|
|
472
495
|
bmesh = {}
|
|
473
496
|
if bmesh_type:
|
|
474
497
|
bmesh_path = _resolve_resource_path(bmesh_type, 'bmesh')
|
|
475
|
-
for h in ['
|
|
476
|
-
fpath = os.path.join(bmesh_path, f'{
|
|
498
|
+
for h in ['L', 'R']:
|
|
499
|
+
fpath = os.path.join(bmesh_path, f'fs_LR.32k.{h}.{bmesh_type}.surf.gii')
|
|
477
500
|
if os.path.exists(fpath):
|
|
478
501
|
bmesh[h] = load_gii2pv(fpath)
|
|
479
502
|
|
|
@@ -91,7 +91,7 @@ def setup_plotter(sel_views, layout, figsize, display_type, needs_bottom_row=Tru
|
|
|
91
91
|
row_weights = None
|
|
92
92
|
|
|
93
93
|
plotter = pv.Plotter(shape=(nrows, ncols), groups=groups, row_weights=row_weights,
|
|
94
|
-
off_screen=(display_type=='
|
|
94
|
+
off_screen=(display_type=='object'), window_size=figsize, border=False)
|
|
95
95
|
plotter.set_background('white')
|
|
96
96
|
return plotter, ncols, nrows
|
|
97
97
|
|
|
@@ -101,7 +101,7 @@ def add_context_to_view(plotter, bmesh, view_side, alpha, color, **kwargs):
|
|
|
101
101
|
"""
|
|
102
102
|
if not bmesh: return
|
|
103
103
|
for h, mesh in bmesh.items():
|
|
104
|
-
if (view_side == 'L' and h == '
|
|
104
|
+
if (view_side == 'L' and h == 'L') or (view_side == 'R' and h == 'R'): continue
|
|
105
105
|
plotter.add_mesh(mesh, color=color, opacity=alpha,
|
|
106
106
|
smooth_shading=True, show_edges=False,
|
|
107
107
|
**kwargs)
|
|
@@ -115,10 +115,19 @@ def set_camera(plotter, view_cfg, zoom=1.0, distance=200):
|
|
|
115
115
|
plotter.camera.zoom(zoom)
|
|
116
116
|
|
|
117
117
|
def finalize_plot(plotter, export_path, display_type):
|
|
118
|
-
if export_path:
|
|
118
|
+
if export_path:
|
|
119
|
+
plotter.screenshot(export_path, transparent_background=True)
|
|
119
120
|
|
|
120
|
-
if display_type == 'static':
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
121
|
+
if display_type == 'static':
|
|
122
|
+
out = plotter.show(jupyter_backend='static')
|
|
123
|
+
plotter.close()
|
|
124
|
+
elif display_type == 'interactive':
|
|
125
|
+
out = plotter.show(jupyter_backend='trame')
|
|
126
|
+
elif display_type == 'object':
|
|
127
|
+
plotter.render()
|
|
128
|
+
return plotter
|
|
129
|
+
else:
|
|
130
|
+
plotter.close()
|
|
131
|
+
out = None
|
|
132
|
+
|
|
133
|
+
return out
|
|
@@ -3,6 +3,7 @@ import numpy as np
|
|
|
3
3
|
import pandas as pd
|
|
4
4
|
import nibabel as nib
|
|
5
5
|
import pyvista as pv
|
|
6
|
+
import scipy.sparse as sp
|
|
6
7
|
import matplotlib.pyplot as plt
|
|
7
8
|
from importlib.resources import files
|
|
8
9
|
|
|
@@ -143,6 +144,93 @@ def map_values_to_surface(data, target_labels, lut_ids, dense_lut_names):
|
|
|
143
144
|
lookup_table[valid_ids] = source_values
|
|
144
145
|
return lookup_table[target_labels]
|
|
145
146
|
|
|
147
|
+
def get_adj(faces, n_v):
|
|
148
|
+
"""build adjacency matrix from faces."""
|
|
149
|
+
row, col = [], []
|
|
150
|
+
for tri in faces:
|
|
151
|
+
row.extend([tri[0], tri[1], tri[2], tri[0], tri[1], tri[2]])
|
|
152
|
+
col.extend([tri[1], tri[2], tri[0], tri[2], tri[0], tri[1]])
|
|
153
|
+
adj = sp.csc_matrix((np.ones_like(row), (row, col)), shape=(n_v, n_v))
|
|
154
|
+
adj.data = np.ones_like(adj.data)
|
|
155
|
+
return adj
|
|
156
|
+
|
|
157
|
+
def get_smooth_mask(faces, data, iterations=4):
|
|
158
|
+
"""blur binary mask for guide of geometric slicing."""
|
|
159
|
+
n_v = len(data)
|
|
160
|
+
mask = data.astype(np.float64)
|
|
161
|
+
adj = get_adj(faces, n_v)
|
|
162
|
+
deg = np.array(adj.sum(axis=1)).flatten()
|
|
163
|
+
deg[deg == 0] = 1.0
|
|
164
|
+
for _ in range(iterations):
|
|
165
|
+
mask = (mask + (adj.dot(mask) / deg)) / 2.0
|
|
166
|
+
return mask
|
|
167
|
+
|
|
168
|
+
def apply_internal_blur(faces, data, iterations=1, weight=0.2):
|
|
169
|
+
"""blur data only on borders where different regions touch."""
|
|
170
|
+
data_out = np.copy(data)
|
|
171
|
+
n_v = len(data)
|
|
172
|
+
adj = get_adj(faces, n_v)
|
|
173
|
+
rows, cols = adj.nonzero()
|
|
174
|
+
valid = ~np.isnan(data_out)
|
|
175
|
+
diff = valid[rows] & valid[cols] & ~np.isclose(data_out[rows], data_out[cols], atol=1e-5)
|
|
176
|
+
b_verts = np.unique(rows[diff])
|
|
177
|
+
|
|
178
|
+
if len(b_verts) == 0: return data_out
|
|
179
|
+
|
|
180
|
+
for _ in range(iterations):
|
|
181
|
+
temp = np.nan_to_num(data_out, nan=0.0)
|
|
182
|
+
v_counts = adj.dot(valid.astype(float))
|
|
183
|
+
v_counts[v_counts == 0] = 1.0
|
|
184
|
+
n_mean = adj.dot(temp) / v_counts
|
|
185
|
+
data_out[b_verts] = (1 - weight) * data_out[b_verts] + weight * n_mean[b_verts]
|
|
186
|
+
return data_out
|
|
187
|
+
|
|
188
|
+
def apply_dilation(faces, data, iterations=4):
|
|
189
|
+
"""push values into NaN space to keep geometric cut pure."""
|
|
190
|
+
data_out = np.copy(data)
|
|
191
|
+
n_v = len(data)
|
|
192
|
+
adj = get_adj(faces, n_v)
|
|
193
|
+
for _ in range(iterations):
|
|
194
|
+
nan_m = np.isnan(data_out)
|
|
195
|
+
temp = np.nan_to_num(data_out, nan=0.0)
|
|
196
|
+
v_counts = adj.dot((~nan_m).astype(float))
|
|
197
|
+
s_neighbors = adj.dot(temp)
|
|
198
|
+
u_mask = nan_m & (v_counts > 0)
|
|
199
|
+
data_out[u_mask] = s_neighbors[u_mask] / v_counts[u_mask]
|
|
200
|
+
return data_out
|
|
201
|
+
|
|
202
|
+
def get_puzzle_pieces(v, f, raw_vals):
|
|
203
|
+
"""carve out geometric pieces with slight overlap to prevent gaps."""
|
|
204
|
+
pieces = []
|
|
205
|
+
valid_mask = ~np.isnan(raw_vals) & (raw_vals != 0.0)
|
|
206
|
+
u_vals = np.unique(raw_vals[valid_mask])
|
|
207
|
+
master = make_cortical_mesh(v, f, np.zeros_like(raw_vals))
|
|
208
|
+
|
|
209
|
+
for val in u_vals:
|
|
210
|
+
r_mask = np.where(raw_vals == val, 1.0, 0.0)
|
|
211
|
+
s_mask = get_smooth_mask(f, r_mask, iterations=4)
|
|
212
|
+
temp = master.copy()
|
|
213
|
+
temp['Slice_Mask'] = s_mask
|
|
214
|
+
# reduce search space
|
|
215
|
+
patch = temp.threshold(0.01, scalars='Slice_Mask')
|
|
216
|
+
if patch.n_points > 0:
|
|
217
|
+
# use 0.48 (slightly expanded) for pieces to seal cracks
|
|
218
|
+
piece = patch.clip_scalar(scalars='Slice_Mask', value=0.48, invert=False)
|
|
219
|
+
if piece.n_points > 0:
|
|
220
|
+
piece['Data'] = np.full(piece.n_points, val)
|
|
221
|
+
pieces.append(piece)
|
|
222
|
+
|
|
223
|
+
# slice base brain
|
|
224
|
+
all_mask = np.where(valid_mask, 1.0, 0.0)
|
|
225
|
+
s_all = get_smooth_mask(f, all_mask, iterations=4)
|
|
226
|
+
master['Slice_Mask'] = s_all
|
|
227
|
+
# use 0.52 (slightly contracted) for the hole to ensure colored pieces cover the edge
|
|
228
|
+
base_p = master.clip_scalar(scalars='Slice_Mask', value=0.52, invert=True)
|
|
229
|
+
if base_p.n_points > 0:
|
|
230
|
+
base_p['Data'] = np.full(base_p.n_points, np.nan)
|
|
231
|
+
|
|
232
|
+
return base_p, pieces
|
|
233
|
+
|
|
146
234
|
def lines_from_streamlines(streamlines):
|
|
147
235
|
if len(streamlines) == 0: return np.array([]), np.array([]), np.array([])
|
|
148
236
|
|
|
@@ -155,7 +243,7 @@ def lines_from_streamlines(streamlines):
|
|
|
155
243
|
cells.append(np.hstack([[length], np.arange(offset, offset + length)]))
|
|
156
244
|
lines = np.hstack(cells)
|
|
157
245
|
|
|
158
|
-
#
|
|
246
|
+
# calculate tangents
|
|
159
247
|
tangents = []
|
|
160
248
|
for s in streamlines:
|
|
161
249
|
if len(s) < 2:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: yabplot
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.5
|
|
4
4
|
Summary: yet another brain plot
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -63,32 +63,41 @@ please refer to the [documentation](https://teanijarv.github.io/yabplot/) for mo
|
|
|
63
63
|
import yabplot as yab
|
|
64
64
|
import numpy as np
|
|
65
65
|
|
|
66
|
-
#
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
# see
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
#
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
yab.
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
#
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
66
|
+
# check that you have the latest version
|
|
67
|
+
print(yab.__version__)
|
|
68
|
+
|
|
69
|
+
# see available atlases and brain meshes
|
|
70
|
+
print(yab.get_available_resources())
|
|
71
|
+
|
|
72
|
+
# see the region names for a specific atlas
|
|
73
|
+
print(yab.get_atlas_regions(atlas='aseg', category='subcortical'))
|
|
74
|
+
|
|
75
|
+
# cortical surfaces
|
|
76
|
+
atlas = 'aparc'
|
|
77
|
+
dmap1 = {'L_lateraloccipital': 0.265, 'L_postcentral': 0.086, ...}
|
|
78
|
+
dmap2 = {'L_fusiform': 0.218, 'L_supramarginal': 0.119, ...}
|
|
79
|
+
yab.plot_cortical(data=dmap1, atlas=atlas, vminmax=[-0.1, 0.3],
|
|
80
|
+
bmesh_type='midthickness', views=['left_lateral', 'left_medial'],
|
|
81
|
+
figsize=(600, 300), cmap='viridis', proc_vertices='sharp')
|
|
82
|
+
yab.plot_cortical(data=dmap2, atlas=atlas, vminmax=[-0.1, 0.3],
|
|
83
|
+
bmesh_type='swm', views=['left_lateral', 'left_medial'],
|
|
84
|
+
figsize=(1200, 600), cmap='viridis', proc_vertices='sharp')
|
|
85
|
+
|
|
86
|
+
# subcortical structures
|
|
87
|
+
atlas = 'aseg'
|
|
88
|
+
regs = yab.get_atlas_regions(atlas=atlas, category='subcortical')
|
|
89
|
+
data = np.arange(1, len(regs)+1)
|
|
90
|
+
yab.plot_subcortical(data=data, atlas=atlas, vminmax=[2, 14],
|
|
91
|
+
views=['left_lateral', 'superior', 'right_lateral'],
|
|
92
|
+
bmesh_alpha=0.1, figsize=(600, 300), cmap='viridis')
|
|
93
|
+
|
|
94
|
+
# white matter bundles
|
|
95
|
+
atlas = 'xtract_tiny'
|
|
96
|
+
regs = yab.get_atlas_regions(atlas=atlas, category='tracts')
|
|
97
|
+
data = {reg: np.sin(i) for i, reg in enumerate(regs)}
|
|
98
|
+
yab.plot_tracts(data=data, atlas=atlas, style='matte',
|
|
99
|
+
views=['left_lateral', 'anterior', 'superior'], bmesh_type='pial',
|
|
100
|
+
bmesh_alpha=0.1, figsize=(1600, 800), cmap='viridis')
|
|
92
101
|
|
|
93
102
|
```
|
|
94
103
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|