pyNIBS 0.2024.8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. pyNIBS-0.2024.8.dist-info/LICENSE +623 -0
  2. pyNIBS-0.2024.8.dist-info/METADATA +723 -0
  3. pyNIBS-0.2024.8.dist-info/RECORD +107 -0
  4. pyNIBS-0.2024.8.dist-info/WHEEL +5 -0
  5. pyNIBS-0.2024.8.dist-info/top_level.txt +1 -0
  6. pynibs/__init__.py +34 -0
  7. pynibs/coil.py +1367 -0
  8. pynibs/congruence/__init__.py +15 -0
  9. pynibs/congruence/congruence.py +1108 -0
  10. pynibs/congruence/ext_metrics.py +257 -0
  11. pynibs/congruence/stimulation_threshold.py +318 -0
  12. pynibs/data/configuration_exp0.yaml +59 -0
  13. pynibs/data/configuration_linear_MEP.yaml +61 -0
  14. pynibs/data/configuration_linear_RT.yaml +61 -0
  15. pynibs/data/configuration_sigmoid4.yaml +68 -0
  16. pynibs/data/network mapping configuration/configuration guide.md +238 -0
  17. pynibs/data/network mapping configuration/configuration_TEMPLATE.yaml +42 -0
  18. pynibs/data/network mapping configuration/configuration_for_testing.yaml +43 -0
  19. pynibs/data/network mapping configuration/configuration_modelTMS.yaml +43 -0
  20. pynibs/data/network mapping configuration/configuration_reg_isi_05.yaml +43 -0
  21. pynibs/data/network mapping configuration/output_documentation.md +185 -0
  22. pynibs/data/network mapping configuration/recommendations_for_accuracy_threshold.md +77 -0
  23. pynibs/data/neuron/models/L23_PC_cADpyr_biphasic_v1.csv +1281 -0
  24. pynibs/data/neuron/models/L23_PC_cADpyr_monophasic_v1.csv +1281 -0
  25. pynibs/data/neuron/models/L4_LBC_biphasic_v1.csv +1281 -0
  26. pynibs/data/neuron/models/L4_LBC_monophasic_v1.csv +1281 -0
  27. pynibs/data/neuron/models/L4_NBC_biphasic_v1.csv +1281 -0
  28. pynibs/data/neuron/models/L4_NBC_monophasic_v1.csv +1281 -0
  29. pynibs/data/neuron/models/L4_SBC_biphasic_v1.csv +1281 -0
  30. pynibs/data/neuron/models/L4_SBC_monophasic_v1.csv +1281 -0
  31. pynibs/data/neuron/models/L5_TTPC2_cADpyr_biphasic_v1.csv +1281 -0
  32. pynibs/data/neuron/models/L5_TTPC2_cADpyr_monophasic_v1.csv +1281 -0
  33. pynibs/expio/Mep.py +1518 -0
  34. pynibs/expio/__init__.py +8 -0
  35. pynibs/expio/brainsight.py +979 -0
  36. pynibs/expio/brainvis.py +71 -0
  37. pynibs/expio/cobot.py +239 -0
  38. pynibs/expio/exp.py +1876 -0
  39. pynibs/expio/fit_funs.py +287 -0
  40. pynibs/expio/localite.py +1987 -0
  41. pynibs/expio/signal_ced.py +51 -0
  42. pynibs/expio/visor.py +624 -0
  43. pynibs/freesurfer.py +502 -0
  44. pynibs/hdf5_io/__init__.py +10 -0
  45. pynibs/hdf5_io/hdf5_io.py +1857 -0
  46. pynibs/hdf5_io/xdmf.py +1542 -0
  47. pynibs/mesh/__init__.py +3 -0
  48. pynibs/mesh/mesh_struct.py +1394 -0
  49. pynibs/mesh/transformations.py +866 -0
  50. pynibs/mesh/utils.py +1103 -0
  51. pynibs/models/_TMS.py +211 -0
  52. pynibs/models/__init__.py +0 -0
  53. pynibs/muap.py +392 -0
  54. pynibs/neuron/__init__.py +2 -0
  55. pynibs/neuron/neuron_regression.py +284 -0
  56. pynibs/neuron/util.py +58 -0
  57. pynibs/optimization/__init__.py +5 -0
  58. pynibs/optimization/multichannel.py +278 -0
  59. pynibs/optimization/opt_mep.py +152 -0
  60. pynibs/optimization/optimization.py +1445 -0
  61. pynibs/optimization/workhorses.py +698 -0
  62. pynibs/pckg/__init__.py +0 -0
  63. pynibs/pckg/biosig/biosig4c++-1.9.5.src_fixed.tar.gz +0 -0
  64. pynibs/pckg/libeep/__init__.py +0 -0
  65. pynibs/pckg/libeep/pyeep.so +0 -0
  66. pynibs/regression/__init__.py +11 -0
  67. pynibs/regression/dual_node_detection.py +2375 -0
  68. pynibs/regression/regression.py +2984 -0
  69. pynibs/regression/score_types.py +0 -0
  70. pynibs/roi/__init__.py +2 -0
  71. pynibs/roi/roi.py +895 -0
  72. pynibs/roi/roi_structs.py +1233 -0
  73. pynibs/subject.py +1009 -0
  74. pynibs/tensor_scaling.py +144 -0
  75. pynibs/tests/data/InstrumentMarker20200225163611937.xml +19 -0
  76. pynibs/tests/data/TriggerMarkers_Coil0_20200225163443682.xml +14 -0
  77. pynibs/tests/data/TriggerMarkers_Coil1_20200225170337572.xml +6373 -0
  78. pynibs/tests/data/Xdmf.dtd +89 -0
  79. pynibs/tests/data/brainsight_niiImage_nifticoord.txt +145 -0
  80. pynibs/tests/data/brainsight_niiImage_nifticoord_largefile.txt +1434 -0
  81. pynibs/tests/data/brainsight_niiImage_niifticoord_mixedtargets.txt +47 -0
  82. pynibs/tests/data/create_subject_testsub.py +332 -0
  83. pynibs/tests/data/data.hdf5 +0 -0
  84. pynibs/tests/data/geo.hdf5 +0 -0
  85. pynibs/tests/test_coil.py +474 -0
  86. pynibs/tests/test_elements2nodes.py +100 -0
  87. pynibs/tests/test_hdf5_io/test_xdmf.py +61 -0
  88. pynibs/tests/test_mesh_transformations.py +123 -0
  89. pynibs/tests/test_mesh_utils.py +143 -0
  90. pynibs/tests/test_nnav_imports.py +101 -0
  91. pynibs/tests/test_quality_measures.py +117 -0
  92. pynibs/tests/test_regressdata.py +289 -0
  93. pynibs/tests/test_roi.py +17 -0
  94. pynibs/tests/test_rotations.py +86 -0
  95. pynibs/tests/test_subject.py +71 -0
  96. pynibs/tests/test_util.py +24 -0
  97. pynibs/tms_pulse.py +34 -0
  98. pynibs/util/__init__.py +4 -0
  99. pynibs/util/dosing.py +233 -0
  100. pynibs/util/quality_measures.py +562 -0
  101. pynibs/util/rotations.py +340 -0
  102. pynibs/util/simnibs.py +763 -0
  103. pynibs/util/util.py +727 -0
  104. pynibs/visualization/__init__.py +2 -0
  105. pynibs/visualization/para.py +4372 -0
  106. pynibs/visualization/plot_2D.py +137 -0
  107. pynibs/visualization/render_3D.py +347 -0
@@ -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)
File without changes
File without changes
Binary file