pyNIBS 0.2024.8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. pyNIBS-0.2024.8.dist-info/LICENSE +623 -0
  2. pyNIBS-0.2024.8.dist-info/METADATA +723 -0
  3. pyNIBS-0.2024.8.dist-info/RECORD +107 -0
  4. pyNIBS-0.2024.8.dist-info/WHEEL +5 -0
  5. pyNIBS-0.2024.8.dist-info/top_level.txt +1 -0
  6. pynibs/__init__.py +34 -0
  7. pynibs/coil.py +1367 -0
  8. pynibs/congruence/__init__.py +15 -0
  9. pynibs/congruence/congruence.py +1108 -0
  10. pynibs/congruence/ext_metrics.py +257 -0
  11. pynibs/congruence/stimulation_threshold.py +318 -0
  12. pynibs/data/configuration_exp0.yaml +59 -0
  13. pynibs/data/configuration_linear_MEP.yaml +61 -0
  14. pynibs/data/configuration_linear_RT.yaml +61 -0
  15. pynibs/data/configuration_sigmoid4.yaml +68 -0
  16. pynibs/data/network mapping configuration/configuration guide.md +238 -0
  17. pynibs/data/network mapping configuration/configuration_TEMPLATE.yaml +42 -0
  18. pynibs/data/network mapping configuration/configuration_for_testing.yaml +43 -0
  19. pynibs/data/network mapping configuration/configuration_modelTMS.yaml +43 -0
  20. pynibs/data/network mapping configuration/configuration_reg_isi_05.yaml +43 -0
  21. pynibs/data/network mapping configuration/output_documentation.md +185 -0
  22. pynibs/data/network mapping configuration/recommendations_for_accuracy_threshold.md +77 -0
  23. pynibs/data/neuron/models/L23_PC_cADpyr_biphasic_v1.csv +1281 -0
  24. pynibs/data/neuron/models/L23_PC_cADpyr_monophasic_v1.csv +1281 -0
  25. pynibs/data/neuron/models/L4_LBC_biphasic_v1.csv +1281 -0
  26. pynibs/data/neuron/models/L4_LBC_monophasic_v1.csv +1281 -0
  27. pynibs/data/neuron/models/L4_NBC_biphasic_v1.csv +1281 -0
  28. pynibs/data/neuron/models/L4_NBC_monophasic_v1.csv +1281 -0
  29. pynibs/data/neuron/models/L4_SBC_biphasic_v1.csv +1281 -0
  30. pynibs/data/neuron/models/L4_SBC_monophasic_v1.csv +1281 -0
  31. pynibs/data/neuron/models/L5_TTPC2_cADpyr_biphasic_v1.csv +1281 -0
  32. pynibs/data/neuron/models/L5_TTPC2_cADpyr_monophasic_v1.csv +1281 -0
  33. pynibs/expio/Mep.py +1518 -0
  34. pynibs/expio/__init__.py +8 -0
  35. pynibs/expio/brainsight.py +979 -0
  36. pynibs/expio/brainvis.py +71 -0
  37. pynibs/expio/cobot.py +239 -0
  38. pynibs/expio/exp.py +1876 -0
  39. pynibs/expio/fit_funs.py +287 -0
  40. pynibs/expio/localite.py +1987 -0
  41. pynibs/expio/signal_ced.py +51 -0
  42. pynibs/expio/visor.py +624 -0
  43. pynibs/freesurfer.py +502 -0
  44. pynibs/hdf5_io/__init__.py +10 -0
  45. pynibs/hdf5_io/hdf5_io.py +1857 -0
  46. pynibs/hdf5_io/xdmf.py +1542 -0
  47. pynibs/mesh/__init__.py +3 -0
  48. pynibs/mesh/mesh_struct.py +1394 -0
  49. pynibs/mesh/transformations.py +866 -0
  50. pynibs/mesh/utils.py +1103 -0
  51. pynibs/models/_TMS.py +211 -0
  52. pynibs/models/__init__.py +0 -0
  53. pynibs/muap.py +392 -0
  54. pynibs/neuron/__init__.py +2 -0
  55. pynibs/neuron/neuron_regression.py +284 -0
  56. pynibs/neuron/util.py +58 -0
  57. pynibs/optimization/__init__.py +5 -0
  58. pynibs/optimization/multichannel.py +278 -0
  59. pynibs/optimization/opt_mep.py +152 -0
  60. pynibs/optimization/optimization.py +1445 -0
  61. pynibs/optimization/workhorses.py +698 -0
  62. pynibs/pckg/__init__.py +0 -0
  63. pynibs/pckg/biosig/biosig4c++-1.9.5.src_fixed.tar.gz +0 -0
  64. pynibs/pckg/libeep/__init__.py +0 -0
  65. pynibs/pckg/libeep/pyeep.so +0 -0
  66. pynibs/regression/__init__.py +11 -0
  67. pynibs/regression/dual_node_detection.py +2375 -0
  68. pynibs/regression/regression.py +2984 -0
  69. pynibs/regression/score_types.py +0 -0
  70. pynibs/roi/__init__.py +2 -0
  71. pynibs/roi/roi.py +895 -0
  72. pynibs/roi/roi_structs.py +1233 -0
  73. pynibs/subject.py +1009 -0
  74. pynibs/tensor_scaling.py +144 -0
  75. pynibs/tests/data/InstrumentMarker20200225163611937.xml +19 -0
  76. pynibs/tests/data/TriggerMarkers_Coil0_20200225163443682.xml +14 -0
  77. pynibs/tests/data/TriggerMarkers_Coil1_20200225170337572.xml +6373 -0
  78. pynibs/tests/data/Xdmf.dtd +89 -0
  79. pynibs/tests/data/brainsight_niiImage_nifticoord.txt +145 -0
  80. pynibs/tests/data/brainsight_niiImage_nifticoord_largefile.txt +1434 -0
  81. pynibs/tests/data/brainsight_niiImage_niifticoord_mixedtargets.txt +47 -0
  82. pynibs/tests/data/create_subject_testsub.py +332 -0
  83. pynibs/tests/data/data.hdf5 +0 -0
  84. pynibs/tests/data/geo.hdf5 +0 -0
  85. pynibs/tests/test_coil.py +474 -0
  86. pynibs/tests/test_elements2nodes.py +100 -0
  87. pynibs/tests/test_hdf5_io/test_xdmf.py +61 -0
  88. pynibs/tests/test_mesh_transformations.py +123 -0
  89. pynibs/tests/test_mesh_utils.py +143 -0
  90. pynibs/tests/test_nnav_imports.py +101 -0
  91. pynibs/tests/test_quality_measures.py +117 -0
  92. pynibs/tests/test_regressdata.py +289 -0
  93. pynibs/tests/test_roi.py +17 -0
  94. pynibs/tests/test_rotations.py +86 -0
  95. pynibs/tests/test_subject.py +71 -0
  96. pynibs/tests/test_util.py +24 -0
  97. pynibs/tms_pulse.py +34 -0
  98. pynibs/util/__init__.py +4 -0
  99. pynibs/util/dosing.py +233 -0
  100. pynibs/util/quality_measures.py +562 -0
  101. pynibs/util/rotations.py +340 -0
  102. pynibs/util/simnibs.py +763 -0
  103. pynibs/util/util.py +727 -0
  104. pynibs/visualization/__init__.py +2 -0
  105. pynibs/visualization/para.py +4372 -0
  106. pynibs/visualization/plot_2D.py +137 -0
  107. pynibs/visualization/render_3D.py +347 -0
pynibs/freesurfer.py ADDED
@@ -0,0 +1,502 @@
1
+ """
2
+ This holds methods to interact with FreeSurfer ([1]_), for example to translate FreeSurfer files into Paraview readable
3
+ .vtk files.
4
+
5
+ References
6
+ ----------
7
+ .. [1] Dale, A.M., Fischl, B., Sereno, M.I., 1999. Cortical surface-based analysis. I. Segmentation and surface
8
+ reconstruction. Neuroimage 9, 179-194.
9
+ """
10
+ import os
11
+ import h5py
12
+ import shutil
13
+ import meshio
14
+ import nibabel
15
+ import platform
16
+ import subprocess
17
+ import numpy as np
18
+ import nibabel as nib
19
+ import pynibs
20
+
21
+
22
+ def read_curv_data(fname_curv, fname_inf, raw=False):
23
+ """
24
+ Read curvature data provided by FreeSurfer with optional normalization.
25
+
26
+ Parameters
27
+ ----------
28
+ fname_curv : str
29
+ Filename of the FreeSurfer curvature file (e.g. ?h.curv), contains curvature data in nodes
30
+ can be found in mri2mesh proband folder: ``proband_ID/fs_ID/surf/?h.curv``.
31
+ fname_inf : str
32
+ Filename of inflated brain surface (e.g. ``?h.inflated``), contains points and connectivity data of surface
33
+ can be found in mri2mesh proband folder: ``proband_ID/fs_ID/surf/?h.inflated``.
34
+ raw : bool
35
+ If raw-data is returned or if the data is normalized to -1 for neg. and +1 for pos. curvature.
36
+
37
+ Returns
38
+ -------
39
+ curv : np.ndarray of float or int
40
+ Curvature data in element centers.
41
+ """
42
+
43
+ # load curvature data
44
+ curv_points = nibabel.freesurfer.io.read_morph_data(fname_curv)
45
+
46
+ # load inflated brain surface data
47
+ points_inf, con_inf = nibabel.freesurfer.read_geometry(fname_inf)
48
+
49
+ # interpolate point data to element centers
50
+ curv = pynibs.data_nodes2elements(curv_points, con_inf)
51
+
52
+ # normalize curvature data, optional
53
+ if not raw:
54
+ curv[curv > 0] = 1
55
+ curv[curv < 0] = -1
56
+
57
+ return curv
58
+
59
+
60
+ def make_group_average(subjects=None, subject_dir=None, average=None, hemi="lh", template="mytemplate", steps=3,
61
+ n_cpu=2, average_dir=None):
62
+ """
63
+ Creates a group average from scratch, based on one subject. This prevents for example the fsaverage problems of
64
+ large elements at M1, etc.
65
+ This is an implemntation of [2]_ 'Creating a registration template from scratch (GW)'.
66
+
67
+ References
68
+ ----------
69
+ .. [2] https://surfer.nmr.mgh.harvard.edu/fswiki/SurfaceRegAndTemplates
70
+
71
+ Parameters
72
+ ----------
73
+ subjects : list of str
74
+ List of FreeSurfer subjects names.
75
+ subject_dir : str
76
+ Temporary subject directory of FreeSurfer (symlinks of subjects will be generated in there and average
77
+ template will be temporarily stored before it is copied to ``average_dir``).
78
+ average: str, default: ``subjects[0]``
79
+ Which subject to base new average template on.
80
+ hemi : str, default: 'lh'
81
+ Which hemisphere: ``lh`` or ``rh``.
82
+
83
+ .. deprecated:: 0.0.1
84
+ Don't use any more.
85
+ template : str, default: 'mytemplate'
86
+ Basename of new template.
87
+ steps : int, default: 2
88
+ Number of iterations.
89
+ n_cpu : int, default: 4
90
+ How many cores for multithreading.
91
+ average_dir : str
92
+ Path to directory where average template will be stored,
93
+ e.g. ``probands/avg_template_15/mesh/0/fs_avg_template_15``.
94
+
95
+ Returns
96
+ -------
97
+ <File> : .tif file
98
+ ``SUBJECT_DIR/TEMPLATE*.tif``, TEMPLATE0.tif based on AVERAGE, rest on all subjects.
99
+ <File> : .myreg file
100
+ ``SUBJECT_DIR/SUBJECT*/surf/HEMI.sphere.myreg*``.
101
+ <File> : .tif file
102
+ Subject wise sphere registration based on ``TEMPLATE*.tif``.
103
+ """
104
+ subject_names = [os.path.split(subjects[i])[1] for i in range(len(subjects))]
105
+
106
+ # define $SUBJECT_DIR of FREESURFER
107
+ if not os.path.exists(subject_dir):
108
+ print(('Creating $SUBJECTS_DIR in {}'.format(subject_dir)))
109
+ os.makedirs(subject_dir)
110
+ print(('Set environment variable of FreeSurfer SUBJECTS_DIR={}'.format(subject_dir)))
111
+ os.putenv('SUBJECTS_DIR', subject_dir)
112
+
113
+ # make symlinks of subjects in $SUBJECT_DIR
114
+ for i, subject in enumerate(subjects):
115
+ if not os.path.exists(os.path.join(subject_dir, subject_names[i])):
116
+ print(('Generating symlink of subject {} in {}'.format(subject_names[i], subject_dir)))
117
+ pynibs.bash('ln -s ' + subject + ' ' + subject_dir)
118
+
119
+ os.chdir(subject_dir)
120
+
121
+ if not average:
122
+ average = subjects[0]
123
+
124
+ print("Creating initial template")
125
+ "mris_make_template lh sphere fs_15484.08 ./templates.tif"
126
+
127
+ command = "mris_make_template {} sphere {} lh{}0.tif".format("lh", os.path.split(average)[1], template)
128
+ os.system(command)
129
+ command = "mris_make_template {} sphere {} rh{}0.tif".format("rh", os.path.split(average)[1], template)
130
+ os.system(command)
131
+
132
+ processes = set()
133
+ for i in range(steps):
134
+ command = "mris_register"
135
+ print("\n\nStep {}".format(i))
136
+ print("############################################################################################")
137
+ # start all subjects registrations to .tif* in parallel
138
+ for subject in subjects:
139
+ # print "{}/surf/{}.sphere {}{}.tif {}.sphere.myreg{}".format(subject, hemi, template, i, hemi, i)
140
+ # print [command, "{}/surf/{}.sphere {}{}.tif {}.sphere.myreg{}".format(subject,
141
+ # hemi, template, i, hemi, i)]
142
+ processes.add(subprocess.Popen(
143
+ [f'{command} '
144
+ f'{os.path.split(subject)[1]}{os.sep}surf{os.sep}lh.sphere lh{template}{i}.tif '
145
+ f'{"lh"}.sphere.{template}{i}'],
146
+ shell=True))
147
+ processes.add(subprocess.Popen(
148
+ [f"{command} "
149
+ f"{os.path.split(subject)[1]}{os.sep}surf{os.sep}rh.sphere rh{template}{i}.tif "
150
+ f"{'rh'}.sphere.{template}{i}"],
151
+ shell=True))
152
+
153
+ # check if n_cpu processes are reached
154
+ if len(processes) >= n_cpu:
155
+ os.wait()
156
+ processes.difference_update([p for p in processes if p.poll() is not None])
157
+
158
+ while len(processes) > 0:
159
+ os.wait()
160
+ processes.difference_update([p for p in processes if p.poll() is not None])
161
+
162
+ short_subjects = [os.path.split(subject)[1] for subject in subjects]
163
+ print(" making template for step {}".format(i))
164
+ command = f"mris_make_template {'rh'} sphere.{template}{i} {' '.join(short_subjects)} " \
165
+ f"rh{template}{i + 1}.tif"
166
+ print(command)
167
+ os.system(command)
168
+
169
+ command = f"mris_make_template {'lh'} sphere.{template}{i} {' '.join(short_subjects)} " \
170
+ f"lh{template}{i + 1}.tif"
171
+ print(command)
172
+ os.system(command)
173
+
174
+ print("##################### Iteration {} done. #####################\n".format(i))
175
+
176
+ print('Creating average surfaces and volumes of average subject')
177
+ make_average_subject(subjects=subjects,
178
+ subject_dir=subject_dir,
179
+ average_dir=average_dir,
180
+ fn_reg='sphere.{}{}'.format(template, steps - 1))
181
+
182
+
183
+ def make_average_subject(subjects, subject_dir, average_dir, fn_reg='sphere.reg'):
184
+ """
185
+ Generates the average template from a list of subjects using the FreeSurfer average.
186
+
187
+ Parameters
188
+ ----------
189
+ subjects : list of str
190
+ Paths of subjects directories, where the FreeSurfer files are located,
191
+ e.g. for simnibs mri2mesh ``.../fs_SUBJECT_ID``.
192
+ subject_dir : str
193
+ Temporary subject directory of FreeSurfer (symlinks of subjects will be generated in there and average
194
+ template will be temporarily stored before it is copied to ``average_dir``).
195
+ average_dir : str
196
+ path to directory where average template will be stored,
197
+ e.g. probands/avg_template_15/mesh/0/fs_avg_template_15.
198
+ fn_reg : str, default: 'sphere.reg' --> ?h.sphere.reg>
199
+ Filename suffix of FreeSurfer registration file containing registration information to template.
200
+
201
+ Returns
202
+ -------
203
+ <Files> : .tif and .reg files
204
+ Average template in average_dir and registered curvature files, ``?h.sphere.reg`` in subjects/surf folders.
205
+ """
206
+
207
+ subject_names = [os.path.split(subjects[i])[1] for i in range(len(subjects))]
208
+
209
+ # check for right operating system
210
+ if platform.system() != 'Linux':
211
+ raise OSError('Average template can only be generated with linux because of FreeSurfer requirements.')
212
+
213
+ # define $SUBJECT_DIR of FREESURFER
214
+ if not os.path.exists(subject_dir):
215
+ print(('Creating $SUBJECTS_DIR in {}'.format(subject_dir)))
216
+ os.makedirs(subject_dir)
217
+ print(('Set environment variable of FreeSurfer SUBJECTS_DIR={}'.format(subject_dir)))
218
+ os.putenv('SUBJECTS_DIR', subject_dir)
219
+
220
+ # make symlinks of subjects in $SUBJECT_DIR
221
+ for i, subject in enumerate(subjects):
222
+ if not os.path.exists(os.path.join(subject_dir, subject_names[i])):
223
+ print(('Generating symlink of subject {} in {}'.format(subject_names[i], subject_dir)))
224
+ pynibs.bash('ln -s ' + subject + ' ' + subject_dir)
225
+
226
+ # make average template
227
+ if os.path.exists(f'{subject_dir}{os.sep}avg_template'):
228
+ shutil.rmtree(f'{subject_dir}{os.sep}avg_template')
229
+ print(('Generating average subject in {}/avg_template'.format(subject_dir)))
230
+ print('==============================================')
231
+ command = 'make_average_subject ' \
232
+ '--out avg_template ' \
233
+ ' --subjects ' + " ".join(subject_names) + \
234
+ ' --surf-reg ' + fn_reg
235
+
236
+ os.system(command)
237
+
238
+ # copy average template to average_dir
239
+ print(('Moving average template to {}'.format(average_dir)))
240
+ os.system('mv $SUBJECTS_DIR/avg_template ' + average_dir)
241
+
242
+
243
+ def data_sub2avg(fn_subject_obj, fn_average_obj, hemisphere, fn_in_hdf5_data, data_hdf5_path, data_label,
244
+ fn_out_hdf5_geo, fn_out_hdf5_data, mesh_idx=0, roi_idx=0, subject_data_in_center=True,
245
+ data_substitute=-1, verbose=True, replace=True, reg_fn="sphere.reg"):
246
+ """
247
+ Maps the data from the subject space to the average template. If the data is given only in an ROI, the data is
248
+ mapped to the whole brain surface.
249
+
250
+ Parameters
251
+ ----------
252
+ fn_subject_obj : str
253
+ Filename of subject object .hdf5 file (incl. path),
254
+ e.g. ``.../probands/subjectID/subjectID.hdf5``
255
+ fn_average_obj : str
256
+ Filename of average template object .pkl file (incl. path),
257
+ e.g. ``.../probands/avg_template/avg_template.hdf5``.
258
+ hemisphere : str
259
+ Define hemisphere to work on (``'lh'`` or ``'rh'`` for left or right hemisphere, respectively).
260
+ fn_in_hdf5_data : str
261
+ Filename of .hdf5 data input file containing the subject data.
262
+ data_hdf5_path : str
263
+ Path in .hdf5 data file where data is stored (e.g. ``'/data/tris/'``).
264
+ data_label : str or list of str
265
+ Label of datasets contained in hdf5 input file to map.
266
+ fn_out_hdf5_geo : str
267
+ Filename of .hdf5 geo output file containing the geometry information.
268
+ fn_out_hdf5_data : str
269
+ Filename of .hdf5 data output file containing the mapped data.
270
+ mesh_idx : int
271
+ Index of mesh used in the simulations.
272
+ roi_idx : int
273
+ Index of region of interest used in the simulations.
274
+ subject_data_in_center : bool, default: True
275
+ Specify if the data is given in the center of the triangles or in the nodes.
276
+ data_substitute : float
277
+ Data substitute with this number for all points outside the ROI mask
278
+ verbose : bool
279
+ Verbose output (Default: True)
280
+ replace : bool
281
+ Replace output files (Default: True)
282
+ reg_fn : str
283
+ Sphere.reg fn
284
+
285
+ Returns
286
+ -------
287
+ <Files> : .hdf5 files
288
+ Geometry and corresponding data files to plot with Paraview:
289
+
290
+ * fn_out_hdf5_geo.hdf5: geometry file containing the geometry information of the average template
291
+ * fn_out_hdf5_data.hdf5: geometry file containing the data
292
+ """
293
+
294
+ subject = pynibs.load_subject(fname=fn_subject_obj)
295
+ average = pynibs.load_subject(fname=fn_average_obj)
296
+
297
+ mesh_folder_avg = average.mesh["0"]["mesh_folder"]
298
+ mesh_folder_sub = subject.mesh[mesh_idx]["mesh_folder"]
299
+
300
+ fn_sub_gm = subject.mesh[mesh_idx]['fn_' + hemisphere + '_gm']
301
+ fn_sub_wm = subject.mesh[mesh_idx]['fn_' + hemisphere + '_wm']
302
+ fn_avg_gm = average.mesh[mesh_idx]['fn_' + hemisphere + '_gm']
303
+ fn_avg_wm = average.mesh[mesh_idx]['fn_' + hemisphere + '_wm']
304
+
305
+ # freesurfer folder, e.g.: /data/pt_01756/probands/subject[i].id + '/mesh/0/fs_' + subject[i].id
306
+ subject_dir = os.path.split(os.path.split(fn_sub_gm)[0])[0]
307
+ average_dir = os.path.split(os.path.split(fn_avg_gm)[0])[0]
308
+
309
+ # filename of mapped subject data in nodes in .curv format for freesurfer (will be generated)
310
+ fn_data_sub_nodes_mapped_curv = [os.path.join(
311
+ os.path.split(fn_out_hdf5_geo)[0],
312
+ hemisphere + '.' +
313
+ os.path.split(subject_dir)[1] +
314
+ '_' +
315
+ data_label[i] +
316
+ '.curv')
317
+ for i in range(len(data_label))]
318
+
319
+ # filename of mapped subject data in nodes on average template in .curv format for freesurfer (will be generated)
320
+ fn_data_sub_nodes_mapped_avg_curv = [os.path.join(
321
+ os.path.split(fn_data_sub_nodes_mapped_curv[i])[0],
322
+ os.path.splitext(os.path.split(fn_data_sub_nodes_mapped_curv[i])[1])[0] +
323
+ '_avg.curv')
324
+ for i in range(len(data_label))]
325
+
326
+ if verbose:
327
+ print('> Generating GM/WM surface on average template')
328
+ points_avg, con_avg = pynibs.make_GM_WM_surface(gm_surf_fname=fn_avg_gm,
329
+ wm_surf_fname=fn_avg_wm,
330
+ mesh_folder=mesh_folder_avg,
331
+ delta=subject.roi[mesh_idx][roi_idx]['delta'],
332
+ layer=1)
333
+ files_not_exist = not np.array([os.path.exists(fn_data_sub_nodes_mapped_avg_curv[i]) for
334
+ i in range(len(data_label))]).all()
335
+
336
+ if files_not_exist or replace:
337
+
338
+ # Generate WM/GM surfaces of subject and average template
339
+ if verbose:
340
+ print(("> Reading ROI #{} of subject ...".format(roi_idx)))
341
+
342
+ with h5py.File(subject.mesh[mesh_idx]['fn_mesh_hdf5'], 'r') as f:
343
+ points_roi = np.array(f['roi_surface/' + str(roi_idx) + '/node_coord_mid'])
344
+ con_roi = np.array(f['roi_surface/' + str(roi_idx) + '/node_number_list'])
345
+
346
+ # Read subject data
347
+ if verbose:
348
+ print(('> Reading subject data: {}'.format(data_label)))
349
+ data_sub = [h5py.File(fn_in_hdf5_data, 'r')[data_hdf5_path + data_label[i]][:] for i in range(len(data_label))]
350
+
351
+ # Transform data when given in element centers to nodes
352
+ if subject_data_in_center:
353
+ if verbose:
354
+ print('> Transforming subject data from element centers to nodes')
355
+ # data_sub_nodes = [pynibs.data_elements2nodes(data_sub[i], con_ROI) for i in range(len(data_label))]
356
+ data_sub_nodes = pynibs.data_elements2nodes(data_sub, con_roi)
357
+
358
+ else:
359
+ data_sub_nodes = data_sub
360
+
361
+ del data_sub
362
+
363
+ # map data given in nodes to brain surface since this is needed for mapping the subject data to the avg template
364
+ # (subject space)
365
+ if subject.roi[mesh_idx][roi_idx]['fn_mask']:
366
+ if verbose:
367
+ print('> Mapping point data of ROI to whole brain surface')
368
+ data_sub_nodes_mapped = pynibs.map_data_to_surface(datasets=data_sub_nodes,
369
+ points_datasets=[points_roi] * len(data_sub_nodes),
370
+ con_datasets=[con_roi] * len(data_sub_nodes),
371
+ fname_fsl_gm=os.path.join(mesh_folder_sub, fn_sub_gm),
372
+ fname_fsl_wm=os.path.join(mesh_folder_sub, fn_sub_wm),
373
+ delta=subject.roi[mesh_idx][roi_idx]['delta'],
374
+ input_data_in_center=False,
375
+ return_data_in_center=False,
376
+ data_substitute=data_substitute)
377
+ else:
378
+ data_sub_nodes_mapped = data_sub_nodes
379
+
380
+ del data_sub_nodes
381
+
382
+ # Saving mapped subject data in nodes to .curv format for freesurfer (subject space)
383
+ if verbose:
384
+ print('> Saving mapped subject data in nodes in .curv format for FreeSurfer')
385
+ for i in range(len(data_label)):
386
+ nibabel.freesurfer.io.write_morph_data(fn_data_sub_nodes_mapped_curv[i], data_sub_nodes_mapped[i])
387
+
388
+ # Set $SUBJECT_DIR of freesurfer
389
+ if verbose:
390
+ print(('> Set environment variable of FreeSurfer SUBJECTS_DIR={}'.format(subject_dir)))
391
+ os.putenv('SUBJECTS_DIR', os.path.split(subject_dir)[0])
392
+
393
+ # Make temporary symlink of average_dir in subject_dir
394
+ if verbose:
395
+ print("> Generating temporary symlink from average template to $SUBJECTS_DIR")
396
+ fn_avg_template_symlink_dest = os.path.join(os.path.split(subject_dir)[0], os.path.split(average_dir)[1])
397
+ if os.path.exists(fn_avg_template_symlink_dest):
398
+ os.system('rm -f ' + fn_avg_template_symlink_dest)
399
+ os.system('ln -s ' + average_dir + ' ' + fn_avg_template_symlink_dest)
400
+
401
+ # map data on it using FreeSurfer
402
+ if verbose:
403
+ print('> Mapping subject data to average template using FreeSurfer mri_surf2surf')
404
+ for i in range(len(data_label)):
405
+ fs_cmd = "mri_surf2surf --srcsubject " + os.path.split(subject_dir)[1] + \
406
+ " --trgsubject " + os.path.split(average_dir)[1] + \
407
+ " --srcsurfval " + fn_data_sub_nodes_mapped_curv[i] + \
408
+ " --trgsurfval " + fn_data_sub_nodes_mapped_avg_curv[i] + \
409
+ " --hemi " + hemisphere + \
410
+ " --src_type curv " + \
411
+ " --trg_type curv " + \
412
+ " --surfreg " + reg_fn
413
+
414
+ os.system(fs_cmd)
415
+
416
+ else:
417
+ print(('> Mapped *_avg.curv files found in {}'.format(os.path.split(fn_data_sub_nodes_mapped_avg_curv[0])[0])))
418
+
419
+ # read mapped data
420
+ data_avg_nodes_mapped = [0] * len(data_label)
421
+ if verbose:
422
+ print('> Reading mapped data from .curv files')
423
+ for i in range(len(data_label)):
424
+ data_avg_nodes_mapped[i] = nibabel.freesurfer.read_morph_data(fn_data_sub_nodes_mapped_avg_curv[i])
425
+
426
+ # transform data from nodes to centers
427
+ data_avg_centers_mapped = [0] * len(data_label)
428
+ if verbose:
429
+ print('> Transforming mapped nodal data to element centers')
430
+ for i in range(len(data_label)):
431
+ data_avg_centers_mapped[i] = pynibs.data_nodes2elements(data_avg_nodes_mapped[i], con_avg)[:, np.newaxis]
432
+
433
+ # create .hdf5 geometry file
434
+ if verbose:
435
+ print('> Creating .hdf5 geometry file of mapped data')
436
+ pynibs.write_geo_hdf5_surf(out_fn=fn_out_hdf5_geo, points=points_avg, con=con_avg, replace=True, hdf5_path='/mesh')
437
+
438
+ # create .hdf5 data file
439
+ if verbose:
440
+ print('> Creating .hdf5 data files')
441
+ pynibs.write_data_hdf5_surf(data=data_avg_centers_mapped,
442
+ data_names=data_label,
443
+ data_hdf_fn_out=fn_out_hdf5_data,
444
+ geo_hdf_fn=fn_out_hdf5_geo,
445
+ replace=True)
446
+
447
+ if verbose:
448
+ print('> Done.\n')
449
+
450
+
451
+ def freesurfer2vtk(in_fns, out_folder, hem='lh', surf='pial', prefix=None, fs_subject='fsaverage',
452
+ fs_subjects_dir=None):
453
+ """
454
+ Transform multiple FreeSurfer .mgh files into one .vtk file.
455
+ This can be read with Paraview and others.
456
+
457
+ Parameters
458
+ ----------
459
+ in_fns : list of str or str
460
+ Input filenames.
461
+ out_folder : str
462
+ Output folder.
463
+ hem : str, default: 'lh'
464
+ Which hemisphere: ``'lh'`` or ``'rh'``.
465
+ surf : str, default: 'pial'
466
+ Which FreeSurfer surface: ``'pial'``, ``'inflated'``, ...
467
+ prefix : str, optional
468
+ Prefix to add to each filename.
469
+ fs_subject : str, default: 'fsaverage'
470
+ FreeSurfer subject.
471
+ fs_subjects_dir : str, optional
472
+ FreeSurfer subjects directory. If not provided, read from environment.
473
+
474
+ Returns
475
+ -------
476
+ <File>: out_folder/{prefix}_{hem}_{surf}.vtk
477
+ One .vtk file with data as overlays from all .mgh files provided
478
+ """
479
+ if not isinstance(in_fns, list):
480
+ in_fns = [in_fns]
481
+
482
+ if prefix is not None and not prefix.endswith('_'):
483
+ prefix += '_'
484
+
485
+ if fs_subjects_dir is None:
486
+ try:
487
+ fs_subjects_dir = os.environ['SUBJECTS_DIR']
488
+ except KeyError:
489
+ print(f"Freesurfer not loaded and 'fs_subjects_dir' not provided.")
490
+
491
+ output_fn = f"{out_folder}/{prefix}{hem}_{surf}.vtk"
492
+
493
+ # create a dict with fn as key, data as value. keys will be vtk data array names
494
+ point_data = {}
495
+ for fn in in_fns:
496
+ point_data[os.path.split(fn)[1].replace('.mgh', '')] = nib.load(fn).get_fdata()
497
+
498
+ # read FreeSurfer surface mesh
499
+ coords, faces = nib.freesurfer.read_geometry(f"{fs_subjects_dir}/{fs_subject}/surf/{hem}.{surf}")
500
+
501
+ # write all dataarrays + mesh geometry into one .vtk
502
+ meshio.Mesh(np.squeeze(coords), [('triangle', faces)], point_data=point_data).write(output_fn)
@@ -0,0 +1,10 @@
1
+ """
2
+ The hdf5_io subpackage provides utilities for reading and writing data in the HDF5 format,
3
+ as well as generating XDMF files for visualization of the data. It includes functions for
4
+ writing surface data, creating XDMF files for surfaces and fibers, overlaying data stored
5
+ in HDF5 files, and writing coordinates to an XDMF file for visualization. This subpackage
6
+ is primarily used for handling and visualizing data related to neuroimaging and brain
7
+ stimulation studies.
8
+ """
9
+ from .hdf5_io import *
10
+ from .xdmf import *