yabplot 0.1.5__tar.gz → 0.2.2__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.5
3
+ Version: 0.2.2
4
4
  Summary: yet another brain plot
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -55,6 +55,9 @@ pip install yabplot --upgrade # to update
55
55
 
56
56
  dependencies: python 3.11 with ipywidgets, nibabel, pandas, pooch, pyvista, scikit-image, trame, trame-vtk, trame-vuetify
57
57
 
58
+ (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)
59
+
60
+
58
61
  ## quick start
59
62
 
60
63
  please refer to the [documentation](https://teanijarv.github.io/yabplot/) for more comprehensive guides.
@@ -32,6 +32,9 @@ pip install yabplot --upgrade # to update
32
32
 
33
33
  dependencies: python 3.11 with ipywidgets, nibabel, pandas, pooch, pyvista, scikit-image, trame, trame-vtk, trame-vuetify
34
34
 
35
+ (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)
36
+
37
+
35
38
  ## quick start
36
39
 
37
40
  please refer to the [documentation](https://teanijarv.github.io/yabplot/) for more comprehensive guides.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "yabplot"
3
- version = "0.1.5"
3
+ version = "0.2.2"
4
4
  description = "yet another brain plot"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -29,4 +29,4 @@ where = ["."]
29
29
  include = ["yabplot*"]
30
30
 
31
31
  [tool.setuptools.package-data]
32
- "yabplot" = ["*.txt", "*.json"]
32
+ "yabplot" = ["*.txt", "*.json"]
@@ -1,7 +1,8 @@
1
1
  from importlib.metadata import version, PackageNotFoundError
2
2
 
3
3
  from .plotting import plot_cortical, plot_subcortical, plot_tracts, clear_tract_cache
4
- from .data import get_available_resources, get_atlas_regions
4
+ from .data import get_available_resources, get_atlas_regions, get_surface_paths
5
+ from .atlas_builder import build_cortical_atlas, build_subcortical_atlas
5
6
 
6
7
  try:
7
8
  __version__ = version("yabplot")
@@ -0,0 +1,404 @@
1
+ import os
2
+ import glob
3
+ import numpy as np
4
+ import nibabel as nib
5
+ import scipy.sparse as sp
6
+ import pyvista as pv
7
+ from skimage import measure
8
+ from .wrappers import run_wb_import, run_wb_projection
9
+ from .data import get_surface_paths
10
+ from .plotting import plot_cortical, plot_subcortical
11
+
12
+ ### CORTICAL
13
+
14
+ def _build_adjacency(surf_path, n_vert=32492):
15
+ """internal helper to build surface adjacency matrix."""
16
+ surf = nib.load(surf_path)
17
+ faces = surf.darrays[1].data.astype(int)
18
+ edges = np.vstack([faces[:, [0, 1]], faces[:, [1, 2]], faces[:, [2, 0]]])
19
+ row, col = np.concatenate([edges[:, 0], edges[:, 1]]), np.concatenate([edges[:, 1], edges[:, 0]])
20
+ return sp.coo_matrix((np.ones(len(row), dtype=int), (row, col)), shape=(n_vert, n_vert)).tocsr()
21
+
22
+ def build_cortical_atlas(nii_path, wb_txt_path, out_dir, include_list=None, exclude_list=None, atlasname='atlas'):
23
+ """
24
+ builds a custom yabplot cortical atlas from a volumetric NIfTI file.
25
+
26
+ projects a volumetric NIfTI atlas to standard fsLR32k surfaces using connectome workbench,
27
+ cleans the medial wall, and applies majority-vote boundary smoothing to remove voxel artifacts.
28
+
29
+ parameters
30
+ ----------
31
+ nii_path : str
32
+ absolute path to the 3D NIfTI volume of the atlas.
33
+ wb_txt_path : str
34
+ absolute path to the text file formatted specifically for connectome workbench.
35
+ out_dir : str
36
+ directory where the final .csv map and .txt LUT will be saved.
37
+ include_list : list of str, optional
38
+ keywords of regions to strictly include. all other regions are ignored.
39
+ exclude_list : list of str, optional
40
+ keywords of regions to strictly exclude. all other regions are kept.
41
+ atlasname : str, optional
42
+ prefix name for the output files. default is 'atlas'.
43
+
44
+ raises
45
+ ------
46
+ ValueError
47
+ if both include_list and exclude_list are provided.
48
+ """
49
+ if include_list and exclude_list:
50
+ raise ValueError("please provide either 'include_list' or 'exclude_list', not both.")
51
+
52
+ os.makedirs(out_dir, exist_ok=True)
53
+
54
+ # define intermediate and output paths
55
+ labeled_nii = os.path.join(out_dir, 'temp_labeled.nii.gz')
56
+ lh_gii = os.path.join(out_dir, 'lh_temp.label.gii')
57
+ rh_gii = os.path.join(out_dir, 'rh_temp.label.gii')
58
+ out_csv = os.path.join(out_dir, f'{atlasname}.csv')
59
+ out_lut = os.path.join(out_dir, f'{atlasname}.txt')
60
+
61
+ # fetch standard fsLR32k surfaces and masks via yabplot data system
62
+ print("fetching standard surfaces...")
63
+ lh_mid, rh_mid = get_surface_paths('midthickness', 'bmesh')
64
+ lh_white, rh_white = get_surface_paths('white', 'bmesh')
65
+ lh_pial, rh_pial = get_surface_paths('pial', 'bmesh')
66
+ lh_mask_path, rh_mask_path = get_surface_paths('nomedialwall', 'label')
67
+
68
+ # run wb_command wrappers
69
+ print("running volume-to-surface projection...")
70
+ run_wb_import(nii_path, wb_txt_path, labeled_nii)
71
+ run_wb_projection(labeled_nii, lh_mid, lh_gii, lh_white, lh_pial)
72
+ run_wb_projection(labeled_nii, rh_mid, rh_gii, rh_white, rh_pial)
73
+
74
+ # extract LUT and apply include/exclude filtering
75
+ labels_dict = nib.load(lh_gii).labeltable.get_labels_as_dict()
76
+ valid_ids = []
77
+ lut_dict = {}
78
+
79
+ for rid, name in labels_dict.items():
80
+ if rid == 0 or name == '???': continue
81
+
82
+ # filter logic
83
+ if include_list:
84
+ if not any(inc in name for inc in include_list):
85
+ continue
86
+ elif exclude_list:
87
+ if any(exc in name for exc in exclude_list):
88
+ continue
89
+
90
+ clean_name = name.replace(' ', '_').replace('/', '-')
91
+ np.random.seed(rid)
92
+ r, g, b = np.random.randint(50, 255, 3)
93
+
94
+ # store the string in the dictionary instead of writing to a file yet
95
+ lut_dict[rid] = f"{rid} {clean_name} {r} {g} {b} 0"
96
+ valid_ids.append(rid)
97
+
98
+ print(f"found {len(valid_ids)} initial cortical regions. mapping and cleaning...")
99
+
100
+ # merge LH and RH, then apply masks
101
+ data = np.concatenate([
102
+ nib.load(lh_gii).darrays[0].data.astype(int).flatten(),
103
+ nib.load(rh_gii).darrays[0].data.astype(int).flatten()
104
+ ])
105
+
106
+ mask = np.concatenate([
107
+ nib.load(lh_mask_path).darrays[0].data.astype(int).flatten() != 0,
108
+ nib.load(rh_mask_path).darrays[0].data.astype(int).flatten() != 0
109
+ ])
110
+
111
+ data[~mask] = 0
112
+ data[~np.isin(data, valid_ids)] = 0
113
+
114
+ # build adjacency and run hole-filling
115
+ print("building surface adjacency and filling holes...")
116
+ adj = sp.block_diag((_build_adjacency(lh_mid), _build_adjacency(rh_mid))).tocsr()
117
+ adj.setdiag(1)
118
+ n_vert = len(data)
119
+
120
+ for _ in range(20):
121
+ holes = (data == 0) & mask
122
+ if not np.any(holes): break
123
+
124
+ unique, inv = np.unique(data, return_inverse=True)
125
+ one_hot = sp.coo_matrix((np.ones(n_vert), (np.arange(n_vert), inv)), shape=(n_vert, len(unique))).tocsr()
126
+ votes = (adj @ one_hot).toarray()
127
+
128
+ zero_idx = np.where(unique == 0)[0][0]
129
+ votes[:, zero_idx] = 0
130
+
131
+ winner = np.argmax(votes, axis=1)
132
+ fill_vals = unique[winner]
133
+ data[holes] = fill_vals[holes]
134
+
135
+ # smooth final boundaries
136
+ print("smoothing boundaries...")
137
+ for _ in range(10):
138
+ unique, inv = np.unique(data, return_inverse=True)
139
+ one_hot = sp.coo_matrix((np.ones(n_vert), (np.arange(n_vert), inv)), shape=(n_vert, len(unique))).tocsr()
140
+ winner = np.argmax((adj @ one_hot).toarray(), axis=1)
141
+ data = unique[winner]
142
+ data[~mask] = 0
143
+
144
+ # save the final vertex map
145
+ np.savetxt(out_csv, data, fmt='%i')
146
+
147
+ # find out which regions actually survived the smoothing/masking
148
+ surviving_ids = np.unique(data)
149
+
150
+ # filter the LUT lines to only include survivors
151
+ final_lines = []
152
+ dropped_count = 0
153
+
154
+ for rid, line_str in lut_dict.items():
155
+ if rid in surviving_ids:
156
+ final_lines.append(line_str)
157
+ else:
158
+ region_name = line_str.split()[1]
159
+ print(f"[WARNING] {region_name} (id {rid}) lost during smoothing/masking. dropping from lut.")
160
+ dropped_count += 1
161
+
162
+ # write the perfectly clean file
163
+ with open(out_lut, 'w') as f:
164
+ f.write("\n".join(final_lines))
165
+
166
+ print(f"final polished atlas saved to: {out_dir}")
167
+ print(f"saved {len(final_lines)} regions ({dropped_count} empty regions dropped).")
168
+
169
+ # cleanup intermediate Workbench files to save space
170
+ for temp_file in [labeled_nii, lh_gii, rh_gii]:
171
+ if os.path.exists(temp_file):
172
+ os.remove(temp_file)
173
+
174
+ def qc_custom_cortical_atlas(atlas_dir, atlasname='atlas'):
175
+ """
176
+ generates a quality control report for a custom cortical atlas.
177
+
178
+ reads the generated vertex map and lookup table, counts the vertices for each region,
179
+ saves a summary text file, and generates individual static plots for every region
180
+ to help identify mapping dropouts or anatomical bleed.
181
+
182
+ parameters
183
+ ----------
184
+ atlas_dir : str
185
+ absolute path to the custom atlas directory containing the .csv and .txt files.
186
+ atlasname : str, optional
187
+ prefix name of the files to check. default is 'atlas'.
188
+ """
189
+
190
+ csv_path = os.path.join(atlas_dir, f"{atlasname}.csv")
191
+ lut_path = os.path.join(atlas_dir, f"{atlasname}.txt")
192
+ qc_dir = os.path.join(atlas_dir, "qc_report")
193
+
194
+ os.makedirs(qc_dir, exist_ok=True)
195
+
196
+ # load the mapped data and the lookup table
197
+ labels = np.loadtxt(csv_path).astype(int)
198
+
199
+ regions = {}
200
+ with open(lut_path, 'r') as f:
201
+ for line in f:
202
+ parts = line.strip().split()
203
+ if len(parts) >= 2:
204
+ regions[int(parts[0])] = parts[1]
205
+
206
+ print(f"starting qc for {len(regions)} regions...\n")
207
+
208
+ report_path = os.path.join(qc_dir, "_vertex_counts.txt")
209
+
210
+ with open(report_path, 'w') as f_out:
211
+ f_out.write("region_name\tid\tvertex_count\n")
212
+ f_out.write("-" * 40 + "\n")
213
+
214
+ for rid, name in regions.items():
215
+ count = np.sum(labels == rid)
216
+ f_out.write(f"{name}\t{rid}\t{count}\n")
217
+
218
+ print(f"[{name}] id: {rid} | vertices: {count}")
219
+
220
+ if count == 0:
221
+ print(f"[WARNING] {name} is empty! skipping plot.")
222
+ continue
223
+
224
+ plot_file = os.path.join(qc_dir, f"{rid:03d}_{name}.png")
225
+
226
+ try:
227
+ plot_cortical(
228
+ data={name: 1},
229
+ custom_atlas_path=atlas_dir,
230
+ cmap='binary',vminmax=[0, 1],
231
+ export_path=plot_file
232
+ )
233
+ except Exception as e:
234
+ print(f" -> failed to plot {name}: {e}")
235
+
236
+ print(f"\nqc complete! check the '{qc_dir}' folder for the report and images.")
237
+
238
+
239
+ ### SUBCORTICAL
240
+
241
+ def build_subcortical_atlas(nii_path, labels_dict, out_dir, include_list=None, exclude_list=None,
242
+ smooth_i=15, smooth_f=0.6):
243
+ """
244
+ extracts 3D subcortical meshes from a volumetric nifti atlas.
245
+
246
+ uses the marching cubes algorithm to generate 3D surface meshes for specific
247
+ regions, applies laplacian smoothing to remove voxel artifacts, and saves them as .vtk files.
248
+
249
+ parameters
250
+ ----------
251
+ nii_path : str
252
+ absolute path to the 3D nifti volume.
253
+ labels_dict : dict
254
+ dictionary mapping integer region IDs to string names (e.g., {1: 'thalamus_l'}).
255
+ out_dir : str
256
+ directory where the .vtk mesh files will be saved.
257
+ include_list : list of str, optional
258
+ keywords of regions to strictly include. all other regions are ignored.
259
+ exclude_list : list of str, optional
260
+ keywords of regions to strictly exclude. all other regions are kept.
261
+ smooth_i : int, optional
262
+ number of iterations for laplacian mesh smoothing. default is 15.
263
+ smooth_f : float, optional
264
+ relaxation factor for laplacian mesh smoothing (0.0 to 1.0). default is 0.6.
265
+
266
+ raises
267
+ ------
268
+ ValueError
269
+ if both include_list and exclude_list are provided.
270
+ """
271
+ if include_list and exclude_list:
272
+ raise ValueError("please provide either 'include_list' or 'exclude_list', not both.")
273
+
274
+ os.makedirs(out_dir, exist_ok=True)
275
+
276
+ # apply the include/exclude filters to the provided dictionary
277
+ targets = {}
278
+ for rid, name in labels_dict.items():
279
+ if include_list:
280
+ if not any(inc in name for inc in include_list):
281
+ continue
282
+ elif exclude_list:
283
+ if any(exc in name for exc in exclude_list):
284
+ continue
285
+
286
+ targets[rid] = name
287
+
288
+ print(f"filtered down to {len(targets)} subcortical regions to extract.")
289
+
290
+ # load the nifti volume and its affine matrix
291
+ img = nib.load(nii_path)
292
+ data = img.get_fdata()
293
+ affine = img.affine
294
+
295
+ # loop through targets, extract meshes, and save
296
+ for rid, name in targets.items():
297
+ # create a binary mask for just this region
298
+ mask = (data == rid).astype(np.uint8)
299
+
300
+ # skip if empty
301
+ if np.sum(mask) == 0:
302
+ print(f"[WARNING] {name} is empty in the volume!")
303
+ continue
304
+
305
+ print(f"extracting: {name} (id {rid})...")
306
+
307
+ # run marching cubes to get raw vertices and faces
308
+ verts, faces, normals, values = measure.marching_cubes(mask, level=0.5)
309
+
310
+ # apply the nifti affine matrix cleanly using nibabel
311
+ verts_mni = nib.affines.apply_affine(affine, verts)
312
+
313
+ # format faces for pyvista: [n_points, p1, p2, p3, n_points, p1, p2, p3...]
314
+ faces_pv = np.column_stack((np.full(len(faces), 3), faces)).flatten()
315
+
316
+ # create the 3d pyvista mesh
317
+ mesh = pv.PolyData(verts_mni, faces_pv)
318
+
319
+ # apply laplacian smoothing to melt away the blocky voxel edges
320
+ mesh = mesh.smooth(n_iter=smooth_i, relaxation_factor=smooth_f)
321
+ mesh.compute_normals(inplace=True)
322
+
323
+ # we remove super small structures which would not be visible
324
+ if mesh.n_points < 4 or abs(mesh.volume) < 0.01:
325
+ print(f"[WARNING] {name} is too small to form a 3D mesh (volume: {abs(mesh.volume):.4f} mm³). dropping from atlas.")
326
+ continue
327
+
328
+ # save as a vtk file
329
+ out_file = os.path.join(out_dir, f"{name}.vtk")
330
+ mesh.save(out_file)
331
+
332
+ print(f"\nsubcortical atlas successfully saved to: {out_dir}")
333
+
334
+ def qc_custom_subcortical_atlas(atlas_dir):
335
+ """
336
+ generates a quality control report for a custom subcortical atlas.
337
+
338
+ reads the generated .vtk meshes, calculates their geometric properties
339
+ (vertices, faces, volume), saves a summary text file, and generates
340
+ individual static plots for every region to help identify corrupt meshes
341
+ or anatomical artifacts.
342
+
343
+ parameters
344
+ ----------
345
+ atlas_dir : str
346
+ absolute path to the custom atlas directory containing the .vtk files.
347
+ """
348
+
349
+ qc_dir = os.path.join(atlas_dir, "qc_report")
350
+ os.makedirs(qc_dir, exist_ok=True)
351
+
352
+ # find all vtk files in the atlas directory
353
+ vtk_files = glob.glob(os.path.join(atlas_dir, "*.vtk"))
354
+
355
+ if not vtk_files:
356
+ print(f"no .vtk files found in {atlas_dir}. cannot run qc.")
357
+ return
358
+
359
+ print(f"starting qc for {len(vtk_files)} subcortical meshes...\n")
360
+
361
+ report_path = os.path.join(qc_dir, "_mesh_properties.txt")
362
+
363
+ with open(report_path, 'w') as f_out:
364
+ # header for our text report
365
+ f_out.write("region_name\tvertices\tfaces\tvolume_mm3\n")
366
+ f_out.write("-" * 55 + "\n")
367
+
368
+ for vtk_path in sorted(vtk_files):
369
+ filename = os.path.basename(vtk_path)
370
+ region_name = os.path.splitext(filename)[0]
371
+
372
+ # read mesh to extract physical properties
373
+ try:
374
+ mesh = pv.read(vtk_path)
375
+ n_verts = mesh.n_points
376
+ n_faces = mesh.n_cells
377
+ volume = mesh.volume
378
+ except Exception as e:
379
+ print(f" -> error reading {filename}: {e}")
380
+ f_out.write(f"{region_name}\tERROR\tERROR\tERROR\n")
381
+ continue
382
+
383
+ f_out.write(f"{region_name}\t{n_verts}\t{n_faces}\t{volume:.2f}\n")
384
+ print(f"[{region_name}] vertices: {n_verts} | volume: {volume:.1f} mm³")
385
+
386
+ # check for empty or severely corrupted meshes
387
+ if n_verts == 0:
388
+ print(f"[WARNING] {region_name} mesh is empty! skipping plot.")
389
+ continue
390
+
391
+ plot_file = os.path.join(qc_dir, f"{region_name}.png")
392
+
393
+ try:
394
+ plot_subcortical(
395
+ data={region_name: 1},
396
+ custom_atlas_path=atlas_dir,
397
+ cmap='binary', vminmax=[0, 1],
398
+ nan_alpha=0.2,
399
+ export_path=plot_file
400
+ )
401
+ except Exception as e:
402
+ print(f" -> failed to plot {region_name}: {e}")
403
+
404
+ print(f"\nqc complete! check the '{qc_dir}' folder for the report and images.")
@@ -70,6 +70,52 @@ def get_available_resources(category=None):
70
70
 
71
71
  return all_resources
72
72
 
73
+ def get_surface_paths(name, category):
74
+ """
75
+ Fetches and returns the paths to the Left and Right hemisphere files
76
+ for a given surface resource (meshes or labels).
77
+
78
+ Parameters
79
+ ----------
80
+ name : str
81
+ Name of the resource (e.g., 'midthickness', 'nomedialwall').
82
+ category : str
83
+ Must be 'bmesh' or 'label'.
84
+
85
+ Returns
86
+ -------
87
+ tuple
88
+ (lh_path, rh_path) containing absolute paths to the files.
89
+ """
90
+ if category not in ['bmesh', 'label']:
91
+ raise ValueError("Category must be 'bmesh' or 'label' to fetch surface paths.")
92
+
93
+ # Download/unpack the zip and get the folder path
94
+ directory = _resolve_resource_path(name, category)
95
+
96
+ lh_path = None
97
+ rh_path = None
98
+
99
+ # Traverse the unzipped directory to find L and R files
100
+ for root, dirs, files in os.walk(directory):
101
+ # Ignore hidden folders like .git or __MACOSX
102
+ dirs[:] = [d for d in dirs if not d.startswith(('.', '__'))]
103
+ for file in files:
104
+ # Ignore hidden files
105
+ if file.startswith('.'):
106
+ continue
107
+
108
+ # Robust checking for Left and Right hemisphere indicators
109
+ if '.L.' in file or '_L_' in file or 'hemi-L' in file:
110
+ lh_path = os.path.join(root, file)
111
+ elif '.R.' in file or '_R_' in file or 'hemi-R' in file:
112
+ rh_path = os.path.join(root, file)
113
+
114
+ if not lh_path or not rh_path:
115
+ raise FileNotFoundError(f"Could not locate both Left and Right hemisphere files for '{name}' in {directory}")
116
+
117
+ return lh_path, rh_path
118
+
73
119
  def get_atlas_regions(atlas, category, custom_atlas_path=None):
74
120
  """
75
121
  Returns the list of region names for a given atlas in the specific order
@@ -191,7 +237,8 @@ def _resolve_resource_path(name, category, custom_path=None):
191
237
  'cortical': 'Cortical parcellations (vertices)',
192
238
  'subcortical': 'Subcortical segmentations (volumes)',
193
239
  'tracts': 'White matter bundles (tracts)',
194
- 'bmesh': 'Brain meshes'
240
+ 'bmesh': 'Brain meshes',
241
+ 'label': 'Surface labels'
195
242
  }.get(category, category)
196
243
 
197
244
  raise ValueError(
@@ -1,15 +1,18 @@
1
1
  cortical-aparc.zip sha256:9e3e4853c580b6ed7c70a2b398b8ddacfc30e05600f04f722d3b5e8ee28ba119 https://osf.io/5btcf/download
2
- cortical-brainnetome.zip sha256:ecace554a79118534ae95bbaef517a6140e282374a95a5ee1f112794c00bd9e4 https://osf.io/xspn5/download
2
+ cortical-brainnetome.zip sha256:fc4bb0043f2efa133771240f9e7178521be4011e76e5cad8102c9c099fce5ef0 https://osf.io/xspn5/download
3
3
  cortical-schaefer_100.zip sha256:041c229b900c86f6b80cc44a05dca74bcfccde9aad5d10878da04a184d0f26ba https://osf.io/yxt5p/download
4
4
  cortical-schaefer_200.zip sha256:065ee05afa1881a8884bdecee25a062ddd5fd6283bfeaabb59e6eb696f39563f https://osf.io/v45gq/download
5
5
  cortical-schaefer_300.zip sha256:5430c66eaef58f008b9e20dd5c59c670c7f7154b02f4b310dda97250331b6eed https://osf.io/9djtr/download
6
6
  cortical-schaefer_400.zip sha256:ae2b7011919d49e9930ee5aed1b19731180427f07f0d1c4497130dd9cd5e878d https://osf.io/pzmc5/download
7
7
  cortical-schaefer_1000.zip sha256:a10448f101a874499d41bc62508c3af29120634955f48beb282ece9a4cc2bb0e https://osf.io/j9ygz/download
8
+ cortical-aal3.zip sha256:925e475e099b127925e20e6ec4d179b1a58971f2bc32b2a61f56a479f3e8d4fb https://osf.io/z9jxh/download
8
9
  subcortical-aseg.zip sha256:a901a7fc6a39f9bdaf2ef2bafbcde1fbc085b225d99e6730c727650d9be047d5 https://osf.io/5cs7y/download
9
10
  subcortical-brainnetome_sc.zip sha256:8301fdf6af109af52a2cf9b06d15486345d457b69cb86169ed38332a34a681c0 https://osf.io/2fsg5/download
10
11
  subcortical-jhu.zip sha256:b0ca292589a9f041851dba8159bb605a4657323e4305ba1570a304944b28d0de https://osf.io/x5fhg/download
11
12
  subcortical-musus100_dbn.zip sha256:1d865832a35570a8c67f79d5049b58c548d25cf4fe1853164c384193b6712e40 https://osf.io/eutmb/download
12
13
  subcortical-tian2020_sc.zip sha256:8b5caf8bf0cdcf8e259a3532fbcb232f8746d68fc734ca9806d10e70cf8707a3 https://osf.io/jrvgp/download
14
+ subcortical-aal3.zip sha256:48abe400656d913cc459ee8766e373cc5eabbeb9e4f770aa7396da3d4a0ab3ca https://osf.io/39ebh/download
15
+ subcortical-aal3_nocer.zip sha256:b87de55861cdd18ddb78934964b0e0855f770560bdc878570fcbb8f27fefb674 https://osf.io/7jdxz/download
13
16
  tracts-hcp1065_medium.zip sha256:366bb100074cf7b1e55586e5468653f0c342e42cfdd91dca6d99b6fde82f06ff https://osf.io/kjf8e/download
14
17
  tracts-hcp1065_small.zip sha256:020f23059c3a20ee0dda8ad12cd3df99b9fe5d3b37e7a6b9ae34b8a52b4b4346 https://osf.io/ynpa5/download
15
18
  tracts-hcp1065_tiny.zip sha256:54380d82f5cd234029c6fc011910e00f0ad859fdb6c22c6aaa6452d055449ae9 https://osf.io/jzk7p/download
@@ -22,4 +25,5 @@ bmesh-midthickness.zip sha256:ef8209853e1dac8804a60b5cde0b653ef2f6c77b6c7536f7ac
22
25
  bmesh-pial.zip sha256:5e36ba7883dd2a88485fc45c9166ed48e22db607d45655242961242b51f9f126 https://osf.io/knpg8/download
23
26
  bmesh-swm.zip sha256:1a516c070cb63557751d841d5cf75772660872b89ec749ee19c23298d77fa807 https://osf.io/hpbc9/download
24
27
  bmesh-very_inflated.zip sha256:8c33a00c718a47f0af118ce2c5804a2adf13d79be05c4665ca26582e3e8dd977 https://osf.io/xp9jr/download
25
- bmesh-white.zip sha256:b885ac1a4dcdf9e241a97e8d3ca59d7b1c86d8a3ebe17df512993bc5c1c0354a https://osf.io/wfc5t/download
28
+ bmesh-white.zip sha256:b885ac1a4dcdf9e241a97e8d3ca59d7b1c86d8a3ebe17df512993bc5c1c0354a https://osf.io/wfc5t/download
29
+ label-nomedialwall.zip sha256:03bd589b151814a1fc5e48236a78decf1a51791f04c3f4a521d2ed4fb214317b https://osf.io/2rgmc/download
@@ -0,0 +1,36 @@
1
+ import subprocess
2
+ import shutil
3
+ import os
4
+
5
+ def check_workbench():
6
+ """
7
+ Checks if Connectome Workbench (wb_command) is installed and available in PATH.
8
+
9
+ Raises
10
+ ------
11
+ EnvironmentError
12
+ If wb_command is not found, providing instructions for installation.
13
+ """
14
+ if shutil.which('wb_command') is None:
15
+ raise EnvironmentError(
16
+ "Connectome Workbench ('wb_command') was not found in your system PATH.\n"
17
+ "This is required for volume-to-surface projection (necessary for creating a custom cortical atlas).\n"
18
+ "Please download it from: https://humanconnectome.org/software/get-connectome-workbench\n"
19
+ "After installing, ensure the 'bin' folder is added to your PATH environment variable."
20
+ )
21
+
22
+ def run_wb_import(input_nii, label_list, output_nii):
23
+ """Wrapper for wb_command -volume-label-import"""
24
+ check_workbench()
25
+ cmd = ["wb_command", "-volume-label-import", input_nii, label_list, output_nii]
26
+ subprocess.run(cmd, check=True)
27
+
28
+ def run_wb_projection(input_nii, midthickness, output_gii, white, pial):
29
+ """Wrapper for wb_command -volume-label-to-surface-mapping (ribbon-constrained)"""
30
+ check_workbench()
31
+ cmd = [
32
+ "wb_command", "-volume-label-to-surface-mapping",
33
+ input_nii, midthickness, output_gii,
34
+ "-ribbon-constrained", white, pial
35
+ ]
36
+ subprocess.run(cmd, check=True)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: yabplot
3
- Version: 0.1.5
3
+ Version: 0.2.2
4
4
  Summary: yet another brain plot
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -55,6 +55,9 @@ pip install yabplot --upgrade # to update
55
55
 
56
56
  dependencies: python 3.11 with ipywidgets, nibabel, pandas, pooch, pyvista, scikit-image, trame, trame-vtk, trame-vuetify
57
57
 
58
+ (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)
59
+
60
+
58
61
  ## quick start
59
62
 
60
63
  please refer to the [documentation](https://teanijarv.github.io/yabplot/) for more comprehensive guides.
@@ -4,9 +4,11 @@ README.md
4
4
  pyproject.toml
5
5
  tests/test_smoke.py
6
6
  yabplot/__init__.py
7
+ yabplot/atlas_builder.py
7
8
  yabplot/plotting.py
8
9
  yabplot/scene.py
9
10
  yabplot/utils.py
11
+ yabplot/wrappers.py
10
12
  yabplot.egg-info/PKG-INFO
11
13
  yabplot.egg-info/SOURCES.txt
12
14
  yabplot.egg-info/dependency_links.txt
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes