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/util/dosing.py
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Functions used in our perspective on e-field based TMS dosing[1]_
|
|
3
|
+
|
|
4
|
+
References
|
|
5
|
+
----------
|
|
6
|
+
.. [1] Numssen, O., Kuhnke, P., Weise, K., & Hartwigsen, G. (2024).
|
|
7
|
+
Electric-field-based dosing for TMS. Imaging Neuroscience, 2, 1-12.
|
|
8
|
+
DOI: 10.1162/imag_a_00106
|
|
9
|
+
"""
|
|
10
|
+
import os
|
|
11
|
+
import h5py
|
|
12
|
+
import numpy as np
|
|
13
|
+
import pynibs
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_intensity_e(e1, e2, target1, target2, radius1, radius2, headmesh,
|
|
17
|
+
rmt=1, roi='midlayer_lh_rh', verbose=False):
|
|
18
|
+
"""
|
|
19
|
+
Computes the stimulator intensity adjustment factor based on the electric field.
|
|
20
|
+
|
|
21
|
+
Parameters
|
|
22
|
+
----------
|
|
23
|
+
e1 : str
|
|
24
|
+
.hdf5 e field with midlayer.
|
|
25
|
+
e2 : str
|
|
26
|
+
.hdf5 e field with midlayer.
|
|
27
|
+
target1 : np.ndarray (3,)
|
|
28
|
+
Coordinates of cortical site of MT.
|
|
29
|
+
target2 : np.ndarray (3,)
|
|
30
|
+
Coordinates of cortical target site.
|
|
31
|
+
radius1 : float
|
|
32
|
+
Electric field of field1 is averaged over elements inside this radius around target1.
|
|
33
|
+
radius2 : float
|
|
34
|
+
Electric field of field2 is averaged over elements inside this radius around target2.
|
|
35
|
+
headmesh : str
|
|
36
|
+
.hdf5 headmesh.
|
|
37
|
+
rmt : float, default: 1
|
|
38
|
+
Resting motor threshold to be corrected.
|
|
39
|
+
roi : str, default: 'midlayer_lh_rh'
|
|
40
|
+
Name of roi. Expected to sit in ``mesh['/data/midlayer/roi_surface/']``.
|
|
41
|
+
verbose : bool, default: False
|
|
42
|
+
Flag indicating verbosity.
|
|
43
|
+
|
|
44
|
+
Returns
|
|
45
|
+
-------
|
|
46
|
+
rmt_e_corr : float
|
|
47
|
+
Adjusted stimulation intensity for target2.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
with h5py.File(headmesh, 'r') as f:
|
|
51
|
+
tris = f[f'/roi_surface/{roi}/tri_center_coord_mid'][:]
|
|
52
|
+
|
|
53
|
+
idx, e_avg_target, e_target, t_idx_sphere = [], [], [], []
|
|
54
|
+
for field, target, radius in zip([e1, e2], [target1, target2], [radius1, radius2]):
|
|
55
|
+
idx.append(np.argmin(np.linalg.norm(tris - target, axis=1)))
|
|
56
|
+
t_idx_sphere.append(np.where(np.linalg.norm(tris - tris[idx[-1]], axis=1) < radius)[0])
|
|
57
|
+
with h5py.File(field, 'r') as e:
|
|
58
|
+
e_avg_target.append(np.mean(e[f'/data/midlayer/roi_surface/{roi}/E_mag'][t_idx_sphere[-1]]))
|
|
59
|
+
e_target.append(e[f'/data/midlayer/roi_surface/{roi}/E_mag'][idx[-1]])
|
|
60
|
+
|
|
61
|
+
# determine scaling factor
|
|
62
|
+
e_fac_avg = e_avg_target[0] / e_avg_target[1]
|
|
63
|
+
e_fac = e_target[0] / e_target[1]
|
|
64
|
+
rmt_e_corr = rmt * e_fac_avg
|
|
65
|
+
|
|
66
|
+
if verbose:
|
|
67
|
+
print(f"Target1: {target1}->{tris[idx[0]]}. E: {e_target[0]:2.4f}, {len(t_idx_sphere[0])} elms")
|
|
68
|
+
print(f"Target2: {target2}->{tris[idx[1]]}. E: {e_target[1]:2.4f}, {len(t_idx_sphere[0])} elms")
|
|
69
|
+
print(f"Efield normalization factor: {e_fac_avg:2.4f} ({e_fac:2.4f} for single elm).")
|
|
70
|
+
# print(f"Center: {target} { tris_center[t_idx, ]}.")
|
|
71
|
+
print(f"Given intensity {rmt}% is normalized to {rmt * e_fac_avg:2.4f}%.")
|
|
72
|
+
|
|
73
|
+
return rmt_e_corr
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def get_intensity_e_old(mesh1, mesh2, target1, target2, radius1, radius2, rmt=1, verbose=False):
|
|
77
|
+
"""
|
|
78
|
+
Computes the stimulator intensity adjustment factor based on the electric field.
|
|
79
|
+
Something weird is going on here - check simnibs coordinates of midlayer before usage.
|
|
80
|
+
|
|
81
|
+
Parameters
|
|
82
|
+
----------
|
|
83
|
+
mesh1 : str or simnibs.msh.mesh_io.Msh
|
|
84
|
+
Midlayer mesh containing results of the optimal coil position of MT in the midlayer
|
|
85
|
+
(e.g.: .../subject_overlays/00001.hd_fixed_TMS_1-0001_MagVenture_MCF_B65_REF_highres.ccd_scalar_central.msh)
|
|
86
|
+
mesh2 : str or simnibs.msh.mesh_io.Msh
|
|
87
|
+
Midlayer mesh containing results of the optimal coil position of the target in the midlayer
|
|
88
|
+
(e.g.: .../subject_overlays/00001.hd_fixed_TMS_1-0001_MagVenture_MCF_B65_REF_highres.ccd_scalar_central.msh)
|
|
89
|
+
target1 : np.ndarray
|
|
90
|
+
(3,) Coordinates of cortical site of MT.
|
|
91
|
+
target2 : np.ndarray
|
|
92
|
+
(3,) Coordinates of cortical target site.
|
|
93
|
+
radius1 : float
|
|
94
|
+
Electric field in target 1 is averaged over elements inside this radius.
|
|
95
|
+
radius2 : float
|
|
96
|
+
Electric field in target 2 is averaged over elements inside this radius.
|
|
97
|
+
rmt : float, default: 1
|
|
98
|
+
Resting motor threshold, which will be corrected.
|
|
99
|
+
verbose : bool, default: False
|
|
100
|
+
Flag indicating verbosity.
|
|
101
|
+
|
|
102
|
+
Returns
|
|
103
|
+
-------
|
|
104
|
+
rmt_e_corr : float
|
|
105
|
+
Adjusted stimulation intensity for target2.
|
|
106
|
+
"""
|
|
107
|
+
from simnibs.msh.mesh_io import read_msh
|
|
108
|
+
|
|
109
|
+
# load mesh1 (MT) if filename is provided
|
|
110
|
+
if isinstance(mesh1, str):
|
|
111
|
+
if os.path.splitext(mesh1)[1] == ".msh":
|
|
112
|
+
mesh1 = read_msh(mesh1)
|
|
113
|
+
elif os.path.splitext(mesh1)[1] == ".hdf5":
|
|
114
|
+
mesh1 = pynibs.load_mesh_hdf5(mesh1)
|
|
115
|
+
|
|
116
|
+
# load mesh2 (target) if filename is provided
|
|
117
|
+
if isinstance(mesh2, str):
|
|
118
|
+
if os.path.splitext(mesh2)[1] == ".msh":
|
|
119
|
+
mesh2 = read_msh(mesh2)
|
|
120
|
+
elif os.path.splitext(mesh2)[1] == ".hdf5":
|
|
121
|
+
mesh2 = pynibs.load_mesh_hdf5(mesh2)
|
|
122
|
+
|
|
123
|
+
# load electric fields in midlayer and average electric field around sphere in targets
|
|
124
|
+
e_avg_target = []
|
|
125
|
+
for mesh, target, radius in zip([mesh1, mesh2], [target1, target2], [radius1, radius2]):
|
|
126
|
+
nodes = mesh.nodes.node_coord
|
|
127
|
+
tris = mesh.elm.node_number_list[:, :-1] - 1
|
|
128
|
+
tris_center = np.mean(nodes[tris,], axis=1)
|
|
129
|
+
|
|
130
|
+
e_norm_nodes = None
|
|
131
|
+
for nodedata in mesh.nodedata:
|
|
132
|
+
if nodedata.field_name == "E_norm":
|
|
133
|
+
e_norm_nodes = nodedata.value
|
|
134
|
+
|
|
135
|
+
e_norm_tris = np.mean(e_norm_nodes[tris], axis=1)
|
|
136
|
+
|
|
137
|
+
# project targets to midlayer
|
|
138
|
+
t_idx = np.argmin(np.linalg.norm(tris_center - target, axis=1))
|
|
139
|
+
|
|
140
|
+
# get indices of surrounding elements in some radius
|
|
141
|
+
t_idx_sphere = np.where(np.linalg.norm(tris_center - tris_center[t_idx,], axis=1) < radius)[0]
|
|
142
|
+
|
|
143
|
+
# average e-field in this area
|
|
144
|
+
e_avg_target.append(np.mean(e_norm_tris[t_idx_sphere]))
|
|
145
|
+
|
|
146
|
+
print(f"Center: {target} {tris_center[t_idx,]}.")
|
|
147
|
+
|
|
148
|
+
# determine scaling factor
|
|
149
|
+
e_fac = e_avg_target[0] / e_avg_target[1]
|
|
150
|
+
rmt_e_corr = rmt * e_fac
|
|
151
|
+
|
|
152
|
+
if verbose:
|
|
153
|
+
print(f"Efield normalized factor is: {e_fac:2.4f}.")
|
|
154
|
+
# print(f"Center: {target} { tris_center[t_idx, ]}.")
|
|
155
|
+
print(f"Given stimulatior intensity {rmt}% is normalized to new intensity {rmt * e_fac:2.4f}%.")
|
|
156
|
+
|
|
157
|
+
return rmt_e_corr
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def get_intensity_stokes(mesh, target1, target2, spat_grad=3, rmt=0, scalp_tag=1005, roi=None, verbose=False):
|
|
161
|
+
"""
|
|
162
|
+
Computes the stimulator intensity adjustment factor according to Stokes et al. 2005
|
|
163
|
+
(doi:10.1152/jn.00067.2005).
|
|
164
|
+
Adjustment is based on target-scalp distance differences:
|
|
165
|
+
adj = (Dist2-Dist1)*spat_grad
|
|
166
|
+
|
|
167
|
+
Parameters
|
|
168
|
+
----------
|
|
169
|
+
mesh : str or simnibs.msh.mesh_io.Msh
|
|
170
|
+
Mesh of the head model.
|
|
171
|
+
target1 : np.ndarray
|
|
172
|
+
(3,) Coordinates of cortical site of MT.
|
|
173
|
+
target2 : np.ndarray
|
|
174
|
+
(3,) Coordinates of cortical target site.
|
|
175
|
+
spat_grad : float, default: 3
|
|
176
|
+
Spatial gradient.
|
|
177
|
+
rmt : float, default: 0
|
|
178
|
+
Resting motor threshold, which will be corrected.
|
|
179
|
+
scalp_tag: int, default: 1005
|
|
180
|
+
Tag in the mesh where the scalp is to be set.
|
|
181
|
+
roi: np.ndarray, optional
|
|
182
|
+
(3,N) Array of nodes to project targets onto.
|
|
183
|
+
verbose : bool, default: False
|
|
184
|
+
Print verbosity information.
|
|
185
|
+
|
|
186
|
+
Returns
|
|
187
|
+
-------
|
|
188
|
+
rmt_stokes : float
|
|
189
|
+
Adjusted stimulation intensity for target2.
|
|
190
|
+
"""
|
|
191
|
+
from simnibs.msh.mesh_io import read_msh
|
|
192
|
+
from pynibs.mesh import project_on_scalp
|
|
193
|
+
|
|
194
|
+
# load mesh if filename is provided
|
|
195
|
+
if isinstance(mesh, str):
|
|
196
|
+
if os.path.splitext(mesh)[1] == ".msh":
|
|
197
|
+
mesh = read_msh(mesh)
|
|
198
|
+
elif os.path.splitext(mesh)[1] == ".hdf5":
|
|
199
|
+
mesh = pynibs.load_mesh_hdf5(mesh)
|
|
200
|
+
|
|
201
|
+
t1_proj = project_on_scalp(target1, mesh, scalp_tag=scalp_tag)
|
|
202
|
+
t2_proj = project_on_scalp(target2, mesh, scalp_tag=scalp_tag)
|
|
203
|
+
|
|
204
|
+
if roi is not None:
|
|
205
|
+
t1_idx = np.argmin(np.linalg.norm(roi - target1, axis=1))
|
|
206
|
+
t2_idx = np.argmin(np.linalg.norm(roi - target2, axis=1))
|
|
207
|
+
t1_on_roi = roi[t1_idx]
|
|
208
|
+
t2_on_roi = roi[t2_idx]
|
|
209
|
+
|
|
210
|
+
if verbose:
|
|
211
|
+
print("Projecting targets on ROI:\n"
|
|
212
|
+
"T1: [{0:+06.2f}, {1:+06.2f}, {2:+06.2f}] -> [{3:+06.2f}, {4:+06.2f}, {5:+06.2f}] Dist: {6:05.2f}mm"
|
|
213
|
+
"\n".format(*target1, *t1_on_roi, np.linalg.norm(target1 - t1_on_roi)) + \
|
|
214
|
+
"T2: [{0:+06.2f}, {1:+06.2f}, {2:+06.2f}] -> [{3:+06.2f}, {4:+06.2f}, {5:+06.2f}] Dist: {6:05.2f}mm"
|
|
215
|
+
"".format(*target2, *t2_on_roi, np.linalg.norm(target2 - t2_on_roi)))
|
|
216
|
+
target1 = t1_on_roi
|
|
217
|
+
target2 = t2_on_roi
|
|
218
|
+
|
|
219
|
+
t1_dist = np.linalg.norm(target1 - t1_proj)
|
|
220
|
+
t2_dist = np.linalg.norm(target2 - t2_proj)
|
|
221
|
+
|
|
222
|
+
stokes_factor = (t2_dist - t1_dist) * spat_grad
|
|
223
|
+
rmt_stokes = rmt + stokes_factor
|
|
224
|
+
|
|
225
|
+
if verbose:
|
|
226
|
+
print("Target 1: [{0:+06.2f}, {1:+06.2f}, {2:+06.2f}] ->"
|
|
227
|
+
" [{3:+06.2f}, {4:+06.2f}, {5:+06.2f}] Dist: {6:05.2f}mm ".format(*target1, *t1_proj.flatten(), t1_dist))
|
|
228
|
+
print("Target 2: [{0:+06.2f}, {1:+06.2f}, {2:+06.2f}] ->"
|
|
229
|
+
" [{3:+06.2f}, {4:+06.2f}, {5:+06.2f}] Dist: {6:05.2f}mm ".format(*target2, *t2_proj.flatten(), t2_dist))
|
|
230
|
+
print(f"Dist1 - Dist2: {t1_dist - t2_dist:05.2f} mm")
|
|
231
|
+
print(f"rMT Stokes corrected: {rmt_stokes:05.2f} %MSO")
|
|
232
|
+
|
|
233
|
+
return rmt_stokes
|