pyNIBS 0.2024.8__py3-none-any.whl → 0.2026.1__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/__init__.py +26 -14
- pynibs/coil/__init__.py +6 -0
- pynibs/{coil.py → coil/coil.py} +213 -543
- pynibs/coil/export.py +508 -0
- pynibs/congruence/__init__.py +4 -1
- pynibs/congruence/congruence.py +37 -45
- pynibs/congruence/ext_metrics.py +40 -11
- pynibs/congruence/stimulation_threshold.py +1 -2
- pynibs/expio/Mep.py +120 -370
- pynibs/expio/__init__.py +10 -0
- pynibs/expio/brainsight.py +34 -37
- pynibs/expio/cobot.py +25 -25
- pynibs/expio/exp.py +10 -7
- pynibs/expio/fit_funs.py +3 -0
- pynibs/expio/invesalius.py +70 -0
- pynibs/expio/localite.py +190 -91
- pynibs/expio/neurone.py +139 -0
- pynibs/expio/signal_ced.py +345 -2
- pynibs/expio/visor.py +16 -15
- pynibs/freesurfer.py +34 -33
- pynibs/hdf5_io/hdf5_io.py +149 -132
- pynibs/hdf5_io/xdmf.py +35 -31
- pynibs/mesh/__init__.py +1 -1
- pynibs/mesh/mesh_struct.py +77 -92
- pynibs/mesh/transformations.py +121 -21
- pynibs/mesh/utils.py +191 -99
- pynibs/models/_TMS.py +2 -1
- pynibs/muap.py +1 -2
- pynibs/neuron/__init__.py +10 -0
- pynibs/neuron/models/mep.py +566 -0
- pynibs/neuron/neuron_regression.py +98 -8
- pynibs/optimization/__init__.py +12 -2
- pynibs/optimization/{optimization.py → coil_opt.py} +157 -133
- pynibs/optimization/multichannel.py +1174 -24
- pynibs/optimization/workhorses.py +7 -8
- pynibs/regression/__init__.py +4 -2
- pynibs/regression/dual_node_detection.py +229 -219
- pynibs/regression/regression.py +92 -61
- pynibs/roi/__init__.py +4 -1
- pynibs/roi/roi_structs.py +19 -21
- pynibs/roi/{roi.py → roi_utils.py} +56 -33
- pynibs/subject.py +24 -14
- pynibs/util/__init__.py +20 -4
- pynibs/util/dosing.py +4 -5
- pynibs/util/quality_measures.py +39 -38
- pynibs/util/rotations.py +116 -9
- pynibs/util/{simnibs.py → simnibs_io.py} +29 -19
- pynibs/util/{util.py → utils.py} +20 -22
- pynibs/visualization/para.py +4 -4
- pynibs/visualization/render_3D.py +4 -4
- pynibs-0.2026.1.dist-info/METADATA +105 -0
- pynibs-0.2026.1.dist-info/RECORD +69 -0
- {pyNIBS-0.2024.8.dist-info → pynibs-0.2026.1.dist-info}/WHEEL +1 -1
- pyNIBS-0.2024.8.dist-info/METADATA +0 -723
- pyNIBS-0.2024.8.dist-info/RECORD +0 -107
- pynibs/data/configuration_exp0.yaml +0 -59
- pynibs/data/configuration_linear_MEP.yaml +0 -61
- pynibs/data/configuration_linear_RT.yaml +0 -61
- pynibs/data/configuration_sigmoid4.yaml +0 -68
- pynibs/data/network mapping configuration/configuration guide.md +0 -238
- pynibs/data/network mapping configuration/configuration_TEMPLATE.yaml +0 -42
- pynibs/data/network mapping configuration/configuration_for_testing.yaml +0 -43
- pynibs/data/network mapping configuration/configuration_modelTMS.yaml +0 -43
- pynibs/data/network mapping configuration/configuration_reg_isi_05.yaml +0 -43
- pynibs/data/network mapping configuration/output_documentation.md +0 -185
- pynibs/data/network mapping configuration/recommendations_for_accuracy_threshold.md +0 -77
- pynibs/data/neuron/models/L23_PC_cADpyr_biphasic_v1.csv +0 -1281
- pynibs/data/neuron/models/L23_PC_cADpyr_monophasic_v1.csv +0 -1281
- pynibs/data/neuron/models/L4_LBC_biphasic_v1.csv +0 -1281
- pynibs/data/neuron/models/L4_LBC_monophasic_v1.csv +0 -1281
- pynibs/data/neuron/models/L4_NBC_biphasic_v1.csv +0 -1281
- pynibs/data/neuron/models/L4_NBC_monophasic_v1.csv +0 -1281
- pynibs/data/neuron/models/L4_SBC_biphasic_v1.csv +0 -1281
- pynibs/data/neuron/models/L4_SBC_monophasic_v1.csv +0 -1281
- pynibs/data/neuron/models/L5_TTPC2_cADpyr_biphasic_v1.csv +0 -1281
- pynibs/data/neuron/models/L5_TTPC2_cADpyr_monophasic_v1.csv +0 -1281
- pynibs/tests/data/InstrumentMarker20200225163611937.xml +0 -19
- pynibs/tests/data/TriggerMarkers_Coil0_20200225163443682.xml +0 -14
- pynibs/tests/data/TriggerMarkers_Coil1_20200225170337572.xml +0 -6373
- pynibs/tests/data/Xdmf.dtd +0 -89
- pynibs/tests/data/brainsight_niiImage_nifticoord.txt +0 -145
- pynibs/tests/data/brainsight_niiImage_nifticoord_largefile.txt +0 -1434
- pynibs/tests/data/brainsight_niiImage_niifticoord_mixedtargets.txt +0 -47
- pynibs/tests/data/create_subject_testsub.py +0 -332
- pynibs/tests/data/data.hdf5 +0 -0
- pynibs/tests/data/geo.hdf5 +0 -0
- pynibs/tests/test_coil.py +0 -474
- pynibs/tests/test_elements2nodes.py +0 -100
- pynibs/tests/test_hdf5_io/test_xdmf.py +0 -61
- pynibs/tests/test_mesh_transformations.py +0 -123
- pynibs/tests/test_mesh_utils.py +0 -143
- pynibs/tests/test_nnav_imports.py +0 -101
- pynibs/tests/test_quality_measures.py +0 -117
- pynibs/tests/test_regressdata.py +0 -289
- pynibs/tests/test_roi.py +0 -17
- pynibs/tests/test_rotations.py +0 -86
- pynibs/tests/test_subject.py +0 -71
- pynibs/tests/test_util.py +0 -24
- /pynibs/{regression/score_types.py → neuron/models/m1_montbrio.py} +0 -0
- {pyNIBS-0.2024.8.dist-info → pynibs-0.2026.1.dist-info/licenses}/LICENSE +0 -0
- {pyNIBS-0.2024.8.dist-info → pynibs-0.2026.1.dist-info}/top_level.txt +0 -0
pynibs/coil/export.py
ADDED
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import h5py
|
|
3
|
+
import numpy as np
|
|
4
|
+
import pandas as pd
|
|
5
|
+
import pynibs
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def create_stimsite_hdf5(fn_exp, fn_hdf, conditions_selected=None, sep="_", merge_sites=False, fix_angles=False,
|
|
9
|
+
data_dict=None, conditions_ignored=None):
|
|
10
|
+
"""
|
|
11
|
+
Reads results_conditions and creates an hdf5/xdmf pair with condition-wise centers of stimulation sites and
|
|
12
|
+
coil directions as data.
|
|
13
|
+
|
|
14
|
+
Parameters
|
|
15
|
+
----------
|
|
16
|
+
fn_exp : str
|
|
17
|
+
Path to results.csv.
|
|
18
|
+
fn_hdf : str
|
|
19
|
+
Path where to write file. Gets overridden if already existing.
|
|
20
|
+
conditions_selected : str or list of str, optional
|
|
21
|
+
List of conditions returned by the function, the others are omitted.
|
|
22
|
+
If None, all conditions are returned.
|
|
23
|
+
sep: str, default: "_"
|
|
24
|
+
Separator between condition label and angle (e.g. M1_0, or M1-0).
|
|
25
|
+
merge_sites : bool
|
|
26
|
+
If true, only one coil center per site is generated.
|
|
27
|
+
fix_angles : bool
|
|
28
|
+
rename 22.5 -> 0, 0 -> -45, 67.5 -> 90, 90 -> 135.
|
|
29
|
+
data_dict : dict ofnp.ndarray of float [n_stimsites] (optional), default: None
|
|
30
|
+
Dictionary containing data corresponding to the stimulation sites (keys).
|
|
31
|
+
conditions_ignored : str or list of str, optional
|
|
32
|
+
Conditions, which are not going to be included in the plot.
|
|
33
|
+
|
|
34
|
+
Returns
|
|
35
|
+
-------
|
|
36
|
+
<Files> : hdf5/xdmf file pair
|
|
37
|
+
Contains information about condition-wise stimulation sites and coil directions (fn_hdf)
|
|
38
|
+
|
|
39
|
+
Example
|
|
40
|
+
-------
|
|
41
|
+
.. code-block:: python
|
|
42
|
+
|
|
43
|
+
pynibs.create_stimsite_hdf5('/exp/1/experiment_corrected.csv',
|
|
44
|
+
'/stimsite', True, True)
|
|
45
|
+
"""
|
|
46
|
+
assert not fn_hdf.endswith('/')
|
|
47
|
+
|
|
48
|
+
exp = pynibs.expio.read_csv(fn_exp)
|
|
49
|
+
|
|
50
|
+
exp_cond = pynibs.expio.sort_by_condition(exp, conditions_selected=conditions_selected) # []
|
|
51
|
+
|
|
52
|
+
# get the unique conditions in the correct order
|
|
53
|
+
conds = [c['condition'][0] for c in exp_cond]
|
|
54
|
+
|
|
55
|
+
# remove conds
|
|
56
|
+
conds_temp = []
|
|
57
|
+
exp_cond_temp = []
|
|
58
|
+
|
|
59
|
+
if type(conditions_ignored) is not list:
|
|
60
|
+
conditions_ignored = [conditions_ignored]
|
|
61
|
+
|
|
62
|
+
for i_c, c in enumerate(conds):
|
|
63
|
+
ignore = False
|
|
64
|
+
for ci in list(conditions_ignored):
|
|
65
|
+
if c == ci:
|
|
66
|
+
ignore = True
|
|
67
|
+
|
|
68
|
+
if not ignore:
|
|
69
|
+
conds_temp.append(conds[i_c])
|
|
70
|
+
exp_cond_temp.append(exp_cond[i_c])
|
|
71
|
+
|
|
72
|
+
exp_cond = exp_cond_temp
|
|
73
|
+
conds = conds_temp
|
|
74
|
+
|
|
75
|
+
# hardcoded row #3 is condition
|
|
76
|
+
cond_idx = np.linspace(0, len(exp_cond), 1)[:, np.newaxis]
|
|
77
|
+
|
|
78
|
+
centers = []
|
|
79
|
+
m0 = []
|
|
80
|
+
m1 = []
|
|
81
|
+
m2 = []
|
|
82
|
+
|
|
83
|
+
for i_cond in range(len(exp_cond)):
|
|
84
|
+
centers.append(exp_cond[i_cond]['coil_mean_matrix'][0][0:3, 3])
|
|
85
|
+
m0.append(exp_cond[i_cond]['coil_mean_matrix'][0][0:3, 0])
|
|
86
|
+
m1.append(exp_cond[i_cond]['coil_mean_matrix'][0][0:3, 1])
|
|
87
|
+
m2.append(exp_cond[i_cond]['coil_mean_matrix'][0][0:3, 2])
|
|
88
|
+
|
|
89
|
+
# split conds to angles and sites: M1_90 -> M1, 90
|
|
90
|
+
angles = np.array([sp.split(sep)[-1] for sp in conds]).astype(np.float64)
|
|
91
|
+
sites = np.array([sp.split(sep)[0] for sp in conds])
|
|
92
|
+
sites_unique = np.unique(sites)
|
|
93
|
+
|
|
94
|
+
# average the center positions of a stimulation site over all orientations
|
|
95
|
+
if merge_sites:
|
|
96
|
+
|
|
97
|
+
# generate sites dict
|
|
98
|
+
centers_sites = dict()
|
|
99
|
+
|
|
100
|
+
for site in sites_unique:
|
|
101
|
+
centers_sites[site] = []
|
|
102
|
+
|
|
103
|
+
# gather all orientations and put them to the corresponding sites
|
|
104
|
+
for i_cond, site in enumerate(sites):
|
|
105
|
+
centers_sites[site].append(exp_cond[i_cond]['coil_mean_matrix'][0][0:3, 3])
|
|
106
|
+
|
|
107
|
+
# determine average position over all orientations for each site
|
|
108
|
+
for site in sites_unique:
|
|
109
|
+
centers_sites[site] = np.mean(np.vstack(centers_sites[site]), axis=0)
|
|
110
|
+
|
|
111
|
+
# write it back to centers
|
|
112
|
+
for i_cond, site in enumerate(sites):
|
|
113
|
+
centers[i_cond] = centers_sites[site]
|
|
114
|
+
|
|
115
|
+
centers = np.vstack(centers)
|
|
116
|
+
m0 = np.vstack(m0)
|
|
117
|
+
m1 = np.vstack(m1)
|
|
118
|
+
m2 = np.vstack(m2)
|
|
119
|
+
|
|
120
|
+
# enumerate sites, as paraview does not plot string array data
|
|
121
|
+
sites_idx = np.array(list(range(len(sites))))[:, np.newaxis]
|
|
122
|
+
|
|
123
|
+
angles[angles[:] == 675.] = 67.5
|
|
124
|
+
angles[angles[:] == 225.] = 22.5
|
|
125
|
+
|
|
126
|
+
if fix_angles:
|
|
127
|
+
# rename wrong angle names
|
|
128
|
+
angles_cor = np.copy(angles)
|
|
129
|
+
angles_cor[angles == 0] = -45.
|
|
130
|
+
angles_cor[angles == 22.5] = 0.
|
|
131
|
+
angles_cor[angles == 67.5] = 90.
|
|
132
|
+
angles_cor[angles == 90] = 135.
|
|
133
|
+
angles = angles_cor
|
|
134
|
+
|
|
135
|
+
# write hdf5 file
|
|
136
|
+
if not fn_hdf.endswith('.hdf5'):
|
|
137
|
+
fn_hdf += '.hdf5'
|
|
138
|
+
f = h5py.File(fn_hdf, 'w')
|
|
139
|
+
f.create_dataset('centers', data=centers.astype(np.float64))
|
|
140
|
+
f.create_dataset('m0', data=m0.astype(np.float64))
|
|
141
|
+
f.create_dataset('m1', data=m1.astype(np.float64))
|
|
142
|
+
f.create_dataset('m2', data=m2.astype(np.float64))
|
|
143
|
+
f.create_dataset('cond', data=np.string_(conds)) # this is a string array, not xdmf compatible
|
|
144
|
+
f.create_dataset('cond_idx', data=cond_idx)
|
|
145
|
+
f.create_dataset('angles', data=angles)
|
|
146
|
+
f.create_dataset('sites', data=np.string_(sites)) # this is a string array, not xdmf compatible
|
|
147
|
+
f.create_dataset('sites_idx', data=sites_idx)
|
|
148
|
+
|
|
149
|
+
data = None
|
|
150
|
+
if data_dict is not None:
|
|
151
|
+
data = np.zeros((len(list(data_dict.keys())), 1))
|
|
152
|
+
for i_data, cond in enumerate(conds):
|
|
153
|
+
data[i_data, 0] = data_dict[cond]
|
|
154
|
+
f.create_dataset('data', data=data)
|
|
155
|
+
f.close()
|
|
156
|
+
|
|
157
|
+
# write .xdmf file
|
|
158
|
+
f = open(fn_hdf[:-4] + 'xdmf', 'w')
|
|
159
|
+
fn_hdf = os.path.basename(fn_hdf) # relative links
|
|
160
|
+
|
|
161
|
+
# header
|
|
162
|
+
f.write('<?xml version="1.0"?>\n')
|
|
163
|
+
f.write('<!DOCTYPE Xdmf SYSTEM "Xdmf.dtd" []>\n')
|
|
164
|
+
f.write('<Xdmf Version="2.0" xmlns:xi="http://www.w3.org/2001/XInclude">\n')
|
|
165
|
+
f.write('<Domain>\n')
|
|
166
|
+
f.write('<Grid\nCollectionType="Spatial"\nGridType="Collection"\nName="Collection">\n')
|
|
167
|
+
|
|
168
|
+
# one grid for coil dipole nodes...store data hdf5.
|
|
169
|
+
#######################################################
|
|
170
|
+
f.write('<Grid Name="stimsites" GridType="Uniform">\n')
|
|
171
|
+
f.write('<Topology NumberOfElements="' + str(centers.shape[0]) +
|
|
172
|
+
'" TopologyType="Polyvertex" Name="Tri">\n')
|
|
173
|
+
f.write('<DataItem Format="XML" Dimensions="' + str(centers.shape[0]) + ' 1">\n')
|
|
174
|
+
# f.write(hdf5_fn + ':' + path + '/triangle_number_list\n')
|
|
175
|
+
np.savetxt(f, list(range(centers.shape[0])), fmt='%d', delimiter=' ') # 1 2 3 4 ... N_Points
|
|
176
|
+
f.write('</DataItem>\n')
|
|
177
|
+
f.write('</Topology>\n')
|
|
178
|
+
|
|
179
|
+
# nodes
|
|
180
|
+
f.write('<Geometry GeometryType="XYZ">\n')
|
|
181
|
+
f.write('<DataItem Format="HDF" Dimensions="' + str(centers.shape[0]) + ' 3">\n')
|
|
182
|
+
f.write(fn_hdf + ':' + '/centers\n')
|
|
183
|
+
f.write('</DataItem>\n')
|
|
184
|
+
f.write('</Geometry>\n')
|
|
185
|
+
|
|
186
|
+
# data
|
|
187
|
+
# dipole magnitude
|
|
188
|
+
# the 4 vectors
|
|
189
|
+
for i in range(3):
|
|
190
|
+
f.write('<Attribute Name="dir_' + str(i) + '" AttributeType="Vector" Center="Cell">\n')
|
|
191
|
+
f.write('<DataItem Format="HDF" Dimensions="' + str(centers.shape[0]) + ' 3">\n')
|
|
192
|
+
f.write(fn_hdf + ':' + '/m' + str(i) + '\n')
|
|
193
|
+
f.write('</DataItem>\n')
|
|
194
|
+
f.write('</Attribute>\n\n')
|
|
195
|
+
|
|
196
|
+
# angles
|
|
197
|
+
f.write('<Attribute Name="angles" AttributeType="Scalar" Center="Cell">\n')
|
|
198
|
+
f.write('<DataItem Format="HDF" Dimensions="' + str(centers.shape[0]) + ' 1">\n')
|
|
199
|
+
f.write(fn_hdf + ':' + '/angles\n')
|
|
200
|
+
f.write('</DataItem>\n')
|
|
201
|
+
f.write('</Attribute>\n\n')
|
|
202
|
+
|
|
203
|
+
# data
|
|
204
|
+
if data_dict is not None:
|
|
205
|
+
f.write('<Attribute Name="data" AttributeType="Scalar" Center="Cell">\n')
|
|
206
|
+
f.write('<DataItem Format="HDF" Dimensions="' + str(data.shape[0]) + ' 1">\n')
|
|
207
|
+
f.write(fn_hdf + ':' + '/data\n')
|
|
208
|
+
f.write('</DataItem>\n')
|
|
209
|
+
f.write('</Attribute>\n\n')
|
|
210
|
+
|
|
211
|
+
# site idx
|
|
212
|
+
f.write('<Attribute Name="sites_idx" AttributeType="Scalar" Center="Cell">\n')
|
|
213
|
+
f.write('<DataItem Format="HDF" Dimensions="' + str(centers.shape[0]) + ' 1">\n')
|
|
214
|
+
f.write(fn_hdf + ':' + '/sites_idx\n')
|
|
215
|
+
f.write('</DataItem>\n')
|
|
216
|
+
f.write('</Attribute>\n\n')
|
|
217
|
+
|
|
218
|
+
f.write('</Grid>\n')
|
|
219
|
+
# end coil dipole data
|
|
220
|
+
|
|
221
|
+
# footer
|
|
222
|
+
f.write('</Grid>\n')
|
|
223
|
+
f.write('</Domain>\n')
|
|
224
|
+
f.write('</Xdmf>\n')
|
|
225
|
+
f.close()
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def create_stimsite_from_list(fn_hdf, poslist, datanames=None, data=None, overwrite=False):
|
|
229
|
+
"""
|
|
230
|
+
This takes a list of matsimnibs-style coil position and orientations and creates an .hdf5 + .xdmf tuple
|
|
231
|
+
for all positions.
|
|
232
|
+
|
|
233
|
+
Centers and coil orientations are written to disk, with optional data for each coil configuration.
|
|
234
|
+
|
|
235
|
+
Parameters
|
|
236
|
+
----------
|
|
237
|
+
fn_hdf: str
|
|
238
|
+
Filename for the .hdf5 file. The .xdmf is saved with the same basename.
|
|
239
|
+
Folder should already exist.
|
|
240
|
+
poslist: list of np.ndarray
|
|
241
|
+
(4,4) Positions.
|
|
242
|
+
datanames: str or list of str, optional
|
|
243
|
+
Dataset names for ``data``.
|
|
244
|
+
data: np.ndarray, optional
|
|
245
|
+
Dataset array with shape = ``(len(poslist.pos), len(datanames())``.
|
|
246
|
+
overwrite : bool, defaul: False
|
|
247
|
+
Overwrite existing files.
|
|
248
|
+
"""
|
|
249
|
+
centers = []
|
|
250
|
+
m0 = []
|
|
251
|
+
m1 = []
|
|
252
|
+
m2 = []
|
|
253
|
+
if data is not None:
|
|
254
|
+
assert isinstance(data, np.ndarray)
|
|
255
|
+
|
|
256
|
+
for lst in poslist:
|
|
257
|
+
centers.append(lst[0:3, 3])
|
|
258
|
+
m0.append(lst[0:3, 0])
|
|
259
|
+
m1.append(lst[0:3, 1])
|
|
260
|
+
m2.append(lst[0:3, 2])
|
|
261
|
+
|
|
262
|
+
centers = np.vstack(centers)
|
|
263
|
+
m0 = np.vstack(m0)
|
|
264
|
+
m1 = np.vstack(m1)
|
|
265
|
+
m2 = np.vstack(m2)
|
|
266
|
+
|
|
267
|
+
write_coil_pos_hdf5(fn_hdf, centers, m0, m1, m2, datanames=datanames, data=data, overwrite=overwrite)
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def create_stimsite_from_tmslist(fn_hdf, poslist, datanames=None, data=None, overwrite=False):
|
|
271
|
+
"""
|
|
272
|
+
This takes a :py:class:simnibs.sim_struct.TMSLIST from simnibs and creates an .hdf5 + .xdmf tuple for all positions.
|
|
273
|
+
|
|
274
|
+
Centers and coil orientations are written to disk, with optional data for each coil configuration.
|
|
275
|
+
|
|
276
|
+
Parameters
|
|
277
|
+
----------
|
|
278
|
+
fn_hdf: str
|
|
279
|
+
Filename for the .hdf5 file. The .xdmf is saved with the same basename.
|
|
280
|
+
Folder should already exist.
|
|
281
|
+
poslist: simnibs.sim_struct.TMSLIST
|
|
282
|
+
poslist.pos[*].matsimnibs have to be set.
|
|
283
|
+
datanames: str or list of str, optional
|
|
284
|
+
Dataset names for ``data``.
|
|
285
|
+
data: np.ndarray, optional
|
|
286
|
+
Dataset array with shape = ``(len(poslist.pos), len(datanames())``.
|
|
287
|
+
overwrite : bool, default: False
|
|
288
|
+
Overwrite existing files
|
|
289
|
+
"""
|
|
290
|
+
centers = []
|
|
291
|
+
m0 = []
|
|
292
|
+
m1 = []
|
|
293
|
+
m2 = []
|
|
294
|
+
assert poslist.pos
|
|
295
|
+
if data is not None:
|
|
296
|
+
assert isinstance(data, np.ndarray)
|
|
297
|
+
for pos in poslist.pos:
|
|
298
|
+
assert pos.matsimnibs is not None
|
|
299
|
+
pos.matsimnibs = np.array(pos.matsimnibs)
|
|
300
|
+
centers.append(pos.matsimnibs[0:3, 3])
|
|
301
|
+
m0.append(pos.matsimnibs[0:3, 0])
|
|
302
|
+
m1.append(pos.matsimnibs[0:3, 1])
|
|
303
|
+
m2.append(pos.matsimnibs[0:3, 2])
|
|
304
|
+
|
|
305
|
+
centers = np.vstack(centers)
|
|
306
|
+
m0 = np.vstack(m0)
|
|
307
|
+
m1 = np.vstack(m1)
|
|
308
|
+
m2 = np.vstack(m2)
|
|
309
|
+
|
|
310
|
+
write_coil_pos_hdf5(fn_hdf, centers, m0, m1, m2, datanames=datanames, data=data, overwrite=overwrite)
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def create_stimsite_from_exp_hdf5(fn_exp, fn_hdf, datanames=None, data=None, overwrite=False):
|
|
314
|
+
"""
|
|
315
|
+
This takes an experiment.hdf5 file and creates an .hdf5 + .xdmf tuple for all coil positions for visualization.
|
|
316
|
+
|
|
317
|
+
Parameters
|
|
318
|
+
----------
|
|
319
|
+
fn_exp : str
|
|
320
|
+
Path to experiment.hdf5
|
|
321
|
+
fn_hdf : str
|
|
322
|
+
Filename for the resulting .hdf5 file. The .xdmf is saved with the same basename.
|
|
323
|
+
Folder should already exist.
|
|
324
|
+
datanames : str or list of str, optional
|
|
325
|
+
Dataset names for ``data``
|
|
326
|
+
data : np.ndarray, optional
|
|
327
|
+
Dataset array with shape = ``(len(poslist.pos), len(datanames())``.
|
|
328
|
+
overwrite : bool, default: False
|
|
329
|
+
Overwrite existing files.
|
|
330
|
+
"""
|
|
331
|
+
df_stim = pd.read_hdf(fn_exp, "stim_data")
|
|
332
|
+
|
|
333
|
+
matsimnibs = np.zeros((4, 4, df_stim.shape[0]))
|
|
334
|
+
|
|
335
|
+
for i in range(df_stim.shape[0]):
|
|
336
|
+
matsimnibs[:, :, i] = df_stim["coil_mean"].iloc[i]
|
|
337
|
+
|
|
338
|
+
create_stimsite_from_matsimnibs(fn_hdf=fn_hdf,
|
|
339
|
+
matsimnibs=matsimnibs,
|
|
340
|
+
datanames=datanames,
|
|
341
|
+
data=data,
|
|
342
|
+
overwrite=overwrite)
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def create_stimsite_from_matsimnibs(fn_hdf, matsimnibs, datanames=None, data=None, overwrite=False):
|
|
346
|
+
"""
|
|
347
|
+
This takes a matsimnibs array and creates an .hdf5 + .xdmf tuple for all coil positions for visualization.
|
|
348
|
+
|
|
349
|
+
Centers and coil orientations are written disk.
|
|
350
|
+
|
|
351
|
+
Parameters
|
|
352
|
+
----------
|
|
353
|
+
fn_hdf: str
|
|
354
|
+
Filename for the .hdf5 file. The .xdmf is saved with the same basename.
|
|
355
|
+
Folder should already exist.
|
|
356
|
+
matsimnibs: np.ndarray
|
|
357
|
+
(4, 4, n_pos)
|
|
358
|
+
Matsimnibs matrices containing the coil orientation (x,y,z) and position (p)
|
|
359
|
+
|
|
360
|
+
.. math::
|
|
361
|
+
\\begin{bmatrix}
|
|
362
|
+
| & | & | & | \\\\
|
|
363
|
+
x & y & z & p \\\\
|
|
364
|
+
| & | & | & | \\\\
|
|
365
|
+
0 & 0 & 0 & 1 \\\\
|
|
366
|
+
\\end{bmatrix}
|
|
367
|
+
datanames: str or list of str, optional
|
|
368
|
+
Dataset names for ``data``.
|
|
369
|
+
data: np.ndarray, optional
|
|
370
|
+
(len(poslist.pos), len(datanames).
|
|
371
|
+
overwrite : bool, default: False
|
|
372
|
+
Overwrite existing files.
|
|
373
|
+
"""
|
|
374
|
+
matsimnibs = np.atleast_3d(matsimnibs)
|
|
375
|
+
n_pos = matsimnibs.shape[2]
|
|
376
|
+
centers = np.zeros((n_pos, 3))
|
|
377
|
+
m0 = np.zeros((n_pos, 3))
|
|
378
|
+
m1 = np.zeros((n_pos, 3))
|
|
379
|
+
m2 = np.zeros((n_pos, 3))
|
|
380
|
+
if data is not None:
|
|
381
|
+
assert isinstance(data, np.ndarray)
|
|
382
|
+
|
|
383
|
+
for i in range(matsimnibs.shape[2]):
|
|
384
|
+
centers[i, :] = matsimnibs[0:3, 3, i]
|
|
385
|
+
m0[i, :] = matsimnibs[0:3, 0, i]
|
|
386
|
+
m1[i, :] = matsimnibs[0:3, 1, i]
|
|
387
|
+
m2[i, :] = matsimnibs[0:3, 2, i]
|
|
388
|
+
|
|
389
|
+
write_coil_pos_hdf5(fn_hdf, centers, m0, m1, m2, datanames=datanames, data=data, overwrite=overwrite)
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def write_coil_pos_hdf5(fn_hdf, centers, m0, m1, m2, datanames=None, data=None, overwrite=False):
|
|
393
|
+
"""
|
|
394
|
+
Creates a ``.hdf5`` + ``.xdmf`` file tuple for all coil positions.
|
|
395
|
+
Coil centers and coil orientations are saved, and - optionally - data for each position if ``data`` and
|
|
396
|
+
``datanames`` are provided.
|
|
397
|
+
|
|
398
|
+
Parameters
|
|
399
|
+
----------
|
|
400
|
+
fn_hdf : str
|
|
401
|
+
Filename for the .hdf5 file. The .xdmf is saved with the same basename.
|
|
402
|
+
Folder should already exist.
|
|
403
|
+
centers : np.ndarray of float
|
|
404
|
+
(n_pos, 3) Coil positions.
|
|
405
|
+
m0 : np.ndarray of float
|
|
406
|
+
(n_pos, 3) Coil orientation x-axis (looking at the active (patient) side of the coil pointing to the right).
|
|
407
|
+
m1 : np.ndarray of float
|
|
408
|
+
(n_pos, 3) Coil orientation y-axis (looking at the active side of the coil pointing up away from the handle).
|
|
409
|
+
m2 : np.ndarray of float
|
|
410
|
+
(n_pos, 3) Coil orientation z-axis (looking at the active (patient) side of the coil pointing to the patient).
|
|
411
|
+
datanames : str or list of str, optional
|
|
412
|
+
(n_data) Dataset names for ``data``
|
|
413
|
+
data : np.ndarray, optional
|
|
414
|
+
(n_pos, n_data) Dataset array with (len(poslist.pos), len(datanames()).
|
|
415
|
+
overwrite : bool, default: False
|
|
416
|
+
Overwrite existing files.
|
|
417
|
+
"""
|
|
418
|
+
n_pos = centers.shape[0]
|
|
419
|
+
if isinstance(datanames, str):
|
|
420
|
+
datanames = [datanames]
|
|
421
|
+
|
|
422
|
+
if data is not None:
|
|
423
|
+
if datanames is None:
|
|
424
|
+
raise ValueError("Provide datanames= with data= argument.")
|
|
425
|
+
if isinstance(datanames, str):
|
|
426
|
+
datanames = [datanames]
|
|
427
|
+
if len(data.shape) <= 1:
|
|
428
|
+
data = np.atleast_1d(data)[:, np.newaxis]
|
|
429
|
+
assert data.shape == (n_pos, len(datanames))
|
|
430
|
+
if datanames is not None and data is None:
|
|
431
|
+
raise ValueError("Provide data= with datanames= argument.")
|
|
432
|
+
|
|
433
|
+
m0_reshaped = np.hstack((m0, np.zeros((n_pos, 1)))).T[:, np.newaxis, :]
|
|
434
|
+
m1_reshaped = np.hstack((m1, np.zeros((n_pos, 1)))).T[:, np.newaxis, :]
|
|
435
|
+
m2_reshaped = np.hstack((m2, np.zeros((n_pos, 1)))).T[:, np.newaxis, :]
|
|
436
|
+
centers_reshaped = np.hstack((centers, np.ones((n_pos, 1)))).T[:, np.newaxis, :]
|
|
437
|
+
|
|
438
|
+
matsimnibs = np.concatenate((m0_reshaped, m1_reshaped, m2_reshaped, centers_reshaped), axis=1)
|
|
439
|
+
|
|
440
|
+
# write hdf5 file
|
|
441
|
+
if not fn_hdf.endswith('.hdf5'):
|
|
442
|
+
fn_hdf += '.hdf5'
|
|
443
|
+
if os.path.exists(fn_hdf) and not overwrite:
|
|
444
|
+
raise OSError(fn_hdf + " already exists. Set overwrite flag for create_stimsite_from_poslist.")
|
|
445
|
+
|
|
446
|
+
with h5py.File(fn_hdf, 'w') as f:
|
|
447
|
+
f.create_dataset('centers', data=centers.astype(np.float64))
|
|
448
|
+
f.create_dataset('m0', data=m0.astype(np.float64))
|
|
449
|
+
f.create_dataset('m1', data=m1.astype(np.float64))
|
|
450
|
+
f.create_dataset('m2', data=m2.astype(np.float64))
|
|
451
|
+
f.create_dataset("matsimnibs", data=matsimnibs)
|
|
452
|
+
|
|
453
|
+
if data is not None:
|
|
454
|
+
for i, col in enumerate(data.T):
|
|
455
|
+
f.create_dataset('/data/' + datanames[i], data=col)
|
|
456
|
+
|
|
457
|
+
# write .xdmf file
|
|
458
|
+
with open(fn_hdf[:-4] + 'xdmf', 'w') as f:
|
|
459
|
+
fn_hdf = os.path.basename(fn_hdf) # relative links
|
|
460
|
+
|
|
461
|
+
# header
|
|
462
|
+
f.write('<?xml version="1.0"?>\n')
|
|
463
|
+
f.write('<!DOCTYPE Xdmf SYSTEM "Xdmf.dtd" []>\n')
|
|
464
|
+
f.write('<Xdmf Version="2.0" xmlns:xi="http://www.w3.org/2001/XInclude">\n')
|
|
465
|
+
f.write('<Domain>\n')
|
|
466
|
+
f.write('<Grid CollectionType="Spatial" GridType="Collection" Name="Collection">\n')
|
|
467
|
+
|
|
468
|
+
# one grid for coil dipole nodes...store data hdf5.
|
|
469
|
+
#######################################################
|
|
470
|
+
f.write('<Grid Name="stimsites" GridType="Uniform">\n')
|
|
471
|
+
f.write(f'<Topology NumberOfElements="{centers.shape[0]}" TopologyType="Polyvertex" Name="Tri">\n')
|
|
472
|
+
f.write(f'\t<DataItem Format="XML" Dimensions="{centers.shape[0]} 1">\n')
|
|
473
|
+
np.savetxt(f, list(range(centers.shape[0])), fmt='\t%d', delimiter=' ') # 1 2 3 4 ... N_Points
|
|
474
|
+
f.write('\t</DataItem>\n')
|
|
475
|
+
f.write('</Topology>\n\n')
|
|
476
|
+
|
|
477
|
+
# nodes
|
|
478
|
+
f.write('<Geometry GeometryType="XYZ">\n')
|
|
479
|
+
f.write(f'\t<DataItem Format="HDF" Dimensions="{centers.shape[0]} 3">\n')
|
|
480
|
+
f.write(f'\t{fn_hdf}:/centers\n')
|
|
481
|
+
f.write('\t</DataItem>\n')
|
|
482
|
+
f.write('</Geometry>\n\n')
|
|
483
|
+
|
|
484
|
+
# data
|
|
485
|
+
# dipole magnitude
|
|
486
|
+
# the 4 vectors
|
|
487
|
+
for i in range(3):
|
|
488
|
+
f.write(f'\t\t<Attribute Name="dir_{i}" AttributeType="Vector" Center="Cell">\n')
|
|
489
|
+
f.write(f'\t\t\t<DataItem Format="HDF" Dimensions="{centers.shape[0]} 3">\n')
|
|
490
|
+
f.write(f'\t\t\t{fn_hdf}:/m{i}\n')
|
|
491
|
+
f.write('\t\t\t</DataItem>\n')
|
|
492
|
+
f.write('\t\t</Attribute>\n\n')
|
|
493
|
+
|
|
494
|
+
if data is not None:
|
|
495
|
+
for i, col in enumerate(data.T):
|
|
496
|
+
f.write(f'\t\t<Attribute Name="{datanames[i]}" AttributeType="Scalar" Center="Cell">\n')
|
|
497
|
+
f.write('\t\t\t<DataItem Format="HDF" Dimensions="' + str(centers.shape[0]) + ' 1">\n')
|
|
498
|
+
f.write(f'\t\t\t{fn_hdf}:/data/{datanames[i]}\n')
|
|
499
|
+
f.write('\t\t\t</DataItem>\n')
|
|
500
|
+
f.write('\t\t</Attribute>\n\n')
|
|
501
|
+
|
|
502
|
+
f.write('</Grid>\n')
|
|
503
|
+
# end coil dipole data
|
|
504
|
+
|
|
505
|
+
# footer
|
|
506
|
+
f.write('</Grid>\n')
|
|
507
|
+
f.write('</Domain>\n')
|
|
508
|
+
f.write('</Xdmf>\n')
|
pynibs/congruence/__init__.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""
|
|
2
2
|
:py:class:`pynibs.congruence` holds the initial congruence factor implementation as described in Weise, Numssen, et al., 2020 [1]_.
|
|
3
|
-
This code is mainly stored here for reproducibility reasons. The current approach (Numssen et al., 2021; [2]_) uses
|
|
3
|
+
This code is mainly stored here for reproducibility reasons. The current approach (Numssen et al., 2021; [2]_; Jing et al., 2024; [3]_) uses
|
|
4
4
|
the :py:class:`pynibs.regression` methods.
|
|
5
5
|
|
|
6
6
|
References
|
|
@@ -9,6 +9,9 @@ References
|
|
|
9
9
|
A novel approach to localize cortical TMS effects. Neuroimage, 209, 116486.
|
|
10
10
|
.. [2] Numssen, O., Zier, A. L., Thielscher, A., Hartwigsen, G., Knösche, T. R., & Weise, K. (2021).
|
|
11
11
|
Efficient high-resolution TMS mapping of the human motor cortex by nonlinear regression. NeuroImage, 245, 118654.
|
|
12
|
+
.. [3] Jing, Y., Numssen, O., Weise, K., Kalloch, B., Buchberger, L., Haueisen, J., Hartwigsen, G., Knösche, T. (2023).
|
|
13
|
+
Modeling the Effects of Transcranial Magnetic Stimulation on Spatial Attention. *Physics in Medicine & Biology*.
|
|
14
|
+
doi: `10.1088/1361-6560/acff34 <https://doi.org/10.1088/1361-6560/acff34>`_
|
|
12
15
|
"""
|
|
13
16
|
from .congruence import *
|
|
14
17
|
from .ext_metrics import *
|