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
|
@@ -0,0 +1,698 @@
|
|
|
1
|
+
import scipy
|
|
2
|
+
import numpy as np
|
|
3
|
+
from sklearn.neighbors import KernelDensity
|
|
4
|
+
|
|
5
|
+
import pynibs
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def mc(idx_list, array, ele_idx_1, mode="cols", **kwargs):
|
|
9
|
+
"""
|
|
10
|
+
Determines mutual coherence for given zap indices in idx_list.
|
|
11
|
+
|
|
12
|
+
Parameters
|
|
13
|
+
----------
|
|
14
|
+
idx_list : list of lists [n_combs][n_zaps]
|
|
15
|
+
Index lists of zaps containing different possible combinations. Usually only the last index changes.
|
|
16
|
+
array : np.ndarray of float [n_zaps x n_ele]
|
|
17
|
+
Electric field for different coil positions and elements.
|
|
18
|
+
ele_idx_1 : np.ndarray of float [n_ele]
|
|
19
|
+
Element indices for which the optimization is performed.
|
|
20
|
+
mode : str, default: "cols"
|
|
21
|
+
Set if the mutual coherence is calculated w.r.t. columns or rows ("cols", "rows").
|
|
22
|
+
|
|
23
|
+
Returns
|
|
24
|
+
-------
|
|
25
|
+
res : np.ndarray of float [n_combs]
|
|
26
|
+
Mutual coherence. Lower values indicate more orthogonal e-field combinations (better).
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
res = []
|
|
30
|
+
array = array[:, ele_idx_1]
|
|
31
|
+
|
|
32
|
+
if array.ndim == 1:
|
|
33
|
+
array = array[:, np.newaxis]
|
|
34
|
+
|
|
35
|
+
for ind in idx_list:
|
|
36
|
+
if mode == "cols":
|
|
37
|
+
res.append(pynibs.mutual_coherence(array[ind, :]))
|
|
38
|
+
elif mode == "rows":
|
|
39
|
+
res.append(pynibs.mutual_coherence(array[ind, :].transpose()))
|
|
40
|
+
else:
|
|
41
|
+
raise NotImplementedError("Specified mode not implemented. Choose 'rows' or 'cols'.")
|
|
42
|
+
|
|
43
|
+
return res
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def roi_elmt_wise_corr(idx_list, e_mat, ele_idx_1, decorrelate_hotspot_only=False, backend=np, **kwargs):
|
|
47
|
+
"""
|
|
48
|
+
Compute element wise correlation for sets of e-field.
|
|
49
|
+
|
|
50
|
+
Parameters
|
|
51
|
+
----------
|
|
52
|
+
idx_list : list of int
|
|
53
|
+
List of efield indicies in e_mat.
|
|
54
|
+
e_mat : np.ndarray
|
|
55
|
+
All e-fields.
|
|
56
|
+
ele_idx_1 : list of int
|
|
57
|
+
All element indices to compute corcoeff for.
|
|
58
|
+
decorrelate_hotspot_only : bool, default: False
|
|
59
|
+
If true, ele_idx_1[-.1] is used for decorrelation.
|
|
60
|
+
backend : module
|
|
61
|
+
Package to use to compute correlation - probably either numpy or cuda.
|
|
62
|
+
|
|
63
|
+
Returns
|
|
64
|
+
-------
|
|
65
|
+
res : np.ndarray
|
|
66
|
+
(e_mat.shape[0], ) with correlations for all e-field sets in idx_list.
|
|
67
|
+
"""
|
|
68
|
+
res = backend.zeros(len(idx_list))
|
|
69
|
+
|
|
70
|
+
for result_list_idx, coil_conf_seq_idcs in enumerate(idx_list):
|
|
71
|
+
# extract values of interest from e-field matrix
|
|
72
|
+
e_selected_subsampled = e_mat[coil_conf_seq_idcs][:, ele_idx_1]
|
|
73
|
+
|
|
74
|
+
# compute column-wise correlation: we want to reduce the e-field correlation between ROI elements
|
|
75
|
+
correlation_mat = backend.corrcoef(e_selected_subsampled, rowvar=False)
|
|
76
|
+
|
|
77
|
+
if decorrelate_hotspot_only:
|
|
78
|
+
# assumption: hotspot index is always included at last position of 'ele_idx_1'
|
|
79
|
+
res[result_list_idx] = backend.mean(backend.abs(correlation_mat[-1, :]))
|
|
80
|
+
else:
|
|
81
|
+
# temp_mat = backend.abs(correlation_mat)
|
|
82
|
+
# res[result_list_idx] = backend.mean(correlation_mat[backend.triu_indices_from(temp_mat, k=1)])
|
|
83
|
+
res[result_list_idx] = backend.mean(backend.abs(correlation_mat))
|
|
84
|
+
|
|
85
|
+
# num_nonzero_elmts = backend.count_nonzero(temp_mat)
|
|
86
|
+
# matrix score
|
|
87
|
+
# score = 1 / num_nonzero_elmts * backend.sum(temp_mat)
|
|
88
|
+
return res
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def coil_wise_corr(idx_list, array, ele_idx_1, **kwargs):
|
|
92
|
+
array = array[:, ele_idx_1]
|
|
93
|
+
|
|
94
|
+
if array.ndim == 1:
|
|
95
|
+
array = array[:, np.newaxis]
|
|
96
|
+
|
|
97
|
+
res = []
|
|
98
|
+
|
|
99
|
+
for ind in idx_list:
|
|
100
|
+
r = np.corrcoef(array[ind, :])
|
|
101
|
+
res.append(np.mean(r[np.triu_indices(r.shape[0], k=1)]))
|
|
102
|
+
|
|
103
|
+
return res
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def var(idx_list, array, ele_idx_1, **kwargs):
|
|
107
|
+
array = array[:, ele_idx_1]
|
|
108
|
+
|
|
109
|
+
if array.ndim == 1:
|
|
110
|
+
array = array[:, np.newaxis]
|
|
111
|
+
|
|
112
|
+
res = []
|
|
113
|
+
|
|
114
|
+
for ind in idx_list:
|
|
115
|
+
res.append(np.mean(np.var(array[ind, :], axis=0)))
|
|
116
|
+
|
|
117
|
+
return res
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def smooth(idx_list, array, ele_idx_1, **kwargs):
|
|
121
|
+
array = array[:, ele_idx_1]
|
|
122
|
+
|
|
123
|
+
if array.ndim == 1:
|
|
124
|
+
array = array[:, np.newaxis]
|
|
125
|
+
|
|
126
|
+
res = []
|
|
127
|
+
|
|
128
|
+
for ind in idx_list:
|
|
129
|
+
res.append(np.var(np.mean(array[ind, :], axis=0)))
|
|
130
|
+
|
|
131
|
+
return res
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def svd(idx_list, array, ele_idx_1, **kwargs):
|
|
135
|
+
"""
|
|
136
|
+
Determines condition number for given zap indices in idx_list.
|
|
137
|
+
|
|
138
|
+
Parameters
|
|
139
|
+
----------
|
|
140
|
+
idx_list : list of lists [n_combs][n_zaps]
|
|
141
|
+
Index lists of zaps containing different possible combinations. Usually only the last index changes.
|
|
142
|
+
array : np.ndarray of float [n_zaps x n_ele]
|
|
143
|
+
Electric field for different coil positions and elements.
|
|
144
|
+
ele_idx_1 : np.ndarray of float [n_ele]
|
|
145
|
+
Element indices for which the optimization is performed.
|
|
146
|
+
|
|
147
|
+
Returns
|
|
148
|
+
-------
|
|
149
|
+
res : np.ndarray of float [n_combs]
|
|
150
|
+
Condition number. Lower values indicate more orthogonal e-field combinations (better).
|
|
151
|
+
"""
|
|
152
|
+
array = array[:, ele_idx_1]
|
|
153
|
+
|
|
154
|
+
if array.ndim == 1:
|
|
155
|
+
array = array[:, np.newaxis]
|
|
156
|
+
|
|
157
|
+
res = []
|
|
158
|
+
|
|
159
|
+
for ind in idx_list:
|
|
160
|
+
s = scipy.linalg.svd(array[ind, :], compute_uv=False)
|
|
161
|
+
res.append(np.max(s) / np.min(s))
|
|
162
|
+
|
|
163
|
+
return res
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def variability(idx_list, array, ele_idx_1, **kwargs):
|
|
167
|
+
"""
|
|
168
|
+
Determines variability score for given zap indices in idx_list.
|
|
169
|
+
|
|
170
|
+
Parameters
|
|
171
|
+
----------
|
|
172
|
+
idx_list : list of lists [n_combs][n_zaps]
|
|
173
|
+
Index lists of zaps containing different possible combinations. Usually only the last index changes.
|
|
174
|
+
array : np.ndarray of float [n_zaps x n_ele]
|
|
175
|
+
Electric field for different coil positions and elements.
|
|
176
|
+
ele_idx_1 : np.ndarray of float [n_ele]
|
|
177
|
+
Element indices for which the optimization is performed.
|
|
178
|
+
|
|
179
|
+
Returns
|
|
180
|
+
-------
|
|
181
|
+
res : np.ndarray of float [n_combs]
|
|
182
|
+
Condition number. Lower values indicate more orthogonal e-field combinations (better).
|
|
183
|
+
"""
|
|
184
|
+
array = array[:, ele_idx_1]
|
|
185
|
+
|
|
186
|
+
if array.ndim == 1:
|
|
187
|
+
array = array[:, np.newaxis]
|
|
188
|
+
|
|
189
|
+
res = []
|
|
190
|
+
|
|
191
|
+
for ind in idx_list:
|
|
192
|
+
distances = np.zeros((array.shape[1], array.shape[1]))
|
|
193
|
+
d = 0
|
|
194
|
+
|
|
195
|
+
for col_idx in range(0, array.shape[1] - 1):
|
|
196
|
+
for row_idx in range(col_idx + 1, array.shape[1]):
|
|
197
|
+
distances[row_idx, col_idx] = np.linalg.norm(array[ind, row_idx] - array[ind, col_idx])
|
|
198
|
+
d += distances[row_idx, col_idx]
|
|
199
|
+
|
|
200
|
+
res.append(1 / d)
|
|
201
|
+
|
|
202
|
+
return res
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def rowvec_diff_prepare(idx_list, array, ele_idx_1, **kwargs):
|
|
206
|
+
"""
|
|
207
|
+
Computes the part of the difference matrix of row-vectors specified by the row indices in idx_list.
|
|
208
|
+
Assumption: 'idx_list' must be sorted and valid within 'array'.
|
|
209
|
+
|
|
210
|
+
:param idx_list: typing.List[int]
|
|
211
|
+
List of row indices whose difference should be determined.
|
|
212
|
+
:param array: numpy.typing.ArrayLike [n_coil x n_ele]
|
|
213
|
+
E-field matrix of all possible coil positions.
|
|
214
|
+
:param ele_idx_1: numpy.typing.ArrayLike [n_ele]
|
|
215
|
+
Indices of the ROI elements that should be considered for optimization.
|
|
216
|
+
:return: numpy.typing.ArrayLike [array.shape[0] x array.shape[0]] = [n_rows x n_rows]
|
|
217
|
+
The difference matrix with the lenght of the difference vectors between
|
|
218
|
+
pairs of row vectors specified by idx_list. All other (not calculated)
|
|
219
|
+
paris of row vectors have a score of 0 in this matrix.
|
|
220
|
+
"""
|
|
221
|
+
efields_from_coil = array
|
|
222
|
+
total_num_coil_pos = array.shape[0]
|
|
223
|
+
|
|
224
|
+
# preparation:
|
|
225
|
+
# for each pair of coil configuration, determine the difference of the associated rwo vectors in the e-field matrix
|
|
226
|
+
efields_diff = np.zeros((efields_from_coil.shape[0], efields_from_coil.shape[0]))
|
|
227
|
+
for coil_idx_outer in idx_list:
|
|
228
|
+
for coil_idx_inner in range(coil_idx_outer + 1, total_num_coil_pos):
|
|
229
|
+
diff_vec = efields_from_coil[coil_idx_outer, ele_idx_1] - efields_from_coil[coil_idx_inner, ele_idx_1]
|
|
230
|
+
efields_diff[coil_idx_outer, coil_idx_inner] = np.linalg.norm(diff_vec, ord=2)
|
|
231
|
+
|
|
232
|
+
return efields_diff
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def dist(idx_list, array, ele_idx_1, **kwargs):
|
|
236
|
+
"""
|
|
237
|
+
Determines distance score for given zap indices in idx_list.
|
|
238
|
+
|
|
239
|
+
Parameters
|
|
240
|
+
----------
|
|
241
|
+
idx_list : list of lists [n_combs][n_zaps]
|
|
242
|
+
Index lists of zaps containing different possible combinations. Usually only the last index changes.
|
|
243
|
+
array : np.ndarray of float [n_zaps x n_ele]
|
|
244
|
+
Electric field for different coil positions and elements.
|
|
245
|
+
ele_idx_1 : np.ndarray of float [n_ele]
|
|
246
|
+
Element indices for which the optimization is performed.
|
|
247
|
+
|
|
248
|
+
Returns
|
|
249
|
+
-------
|
|
250
|
+
res : np.ndarray of float [n_combs]
|
|
251
|
+
Distance based score. Lower values indicate more equidistant sampling (better)
|
|
252
|
+
"""
|
|
253
|
+
res = np.zeros(len(idx_list))
|
|
254
|
+
|
|
255
|
+
array_dist = array[:, ele_idx_1]
|
|
256
|
+
|
|
257
|
+
if array_dist.ndim == 1:
|
|
258
|
+
array_dist = array_dist[:, np.newaxis]
|
|
259
|
+
|
|
260
|
+
e_max = np.max(array_dist, axis=0)
|
|
261
|
+
e_min = np.min(array_dist, axis=0)
|
|
262
|
+
|
|
263
|
+
for j, ind in enumerate(idx_list):
|
|
264
|
+
p = np.vstack((e_min, array_dist[ind, :], e_max))
|
|
265
|
+
d_var = np.zeros(array_dist.shape[1])
|
|
266
|
+
|
|
267
|
+
for i in range(array_dist.shape[1]):
|
|
268
|
+
p_sort = np.sort(p[:, i])
|
|
269
|
+
|
|
270
|
+
if p_sort[0] == p_sort[1]:
|
|
271
|
+
p_sort = p_sort[1:]
|
|
272
|
+
|
|
273
|
+
if p_sort[-2] == p_sort[-1]:
|
|
274
|
+
p_sort = p_sort[:-1]
|
|
275
|
+
|
|
276
|
+
d_var[i] = np.var(np.diff(p_sort))
|
|
277
|
+
|
|
278
|
+
res[j] = np.mean(d_var)
|
|
279
|
+
|
|
280
|
+
return res
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def dist_svd(idx_list, array, ele_idx_1, ele_idx_2, **kwargs):
|
|
284
|
+
"""
|
|
285
|
+
Determines distance score and condition number for given zap indices in idx_list. If c_max_idx is given,
|
|
286
|
+
the distance based score is calculated only for this element.
|
|
287
|
+
The condition number however is optimized for all elements in array
|
|
288
|
+
|
|
289
|
+
Parameters
|
|
290
|
+
----------
|
|
291
|
+
idx_list : list of lists [n_combs][n_zaps]
|
|
292
|
+
Index lists of zaps containing different possible combinations. Usually only the last index changes.
|
|
293
|
+
array : np.ndarray of float [n_zaps x n_ele]
|
|
294
|
+
Electric field for different coil positions and elements.
|
|
295
|
+
ele_idx_1 : np.ndarray of float [n_ele]
|
|
296
|
+
Element indices for which the dist optimization is performed for.
|
|
297
|
+
ele_idx_2 : np.ndarray of float [n_ele]
|
|
298
|
+
Element indices for which the svd optimization is performed for.
|
|
299
|
+
|
|
300
|
+
Returns
|
|
301
|
+
-------
|
|
302
|
+
res_dist : np.ndarray of float [n_combs]
|
|
303
|
+
Distance based score. Lower values indicate more equidistant sampling (better).
|
|
304
|
+
res_svd : np.ndarray of float [n_combs]
|
|
305
|
+
Condition number. Lower values indicate more orthogonal e-field combinations (better).
|
|
306
|
+
"""
|
|
307
|
+
res_dist = np.zeros(len(idx_list))
|
|
308
|
+
res_svd = np.zeros(len(idx_list))
|
|
309
|
+
|
|
310
|
+
array_dist = array[:, ele_idx_1]
|
|
311
|
+
|
|
312
|
+
if array_dist.ndim == 1:
|
|
313
|
+
array_dist = array_dist[:, np.newaxis]
|
|
314
|
+
|
|
315
|
+
array_svd = array[:, ele_idx_2]
|
|
316
|
+
|
|
317
|
+
if array_svd.ndim == 1:
|
|
318
|
+
array_svd = array_svd[:, np.newaxis]
|
|
319
|
+
|
|
320
|
+
e_max = np.max(array_dist, axis=0)
|
|
321
|
+
e_min = np.min(array_dist, axis=0)
|
|
322
|
+
|
|
323
|
+
for j, ind in enumerate(idx_list):
|
|
324
|
+
|
|
325
|
+
# svd
|
|
326
|
+
u, s, vh = scipy.linalg.svd(array_svd[ind, :])
|
|
327
|
+
res_svd[j] = np.max(s) / np.min(s)
|
|
328
|
+
|
|
329
|
+
# dist
|
|
330
|
+
p = np.vstack((e_min, array_dist[ind, :], e_max))
|
|
331
|
+
d_var = np.zeros(array_dist.shape[1])
|
|
332
|
+
|
|
333
|
+
for i in range(array_dist.shape[1]):
|
|
334
|
+
p_sort = np.sort(p[:, i])
|
|
335
|
+
|
|
336
|
+
if p_sort[0] == p_sort[1]:
|
|
337
|
+
p_sort = p_sort[1:]
|
|
338
|
+
|
|
339
|
+
if p_sort[-2] == p_sort[-1]:
|
|
340
|
+
p_sort = p_sort[:-1]
|
|
341
|
+
|
|
342
|
+
d_var[i] = np.var(np.diff(p_sort))
|
|
343
|
+
|
|
344
|
+
res_dist[j] = np.mean(d_var)
|
|
345
|
+
|
|
346
|
+
return res_dist, res_svd
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def dist_mc(idx_list, array, ele_idx_1, ele_idx_2, mode="cols", **kwargs):
|
|
350
|
+
"""
|
|
351
|
+
Determines distance score and mutual coherence for given zap indices in idx_list. If c_max_idx is given,
|
|
352
|
+
the distance based score is calculated only for this element.
|
|
353
|
+
The condition number however is optimized for all elements in array.
|
|
354
|
+
|
|
355
|
+
Parameters
|
|
356
|
+
----------
|
|
357
|
+
idx_list : list of lists [n_combs][n_zaps]
|
|
358
|
+
Index lists of zaps containing different possible combinations. Usually only the last index changes.
|
|
359
|
+
array : np.ndarray of float [n_zaps x n_ele]
|
|
360
|
+
Electric field for different coil positions and elements.
|
|
361
|
+
mode : str, default: "cols"
|
|
362
|
+
Set if the mutual coherence is calculated w.r.t. columns or rows ("cols", "rows").
|
|
363
|
+
ele_idx_1 : np.ndarray of float [n_ele]
|
|
364
|
+
Element indices for which the dist optimization is performed for.
|
|
365
|
+
ele_idx_2 : np.ndarray of float [n_ele]
|
|
366
|
+
Element indices for which the mc optimization is performed for.
|
|
367
|
+
|
|
368
|
+
Returns
|
|
369
|
+
-------
|
|
370
|
+
res_dist : np.ndarray of float [n_combs]
|
|
371
|
+
Distance based score. Lower values indicate more equidistant sampling (better).
|
|
372
|
+
res_mc : np.ndarray of float [n_combs]
|
|
373
|
+
Mutual coherence. Lower values indicate more orthogonal e-field combinations (better).
|
|
374
|
+
"""
|
|
375
|
+
res_dist = np.zeros(len(idx_list))
|
|
376
|
+
res_mc = np.zeros(len(idx_list))
|
|
377
|
+
|
|
378
|
+
array_dist = array[:, ele_idx_1]
|
|
379
|
+
|
|
380
|
+
if array_dist.ndim == 1:
|
|
381
|
+
array_dist = array_dist[:, np.newaxis]
|
|
382
|
+
|
|
383
|
+
array_mc = array[:, ele_idx_2]
|
|
384
|
+
|
|
385
|
+
if array_mc.ndim == 1:
|
|
386
|
+
array_mc = array_mc[:, np.newaxis]
|
|
387
|
+
|
|
388
|
+
e_max = np.max(array_dist, axis=0)
|
|
389
|
+
e_min = np.min(array_dist, axis=0)
|
|
390
|
+
|
|
391
|
+
for j, ind in enumerate(idx_list):
|
|
392
|
+
|
|
393
|
+
# mc
|
|
394
|
+
if mode == "cols":
|
|
395
|
+
res_mc[j] = pynibs.mutual_coherence(array_mc[ind, :])
|
|
396
|
+
elif mode == "rows":
|
|
397
|
+
res_mc[j] = pynibs.mutual_coherence(array_mc[ind, :].transpose())
|
|
398
|
+
else:
|
|
399
|
+
raise NotImplementedError("Specified mode not implemented. Choose 'rows' or 'cols'.")
|
|
400
|
+
|
|
401
|
+
# dist
|
|
402
|
+
p = np.vstack((e_min, array_dist[ind, :], e_max))
|
|
403
|
+
d_var = np.zeros(array_dist.shape[1])
|
|
404
|
+
|
|
405
|
+
for i in range(array_dist.shape[1]):
|
|
406
|
+
p_sort = np.sort(p[:, i])
|
|
407
|
+
|
|
408
|
+
if p_sort[0] == p_sort[1]:
|
|
409
|
+
p_sort = p_sort[1:]
|
|
410
|
+
|
|
411
|
+
if p_sort[-2] == p_sort[-1]:
|
|
412
|
+
p_sort = p_sort[:-1]
|
|
413
|
+
|
|
414
|
+
d_var[i] = np.var(np.diff(p_sort))
|
|
415
|
+
|
|
416
|
+
res_dist[j] = np.mean(d_var)
|
|
417
|
+
|
|
418
|
+
return res_dist, res_mc
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
def coverage_prepare(idx_list, array, zap_idx, **kwargs):
|
|
422
|
+
"""Prepares coverage calculation.
|
|
423
|
+
Determines coverage distributions for elements in idx_list given the zaps in zap_idx
|
|
424
|
+
|
|
425
|
+
Parameters
|
|
426
|
+
----------
|
|
427
|
+
idx_list : list [n_ele]
|
|
428
|
+
Index lists of elements.
|
|
429
|
+
array : ndarray of float [n_zaps x n_ele]
|
|
430
|
+
Electric field for different coil positions and elements.
|
|
431
|
+
zap_idx : ndarray of int
|
|
432
|
+
Included zaps in coverage distribution.
|
|
433
|
+
|
|
434
|
+
Returns
|
|
435
|
+
-------
|
|
436
|
+
x : ndarray of float [200 x n_ele]
|
|
437
|
+
x-values of coverage distributions, defined in interval [0, 1] (element wise normalized electric field).
|
|
438
|
+
y : ndarray of float [200 x n_ele]
|
|
439
|
+
y-values of coverage distributions (element wise probability of already included e-fields).
|
|
440
|
+
"""
|
|
441
|
+
|
|
442
|
+
n_x = 200
|
|
443
|
+
|
|
444
|
+
x = np.zeros((n_x, len(idx_list)))
|
|
445
|
+
y = np.zeros((n_x, len(idx_list)))
|
|
446
|
+
|
|
447
|
+
kde = KernelDensity(bandwidth=0.03, kernel='gaussian')
|
|
448
|
+
|
|
449
|
+
for j, ind in enumerate(idx_list):
|
|
450
|
+
e_min = np.min(array[:, ind])
|
|
451
|
+
e_max = np.max(array[:, ind])
|
|
452
|
+
e_samples = (array[zap_idx, ind] - e_min) / (e_max - e_min)
|
|
453
|
+
|
|
454
|
+
if not isinstance(e_samples, np.ndarray):
|
|
455
|
+
e_samples = np.array([e_samples])
|
|
456
|
+
|
|
457
|
+
kde_ele = kde.fit(e_samples[:, np.newaxis])
|
|
458
|
+
x[:, j] = np.linspace(0, 1, n_x)
|
|
459
|
+
y[:, j] = np.exp(kde_ele.score_samples(x[:, j][:, np.newaxis]))
|
|
460
|
+
|
|
461
|
+
return x, y
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
def coverage(idx_list, array, x, y, ele_idx_1, **kwargs):
|
|
465
|
+
"""
|
|
466
|
+
Determine coverage score (likelihood) for given zap indices in idx_list
|
|
467
|
+
|
|
468
|
+
Parameters
|
|
469
|
+
----------
|
|
470
|
+
idx_list : list of lists [n_combs][n_zaps]
|
|
471
|
+
Index lists of zaps containing different possible combinations. Usually only the last index changes.
|
|
472
|
+
array : np.ndarray of float [n_zaps x n_ele]
|
|
473
|
+
Electric field for different coil positions and elements.
|
|
474
|
+
x : np.ndarray of float [200 x n_ele]
|
|
475
|
+
x-values of coverage distributions, defined in interval [0, 1] (element wise normalized electric field).
|
|
476
|
+
y : np.ndarray of float [200 x n_ele]
|
|
477
|
+
y-values of coverage distributions (element wise probability of already included e-fields).
|
|
478
|
+
ele_idx_1 : np.ndarray of float [n_roi]
|
|
479
|
+
Element indices for which the coverage optimization is performed for.
|
|
480
|
+
|
|
481
|
+
Returns
|
|
482
|
+
-------
|
|
483
|
+
res : np.ndarray of float [n_combs]
|
|
484
|
+
Coverage score (likelihood) for given electric field combinations. Lower values indicate that the
|
|
485
|
+
new zap fills a gap which was not covered before.
|
|
486
|
+
"""
|
|
487
|
+
|
|
488
|
+
p_e = np.zeros(len(idx_list))
|
|
489
|
+
|
|
490
|
+
array = array[:, ele_idx_1]
|
|
491
|
+
|
|
492
|
+
if array.ndim == 1:
|
|
493
|
+
array = array[:, np.newaxis]
|
|
494
|
+
|
|
495
|
+
for j, ind in enumerate(idx_list):
|
|
496
|
+
p_e_zap = np.zeros(array.shape[1])
|
|
497
|
+
|
|
498
|
+
# normalized e-fields of this zap in all elements
|
|
499
|
+
e_zap = (array[ind[-1], :] - np.min(array, axis=0)) / (np.max(array, axis=0) - np.min(array, axis=0))
|
|
500
|
+
|
|
501
|
+
# determine e-field coverage probability for every element
|
|
502
|
+
for i_ele in range(array.shape[1]):
|
|
503
|
+
p_e_zap[i_ele] = np.interp(x=e_zap[i_ele], xp=x[:, i_ele], fp=y[:, i_ele])
|
|
504
|
+
|
|
505
|
+
# accumulate e-field coverage over ever elements using log-likelihood
|
|
506
|
+
p_e_zap[p_e_zap <= 0] = 1e-100
|
|
507
|
+
p_e[j] = np.sum(p_e_zap)
|
|
508
|
+
|
|
509
|
+
return p_e
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
def fim(idx_list, array, ele_idx_1, e_opt, c=None, **kwargs):
|
|
513
|
+
"""
|
|
514
|
+
Determine difference between e-fields and optimal e-field determined using the Fisher Information Matrix.
|
|
515
|
+
|
|
516
|
+
Parameters
|
|
517
|
+
----------
|
|
518
|
+
idx_list : list of lists [n_combs][n_zaps]
|
|
519
|
+
Index lists of zaps containing different possible combinations. Usually only the last index changes.
|
|
520
|
+
array : np.ndarray of float [n_zaps x n_ele]
|
|
521
|
+
Electric field for different coil positions and elements.
|
|
522
|
+
ele_idx_1 : np.ndarray of float [n_roi]
|
|
523
|
+
Element indices for which the fim optimization is performed for.
|
|
524
|
+
e_opt : np.ndarray of float [n_roi]
|
|
525
|
+
Optimal electric field value(s) (target) determined by FIM method.
|
|
526
|
+
c : np.ndarray of float [n_ele], optional
|
|
527
|
+
Congruence factor map normalized to 1 (whole ROI) used to weight the difference between the optimal e-field
|
|
528
|
+
and the candidate e-field. If None, no weighting is applied.
|
|
529
|
+
|
|
530
|
+
Returns
|
|
531
|
+
-------
|
|
532
|
+
res : np.ndarray of float [n_combs]
|
|
533
|
+
Difference between e-fields and optimal e-field.
|
|
534
|
+
"""
|
|
535
|
+
|
|
536
|
+
if c is None:
|
|
537
|
+
c = np.ones(array.shape[1])
|
|
538
|
+
|
|
539
|
+
res = np.zeros(len(idx_list))
|
|
540
|
+
|
|
541
|
+
array = array[:, ele_idx_1]
|
|
542
|
+
|
|
543
|
+
if array.ndim == 1:
|
|
544
|
+
array = array[:, np.newaxis]
|
|
545
|
+
|
|
546
|
+
for j, ind in enumerate(idx_list):
|
|
547
|
+
res[j] = np.linalg.norm((e_opt - array[ind[-1], ele_idx_1]) * c[ele_idx_1])
|
|
548
|
+
|
|
549
|
+
return res
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
def fim_svd(idx_list, array, ele_idx_1, ele_idx_2, e_opt, c=None, **kwargs):
|
|
553
|
+
"""
|
|
554
|
+
Determine difference between e-fields and optimal e-field determined using the Fisher Information Matrix and
|
|
555
|
+
condition number.
|
|
556
|
+
|
|
557
|
+
Parameters
|
|
558
|
+
----------
|
|
559
|
+
idx_list : list of lists [n_combs][n_zaps]
|
|
560
|
+
Index lists of zaps containing different possible combinations. Usually only the last index changes.
|
|
561
|
+
array : np.ndarray of float [n_zaps x n_ele]
|
|
562
|
+
Electric field for different coil positions and elements.
|
|
563
|
+
ele_idx_1 : np.ndarray of float [n_roi_1]
|
|
564
|
+
Element indices for which the fim optimization is performed for.
|
|
565
|
+
ele_idx_2 : np.ndarray of float [n_roi_2]
|
|
566
|
+
Element indices for which the svd optimization is performed for.
|
|
567
|
+
e_opt : float
|
|
568
|
+
Optimal electric field value (target) determined by FIM method.
|
|
569
|
+
c : np.ndarray of float [n_ele], optional
|
|
570
|
+
Congruence factor map normalized to 1 (whole ROI) used to weight the difference between the optimal e-field
|
|
571
|
+
and the candidate e-field. If None, no weighting is applied.
|
|
572
|
+
|
|
573
|
+
Returns
|
|
574
|
+
-------
|
|
575
|
+
res_fim : np.ndarray of float [n_combs]
|
|
576
|
+
Difference between e-fields and optimal e-field.
|
|
577
|
+
res_svd : np.ndarray of float [n_combs]
|
|
578
|
+
Condition number. Lower values indicate more orthogonal e-field combinations (better)
|
|
579
|
+
"""
|
|
580
|
+
|
|
581
|
+
if c is None:
|
|
582
|
+
c = np.ones(array.shape[1])
|
|
583
|
+
|
|
584
|
+
res_fim = np.zeros(len(idx_list))
|
|
585
|
+
res_svd = np.zeros(len(idx_list))
|
|
586
|
+
|
|
587
|
+
array_fim = array[:, ele_idx_1]
|
|
588
|
+
|
|
589
|
+
if array_fim.ndim == 1:
|
|
590
|
+
array_fim = array_fim[:, np.newaxis]
|
|
591
|
+
|
|
592
|
+
array_svd = array[:, ele_idx_2]
|
|
593
|
+
|
|
594
|
+
if array_svd.ndim == 1:
|
|
595
|
+
array_svd = array_svd[:, np.newaxis]
|
|
596
|
+
|
|
597
|
+
for j, ind in enumerate(idx_list):
|
|
598
|
+
# fim
|
|
599
|
+
intensity = np.mean(array_fim[ind[-1], :] / e_opt)
|
|
600
|
+
res_fim[j] = np.linalg.norm((e_opt - intensity * array_fim[ind[-1], :]) * c[ele_idx_1])
|
|
601
|
+
|
|
602
|
+
# svd
|
|
603
|
+
u, s, vh = scipy.linalg.svd(array_svd[ind, :])
|
|
604
|
+
res_svd[j] = np.max(s) / np.min(s)
|
|
605
|
+
|
|
606
|
+
return res_fim, res_svd
|
|
607
|
+
|
|
608
|
+
|
|
609
|
+
def fim_mc(idx_list, array, ele_idx_1, ele_idx_2, e_opt, c=None, mode="rows", **kwargs):
|
|
610
|
+
"""
|
|
611
|
+
Determine difference between e-fields and optimal e-field determined using the Fisher Information Matrix and
|
|
612
|
+
mutual coherence.
|
|
613
|
+
|
|
614
|
+
Parameters
|
|
615
|
+
----------
|
|
616
|
+
idx_list : list of lists [n_combs][n_zaps]
|
|
617
|
+
Index lists of zaps containing different possible combinations. Usually only the last index changes.
|
|
618
|
+
array : np.ndarray of float [n_zaps x n_ele]
|
|
619
|
+
Electric field for different coil positions and elements.
|
|
620
|
+
ele_idx_1 : np.ndarray of float [n_roi_1]
|
|
621
|
+
Element indices for which the fim optimization is performed for.
|
|
622
|
+
ele_idx_2 : np.ndarray of float [n_roi_2]
|
|
623
|
+
Element indices for which the mc optimization is performed for.
|
|
624
|
+
e_opt : float
|
|
625
|
+
Optimal electric field value (target) determined by FIM method.
|
|
626
|
+
c : np.ndarray of float [n_ele], optional
|
|
627
|
+
Congruence factor map normalized to 1 (whole ROI) used to weight the difference between the optimal e-field
|
|
628
|
+
and the candidate e-field. If None, no weighting is applied.
|
|
629
|
+
mode : str
|
|
630
|
+
|
|
631
|
+
Returns
|
|
632
|
+
-------
|
|
633
|
+
res_fim : np.ndarray of float [n_combs]
|
|
634
|
+
Difference between e-fields and optimal e-field.
|
|
635
|
+
res_mc : np.ndarray of float [n_combs]
|
|
636
|
+
Mutual coherence. Lower values indicate more orthogonal e-field combinations (better)
|
|
637
|
+
"""
|
|
638
|
+
|
|
639
|
+
if c is None:
|
|
640
|
+
c = np.ones(array.shape[1])
|
|
641
|
+
|
|
642
|
+
res_fim = np.zeros(len(idx_list))
|
|
643
|
+
res_mc = np.zeros(len(idx_list))
|
|
644
|
+
|
|
645
|
+
array_fim = array[:, ele_idx_1]
|
|
646
|
+
|
|
647
|
+
if array_fim.ndim == 1:
|
|
648
|
+
array_fim = array_fim[:, np.newaxis]
|
|
649
|
+
|
|
650
|
+
array_mc = array[:, ele_idx_2]
|
|
651
|
+
|
|
652
|
+
if array_mc.ndim == 1:
|
|
653
|
+
array_mc = array_mc[:, np.newaxis]
|
|
654
|
+
|
|
655
|
+
for j, ind in enumerate(idx_list):
|
|
656
|
+
# fim
|
|
657
|
+
res_fim[j] = np.linalg.norm((e_opt - array_fim[ind[-1], :]) * c[ele_idx_1])
|
|
658
|
+
|
|
659
|
+
# mc
|
|
660
|
+
if mode == "cols":
|
|
661
|
+
res_mc[j] = pynibs.mutual_coherence(array_mc[ind, :])
|
|
662
|
+
elif mode == "rows":
|
|
663
|
+
res_mc[j] = pynibs.mutual_coherence(array_mc[ind, :].transpose())
|
|
664
|
+
else:
|
|
665
|
+
raise NotImplementedError("Specified mode not implemented. Choose 'rows' or 'cols'.")
|
|
666
|
+
|
|
667
|
+
return res_fim, res_mc
|
|
668
|
+
|
|
669
|
+
|
|
670
|
+
def rowvec_diff(candidate_coil_idcs, selected_coil_idcs, efields_diff_mat):
|
|
671
|
+
"""
|
|
672
|
+
Given a difference matrix (e.g. of row vectors/coil configurations) this function
|
|
673
|
+
returns the coil configuration out of all available configurations exhibiting the
|
|
674
|
+
highest minimum difference to the already selected configurations.
|
|
675
|
+
|
|
676
|
+
Parameters
|
|
677
|
+
----------
|
|
678
|
+
candidate_coil_idcs : np.ndarry[int]
|
|
679
|
+
List of indices of coil configurations that are still available to pick for the optiized sequence.
|
|
680
|
+
selected_coil_idcs : np.ndarray[int]
|
|
681
|
+
List of indices of coil configurations that have already been selected for the optimized sequence.
|
|
682
|
+
efields_diff_mat : np.ndarray[float], [n_coil,n_coil]
|
|
683
|
+
Difference matrix, where each cell denotes the magnitude of the difference vector between
|
|
684
|
+
two coil configurations (determined by row_idx,col_idx).
|
|
685
|
+
|
|
686
|
+
Returns
|
|
687
|
+
-------
|
|
688
|
+
coil_idx: int
|
|
689
|
+
Index of coil configuration with maximal minimal difference to the set of already selected coil configurations.
|
|
690
|
+
"""
|
|
691
|
+
|
|
692
|
+
# min_diff_selected_to_all_coil_pos = matrix with:
|
|
693
|
+
# rows -> set of selected coil configurations (selected_coil_idcs)
|
|
694
|
+
# columns -> available coil configuration for optimization (candidate_coil_idcs)
|
|
695
|
+
min_diff_selected_to_all_coil_pos = np.min(efields_diff_mat[selected_coil_idcs][:, candidate_coil_idcs], axis=0)
|
|
696
|
+
|
|
697
|
+
# returned index valid in the "idx_list" array
|
|
698
|
+
return np.argmax(min_diff_selected_to_all_coil_pos), np.max(min_diff_selected_to_all_coil_pos)
|
pynibs/pckg/__init__.py
ADDED
|
File without changes
|
|
Binary file
|
|
File without changes
|
|
Binary file
|