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.
- pyNIBS-0.2024.8.dist-info/LICENSE +623 -0
- pyNIBS-0.2024.8.dist-info/METADATA +723 -0
- pyNIBS-0.2024.8.dist-info/RECORD +107 -0
- pyNIBS-0.2024.8.dist-info/WHEEL +5 -0
- pyNIBS-0.2024.8.dist-info/top_level.txt +1 -0
- pynibs/__init__.py +34 -0
- pynibs/coil.py +1367 -0
- pynibs/congruence/__init__.py +15 -0
- pynibs/congruence/congruence.py +1108 -0
- pynibs/congruence/ext_metrics.py +257 -0
- pynibs/congruence/stimulation_threshold.py +318 -0
- pynibs/data/configuration_exp0.yaml +59 -0
- pynibs/data/configuration_linear_MEP.yaml +61 -0
- pynibs/data/configuration_linear_RT.yaml +61 -0
- pynibs/data/configuration_sigmoid4.yaml +68 -0
- pynibs/data/network mapping configuration/configuration guide.md +238 -0
- pynibs/data/network mapping configuration/configuration_TEMPLATE.yaml +42 -0
- pynibs/data/network mapping configuration/configuration_for_testing.yaml +43 -0
- pynibs/data/network mapping configuration/configuration_modelTMS.yaml +43 -0
- pynibs/data/network mapping configuration/configuration_reg_isi_05.yaml +43 -0
- pynibs/data/network mapping configuration/output_documentation.md +185 -0
- pynibs/data/network mapping configuration/recommendations_for_accuracy_threshold.md +77 -0
- pynibs/data/neuron/models/L23_PC_cADpyr_biphasic_v1.csv +1281 -0
- pynibs/data/neuron/models/L23_PC_cADpyr_monophasic_v1.csv +1281 -0
- pynibs/data/neuron/models/L4_LBC_biphasic_v1.csv +1281 -0
- pynibs/data/neuron/models/L4_LBC_monophasic_v1.csv +1281 -0
- pynibs/data/neuron/models/L4_NBC_biphasic_v1.csv +1281 -0
- pynibs/data/neuron/models/L4_NBC_monophasic_v1.csv +1281 -0
- pynibs/data/neuron/models/L4_SBC_biphasic_v1.csv +1281 -0
- pynibs/data/neuron/models/L4_SBC_monophasic_v1.csv +1281 -0
- pynibs/data/neuron/models/L5_TTPC2_cADpyr_biphasic_v1.csv +1281 -0
- pynibs/data/neuron/models/L5_TTPC2_cADpyr_monophasic_v1.csv +1281 -0
- pynibs/expio/Mep.py +1518 -0
- pynibs/expio/__init__.py +8 -0
- pynibs/expio/brainsight.py +979 -0
- pynibs/expio/brainvis.py +71 -0
- pynibs/expio/cobot.py +239 -0
- pynibs/expio/exp.py +1876 -0
- pynibs/expio/fit_funs.py +287 -0
- pynibs/expio/localite.py +1987 -0
- pynibs/expio/signal_ced.py +51 -0
- pynibs/expio/visor.py +624 -0
- pynibs/freesurfer.py +502 -0
- pynibs/hdf5_io/__init__.py +10 -0
- pynibs/hdf5_io/hdf5_io.py +1857 -0
- pynibs/hdf5_io/xdmf.py +1542 -0
- pynibs/mesh/__init__.py +3 -0
- pynibs/mesh/mesh_struct.py +1394 -0
- pynibs/mesh/transformations.py +866 -0
- pynibs/mesh/utils.py +1103 -0
- pynibs/models/_TMS.py +211 -0
- pynibs/models/__init__.py +0 -0
- pynibs/muap.py +392 -0
- pynibs/neuron/__init__.py +2 -0
- pynibs/neuron/neuron_regression.py +284 -0
- pynibs/neuron/util.py +58 -0
- pynibs/optimization/__init__.py +5 -0
- pynibs/optimization/multichannel.py +278 -0
- pynibs/optimization/opt_mep.py +152 -0
- pynibs/optimization/optimization.py +1445 -0
- pynibs/optimization/workhorses.py +698 -0
- pynibs/pckg/__init__.py +0 -0
- pynibs/pckg/biosig/biosig4c++-1.9.5.src_fixed.tar.gz +0 -0
- pynibs/pckg/libeep/__init__.py +0 -0
- pynibs/pckg/libeep/pyeep.so +0 -0
- pynibs/regression/__init__.py +11 -0
- pynibs/regression/dual_node_detection.py +2375 -0
- pynibs/regression/regression.py +2984 -0
- pynibs/regression/score_types.py +0 -0
- pynibs/roi/__init__.py +2 -0
- pynibs/roi/roi.py +895 -0
- pynibs/roi/roi_structs.py +1233 -0
- pynibs/subject.py +1009 -0
- pynibs/tensor_scaling.py +144 -0
- pynibs/tests/data/InstrumentMarker20200225163611937.xml +19 -0
- pynibs/tests/data/TriggerMarkers_Coil0_20200225163443682.xml +14 -0
- pynibs/tests/data/TriggerMarkers_Coil1_20200225170337572.xml +6373 -0
- pynibs/tests/data/Xdmf.dtd +89 -0
- pynibs/tests/data/brainsight_niiImage_nifticoord.txt +145 -0
- pynibs/tests/data/brainsight_niiImage_nifticoord_largefile.txt +1434 -0
- pynibs/tests/data/brainsight_niiImage_niifticoord_mixedtargets.txt +47 -0
- pynibs/tests/data/create_subject_testsub.py +332 -0
- pynibs/tests/data/data.hdf5 +0 -0
- pynibs/tests/data/geo.hdf5 +0 -0
- pynibs/tests/test_coil.py +474 -0
- pynibs/tests/test_elements2nodes.py +100 -0
- pynibs/tests/test_hdf5_io/test_xdmf.py +61 -0
- pynibs/tests/test_mesh_transformations.py +123 -0
- pynibs/tests/test_mesh_utils.py +143 -0
- pynibs/tests/test_nnav_imports.py +101 -0
- pynibs/tests/test_quality_measures.py +117 -0
- pynibs/tests/test_regressdata.py +289 -0
- pynibs/tests/test_roi.py +17 -0
- pynibs/tests/test_rotations.py +86 -0
- pynibs/tests/test_subject.py +71 -0
- pynibs/tests/test_util.py +24 -0
- pynibs/tms_pulse.py +34 -0
- pynibs/util/__init__.py +4 -0
- pynibs/util/dosing.py +233 -0
- pynibs/util/quality_measures.py +562 -0
- pynibs/util/rotations.py +340 -0
- pynibs/util/simnibs.py +763 -0
- pynibs/util/util.py +727 -0
- pynibs/visualization/__init__.py +2 -0
- pynibs/visualization/para.py +4372 -0
- pynibs/visualization/plot_2D.py +137 -0
- 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 *
|