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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: yabplot
3
- Version: 0.1.4
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
- # see available cortical atlases
67
- atlases = yab.get_available_resources(category='cortical')
68
-
69
- # see the region names within the aseg atlas
70
- regions = yab.get_atlas_regions(atlas='aseg', category='subcortical')
71
-
72
- # plot data on cortical regions
73
- data = np.arange(0, 1, 0.001)
74
- yab.plot_cortical(data=data, atlas='schaefer_1000', figsize=(600, 300),
75
- cmap='viridis', vminmax=[0, 1], style='default',
76
- views=['left_lateral', 'superior', 'right_lateral'])
77
-
78
-
79
- # plot values for specific subcortical regions
80
- data = {'Left_Amygdala': 0.8, 'Right_Hippocampus': 0.5,
81
- 'Right_Thalamus': -0.5, 'Left_Putamen': -1}
82
- yab.plot_subcortical(data=data, atlas='aseg', figsize=(600, 450), layout=(2, 2),
83
- views=['superior', 'anterior', 'left_lateral', 'right_lateral'],
84
- cmap='coolwarm', vminmax=[-1, 1], style='matte')
85
-
86
- # plot data on white matter bundles
87
- regions = yab.get_atlas_regions(atlas='xtract_tiny', category='tracts')
88
- data = np.arange(0, len(regions))
89
- yab.plot_tracts(data=data, atlas='xtract_tiny', figsize=(600, 300),
90
- views=['superior', 'anterior', 'left_lateral'], nan_color='#cccccc',
91
- bmesh_type='fsaverage', style='default', cmap='plasma')
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
- # see available cortical atlases
44
- atlases = yab.get_available_resources(category='cortical')
45
-
46
- # see the region names within the aseg atlas
47
- regions = yab.get_atlas_regions(atlas='aseg', category='subcortical')
48
-
49
- # plot data on cortical regions
50
- data = np.arange(0, 1, 0.001)
51
- yab.plot_cortical(data=data, atlas='schaefer_1000', figsize=(600, 300),
52
- cmap='viridis', vminmax=[0, 1], style='default',
53
- views=['left_lateral', 'superior', 'right_lateral'])
54
-
55
-
56
- # plot values for specific subcortical regions
57
- data = {'Left_Amygdala': 0.8, 'Right_Hippocampus': 0.5,
58
- 'Right_Thalamus': -0.5, 'Left_Putamen': -1}
59
- yab.plot_subcortical(data=data, atlas='aseg', figsize=(600, 450), layout=(2, 2),
60
- views=['superior', 'anterior', 'left_lateral', 'right_lateral'],
61
- cmap='coolwarm', vminmax=[-1, 1], style='matte')
62
-
63
- # plot data on white matter bundles
64
- regions = yab.get_atlas_regions(atlas='xtract_tiny', category='tracts')
65
- data = np.arange(0, len(regions))
66
- yab.plot_tracts(data=data, atlas='xtract_tiny', figsize=(600, 300),
67
- views=['superior', 'anterior', 'left_lateral'], nan_color='#cccccc',
68
- bmesh_type='fsaverage', style='default', cmap='plasma')
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
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "yabplot"
3
- version = "0.1.4"
3
+ version = "0.1.5"
4
4
  description = "yet another brain plot"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -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='none')
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='none')
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='none')
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-conte69.zip sha256:5ba9ffc89ae7f640590a80fc188d277b1b49a34a0bd99c0c9e5cfb52fc963cac https://osf.io/h4ugr/download
21
- bmesh-fsaverage.zip sha256:bccec9e3fc10ffc9d1cbdb6ad2b0ffd81c93ca0fa8d26fc530d9b4d58dfcd7a0 https://osf.io/sq7am/download
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
- lines_from_streamlines, make_cortical_mesh
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='RdYlBu_r', vminmax=[None, None],
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
- # defaults
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('conte69', 'bmesh')
89
- lh_v, lh_f = load_gii(os.path.join(bmesh_path, 'conte69.lh.gii'))
90
- rh_v, rh_f = load_gii(os.path.join(bmesh_path, 'conte69.rh.gii'))
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 path (either download or custom directory)
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
- lh_vals = all_vals[:len(lh_v)]
106
- rh_vals = all_vals[len(lh_v):]
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 is_categorical:
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
- # create meshes
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
- meshes = []
136
- if cfg['side'] in ['L', 'both']: meshes.append(lh_mesh)
137
- if cfg['side'] in ['R', 'both']: meshes.append(rh_mesh)
138
-
139
- for mesh in meshes:
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
- mesh,
142
- scalars='Data',
143
- cmap=cmap,
144
- clim=(vmin, vmax),
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 is_categorical and scalar_bar_mapper:
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, title='', n_labels=2,
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='conte69',
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., 'conte69').
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 ['lh', 'rh']:
250
- fpath = os.path.join(bmesh_path, f'{bmesh_type}.{h}.gii')
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='conte69', bmesh_alpha=0.2, bmesh_color='lightgray',
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., 'conte69').
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 ['lh', 'rh']:
476
- fpath = os.path.join(bmesh_path, f'{bmesh_type}.{h}.gii')
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=='none'), window_size=figsize, border=False)
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 == 'rh') or (view_side == 'R' and h == 'lh'): continue
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: plotter.screenshot(export_path, transparent_background=True)
118
+ if export_path:
119
+ plotter.screenshot(export_path, transparent_background=True)
119
120
 
120
- if display_type == 'static': plotter.show(jupyter_backend='static')
121
- elif display_type == 'interactive': plotter.show(jupyter_backend='trame')
122
- elif display_type == 'none': plotter.close()
123
- return plotter
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
- # Calculate tangents
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.4
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
- # see available cortical atlases
67
- atlases = yab.get_available_resources(category='cortical')
68
-
69
- # see the region names within the aseg atlas
70
- regions = yab.get_atlas_regions(atlas='aseg', category='subcortical')
71
-
72
- # plot data on cortical regions
73
- data = np.arange(0, 1, 0.001)
74
- yab.plot_cortical(data=data, atlas='schaefer_1000', figsize=(600, 300),
75
- cmap='viridis', vminmax=[0, 1], style='default',
76
- views=['left_lateral', 'superior', 'right_lateral'])
77
-
78
-
79
- # plot values for specific subcortical regions
80
- data = {'Left_Amygdala': 0.8, 'Right_Hippocampus': 0.5,
81
- 'Right_Thalamus': -0.5, 'Left_Putamen': -1}
82
- yab.plot_subcortical(data=data, atlas='aseg', figsize=(600, 450), layout=(2, 2),
83
- views=['superior', 'anterior', 'left_lateral', 'right_lateral'],
84
- cmap='coolwarm', vminmax=[-1, 1], style='matte')
85
-
86
- # plot data on white matter bundles
87
- regions = yab.get_atlas_regions(atlas='xtract_tiny', category='tracts')
88
- data = np.arange(0, len(regions))
89
- yab.plot_tracts(data=data, atlas='xtract_tiny', figsize=(600, 300),
90
- views=['superior', 'anterior', 'left_lateral'], nan_color='#cccccc',
91
- bmesh_type='fsaverage', style='default', cmap='plasma')
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