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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""
|
|
2
|
-
The `
|
|
2
|
+
The `coil_opt.py` module provides functions for optimizing coil placements in Transcranial Magnetic Stimulation
|
|
3
3
|
(TMS) based on given electric field matrices and fMRI statistics. It includes functions for identifying optimal coil
|
|
4
4
|
placement regions, calculating the gain map between a reference and an optimized sequence of electric fields,
|
|
5
5
|
and performing virtual online optimization to determine the congruence factor.
|
|
@@ -7,19 +7,15 @@ and performing virtual online optimization to determine the congruence factor.
|
|
|
7
7
|
The module includes the following functions:
|
|
8
8
|
|
|
9
9
|
- `rowvec_diff()`: This function returns the coil configuration out of all available configurations exhibiting the
|
|
10
|
-
highest minimum difference to the already selected configurations.
|
|
11
|
-
|
|
10
|
+
highest minimum difference to the already selected configurations.
|
|
12
11
|
- `get_optimal_coil_positions()`: This function determines a set of optimal coil positions for TMS regression analysis.
|
|
13
|
-
|
|
14
12
|
- `online_optimization()`: This function performs virtual online optimization to determine the congruence factor.
|
|
15
|
-
After an initial set of coil positions, the algorithm iteratively optimizes the next coil position based on the
|
|
16
|
-
virtually measured MEP data.
|
|
17
|
-
|
|
13
|
+
After an initial set of coil positions, the algorithm iteratively optimizes the next coil position based on the
|
|
14
|
+
virtually measured MEP data.
|
|
18
15
|
- `calc_opt_gain_map()`: This function calculates the gain map between a reference e_matrix (e.g. from random
|
|
19
|
-
sampling) and an optimized sequence of electric fields for mapping.
|
|
20
|
-
|
|
16
|
+
sampling) and an optimized sequence of electric fields for mapping.
|
|
21
17
|
- `optimal_coilplacement_region()`: This function identifies the optimal coil placement regions based on given
|
|
22
|
-
electric field (E-field) matrices and fMRI statistics.
|
|
18
|
+
electric field (E-field) matrices and fMRI statistics.
|
|
23
19
|
|
|
24
20
|
Each function in this module is documented with docstrings providing more detailed information about its purpose,
|
|
25
21
|
parameters, and return values.
|
|
@@ -31,8 +27,6 @@ import h5py
|
|
|
31
27
|
import numpy as np
|
|
32
28
|
import multiprocessing
|
|
33
29
|
from functools import partial
|
|
34
|
-
from matplotlib import pyplot as plt
|
|
35
|
-
|
|
36
30
|
import pynibs
|
|
37
31
|
|
|
38
32
|
|
|
@@ -42,11 +36,13 @@ def rowvec_diff(candidate_coil_idcs, selected_coil_idcs, efields_diff_mat):
|
|
|
42
36
|
returns the coil configuration out of all available configurations exhibiting the
|
|
43
37
|
highest minimum difference to the already selected configurations.
|
|
44
38
|
|
|
45
|
-
|
|
39
|
+
Parameters
|
|
40
|
+
----------
|
|
41
|
+
candidate_coil_idcs : np.ndarry[int]
|
|
46
42
|
List of indices of coil configurations that are still available to pick for the optiized sequence.
|
|
47
|
-
|
|
43
|
+
selected_coil_idcs : np.ndarray[int]
|
|
48
44
|
List of indices of coil configurations that have already been selected for the optimized sequence.
|
|
49
|
-
|
|
45
|
+
efields_diff_mat : np.ndarray[float], [n_coil,n_coil]
|
|
50
46
|
Difference matrix, where each cell denotes the magnitude of the difference vector between
|
|
51
47
|
two coil configurations (determined by row_idx,col_idx).
|
|
52
48
|
|
|
@@ -55,7 +51,6 @@ def rowvec_diff(candidate_coil_idcs, selected_coil_idcs, efields_diff_mat):
|
|
|
55
51
|
coil_idx: int
|
|
56
52
|
index of coil configuration with maximal minimal difference to the set of already selected coil configurations.
|
|
57
53
|
"""
|
|
58
|
-
|
|
59
54
|
# min_diff_selected_to_all_coil_pos = matrix with:
|
|
60
55
|
# rows -> set of selected coil configurations (selected_coil_idcs)
|
|
61
56
|
# columns -> available coil configuration for optimization (candidate_coil_idcs)
|
|
@@ -114,9 +109,9 @@ def get_optimal_coil_positions(
|
|
|
114
109
|
n_stim : int
|
|
115
110
|
Maximum number of stimulations.
|
|
116
111
|
ele_idx_1 : np.ndarray of int, optional
|
|
117
|
-
Element indices the first optimization goal is performed for, If None, all elements are
|
|
112
|
+
Element indices the first optimization goal is performed for, If None, all elements are considered.
|
|
118
113
|
ele_idx_2 : np.ndarray of int, optional
|
|
119
|
-
Element indices the first optimization goal is performed for. If None, all elements are
|
|
114
|
+
Element indices the first optimization goal is performed for. If None, all elements are considered.
|
|
120
115
|
n_cpu : int
|
|
121
116
|
Number of threads.
|
|
122
117
|
fn_out_hdf5 : str, optional
|
|
@@ -142,7 +137,7 @@ def get_optimal_coil_positions(
|
|
|
142
137
|
metrics_weights : list of float [2], default: [0.5, 0.5]
|
|
143
138
|
Weights of optimization criteria in case of multiple goal functions (e_all_coil_pos.g. fim_svd).
|
|
144
139
|
Higher weight means higher importance for the respective criteria.
|
|
145
|
-
By default both optimization criteria are weighted equally [0.5, 0.5].
|
|
140
|
+
By default, both optimization criteria are weighted equally [0.5, 0.5].
|
|
146
141
|
overwrite : bool, default: True
|
|
147
142
|
Overwrite existing solutions or read existing hdf5 file and continue optimization.
|
|
148
143
|
verbose : bool, default: True
|
|
@@ -159,7 +154,7 @@ def get_optimal_coil_positions(
|
|
|
159
154
|
Not required for any other metric than FIM.
|
|
160
155
|
fim_mso_didt_conversion_factor : float, default: 1.43
|
|
161
156
|
Factor to convert between realized current (dI/dt) and percentage of maximum stimulator output (%MSO).
|
|
162
|
-
Defaults to 1.43 describing the factor of a
|
|
157
|
+
Defaults to 1.43 describing the factor of a MagVenture Pro with an MCF-B65 coil.
|
|
163
158
|
Not required for any other metric than FIM.
|
|
164
159
|
fim_visited_positions_e_mat : np.ndarray[float], (len(zap_idx_opt], n_ele), optional
|
|
165
160
|
The efield matrix computed using the actually approached coil configurations.
|
|
@@ -177,7 +172,7 @@ def get_optimal_coil_positions(
|
|
|
177
172
|
Number of refits used in the mag(E)<>p2p regression during FIM optimization.
|
|
178
173
|
Not required for any other metric than FIM.
|
|
179
174
|
fim_debug_screenshot_dir_fn : str
|
|
180
|
-
String representation of the fully qualified path to a directory where a 3D rendering of
|
|
175
|
+
String representation of the fully qualified path to a directory where a 3D rendering of
|
|
181
176
|
the FIM optimal coil positions, ie coil positions that can reach the FIM optimal e-field strength
|
|
182
177
|
at the current target hotspot given the MSO bounds [fim_rmt_mso, 100], should be saved.
|
|
183
178
|
Not required for any other metric than FIM.
|
|
@@ -288,8 +283,9 @@ def get_optimal_coil_positions(
|
|
|
288
283
|
if criterion == "rowvec_diff":
|
|
289
284
|
# Preparation for the rowvec difference metric is independent of the number of requested stimulation (n_stim):
|
|
290
285
|
# Compute the difference matrix.
|
|
291
|
-
workhorse_prepare = partial(pynibs.optimization.workhorses.rowvec_diff_prepare, array=e_all_coil_pos,
|
|
292
|
-
|
|
286
|
+
workhorse_prepare = partial(pynibs.optimization.workhorses.rowvec_diff_prepare, array=e_all_coil_pos,
|
|
287
|
+
ele_idx_1=ele_idx_1)
|
|
288
|
+
coil_idx_list_chunks = pynibs.util.utils.compute_chunks(list(range(e_all_coil_pos.shape[0])), n_cpu)
|
|
293
289
|
res = pool.map(workhorse_prepare, coil_idx_list_chunks)
|
|
294
290
|
|
|
295
291
|
# sum all difference matrices up = create upper triangle difference matrix
|
|
@@ -329,9 +325,9 @@ def get_optimal_coil_positions(
|
|
|
329
325
|
|
|
330
326
|
# preparatory functions of some of the result metrics
|
|
331
327
|
if criterion == "coverage":
|
|
332
|
-
workhorse_prepare = partial(workhorses.coverage_prepare, array=e_all_coil_pos,
|
|
328
|
+
workhorse_prepare = partial(pynibs.optimization.workhorses.coverage_prepare, array=e_all_coil_pos,
|
|
333
329
|
zap_idx=zap_idx_e_opt[:idx_in_result_list - 1])
|
|
334
|
-
ele_idx_list_chunks = pynibs.compute_chunks([j for j in range(e_all_coil_pos.shape[1])], n_cpu)
|
|
330
|
+
ele_idx_list_chunks = pynibs.util.utils.compute_chunks([j for j in range(e_all_coil_pos.shape[1])], n_cpu)
|
|
335
331
|
res = pool.map(workhorse_prepare, ele_idx_list_chunks)
|
|
336
332
|
|
|
337
333
|
x = np.zeros((1, 1))
|
|
@@ -345,7 +341,7 @@ def get_optimal_coil_positions(
|
|
|
345
341
|
x = np.hstack((x, res[j][0]))
|
|
346
342
|
y = np.hstack((y, res[j][1]))
|
|
347
343
|
|
|
348
|
-
workhorse_partial = partial(workhorses.coverage, array=e_all_coil_pos, x=x, y=y)
|
|
344
|
+
workhorse_partial = partial(pynibs.optimization.workhorses.coverage, array=e_all_coil_pos, x=x, y=y)
|
|
349
345
|
# Preprocessing of metrics combined with FIM:
|
|
350
346
|
# - determine the required %MSO for each candidate location via FIM
|
|
351
347
|
# - determine valid candidate locations that can achieve a FIM optimal mag(E) within the valid range of
|
|
@@ -368,7 +364,7 @@ def get_optimal_coil_positions(
|
|
|
368
364
|
raise ValueError("[Error] get_optimal_coil_positions: requested FIM optimal sampling without"
|
|
369
365
|
"specifying a specific resting motor threshold (fim_rmt_mso).")
|
|
370
366
|
|
|
371
|
-
if fim_fit_fun == pynibs.sigmoid4 or fim_fit_fun == pynibs.sigmoid4_log:
|
|
367
|
+
if fim_fit_fun == pynibs.expio.sigmoid4 or fim_fit_fun == pynibs.expio.sigmoid4_log:
|
|
372
368
|
default_sigmoid_params = {
|
|
373
369
|
"x0": 0.5,
|
|
374
370
|
"y0": 1e-10,
|
|
@@ -415,7 +411,7 @@ def get_optimal_coil_positions(
|
|
|
415
411
|
e_realized_in_hotspot_np = np.zeros((e_realized_in_hotspot.shape[0], 1))
|
|
416
412
|
e_realized_in_hotspot_np[:, 0] = e_realized_in_hotspot
|
|
417
413
|
|
|
418
|
-
_, regression_sigmoid_fitparams = pynibs.regress_data(
|
|
414
|
+
_, regression_sigmoid_fitparams = pynibs.regression.regress_data(
|
|
419
415
|
e_matrix=e_realized_in_hotspot_np,
|
|
420
416
|
mep=fim_p2p_amps,
|
|
421
417
|
fun=fim_fit_fun,
|
|
@@ -517,85 +513,109 @@ def get_optimal_coil_positions(
|
|
|
517
513
|
|
|
518
514
|
# prepare the result metrics for the parallel execution
|
|
519
515
|
if criterion == "svd":
|
|
520
|
-
workhorse_partial = partial(workhorses.svd, array=e_all_coil_pos,
|
|
516
|
+
workhorse_partial = partial(pynibs.optimization.workhorses.svd, array=e_all_coil_pos,
|
|
517
|
+
ele_idx_1=ele_idx_1)
|
|
521
518
|
|
|
522
519
|
elif criterion == "dist":
|
|
523
|
-
workhorse_partial = partial(workhorses.dist, array=e_all_coil_pos,
|
|
520
|
+
workhorse_partial = partial(pynibs.optimization.workhorses.dist, array=e_all_coil_pos,
|
|
521
|
+
ele_idx_1=ele_idx_1)
|
|
524
522
|
|
|
525
523
|
elif criterion == "mc_cols":
|
|
526
524
|
e_all_coil_pos = e_all_coil_pos - np.mean(e_all_coil_pos[:, ele_idx_1], axis=1)[:, np.newaxis]
|
|
527
|
-
workhorse_partial = partial(workhorses.mc, array=e_all_coil_pos,
|
|
525
|
+
workhorse_partial = partial(pynibs.optimization.workhorses.mc, array=e_all_coil_pos,
|
|
526
|
+
ele_idx_1=ele_idx_1,
|
|
527
|
+
mode="cols")
|
|
528
528
|
|
|
529
529
|
elif criterion == "mc_rows":
|
|
530
|
-
workhorse_partial = partial(workhorses.mc, array=e_all_coil_pos,
|
|
530
|
+
workhorse_partial = partial(pynibs.optimization.workhorses.mc, array=e_all_coil_pos,
|
|
531
|
+
ele_idx_1=ele_idx_1, mode="rows")
|
|
531
532
|
|
|
532
533
|
elif criterion == "variability":
|
|
533
|
-
workhorse_partial = partial(workhorses.variability, array=e_all_coil_pos,
|
|
534
|
+
workhorse_partial = partial(pynibs.optimization.workhorses.variability, array=e_all_coil_pos,
|
|
535
|
+
ele_idx_1=ele_idx_1)
|
|
534
536
|
|
|
535
537
|
elif criterion == "dist_mc_cols":
|
|
536
538
|
if metrics_weights[0] == 0:
|
|
537
|
-
workhorse_partial = partial(workhorses.mc, array=e_all_coil_pos,
|
|
539
|
+
workhorse_partial = partial(pynibs.optimization.workhorses.mc, array=e_all_coil_pos,
|
|
540
|
+
ele_idx_1=ele_idx_2, mode="cols")
|
|
538
541
|
elif metrics_weights[1] == 0:
|
|
539
|
-
workhorse_partial = partial(workhorses.dist, array=e_all_coil_pos,
|
|
542
|
+
workhorse_partial = partial(pynibs.optimization.workhorses.dist, array=e_all_coil_pos,
|
|
543
|
+
ele_idx_1=ele_idx_1)
|
|
540
544
|
else:
|
|
541
|
-
workhorse_partial = partial(workhorses.dist_mc, array=e_all_coil_pos,
|
|
545
|
+
workhorse_partial = partial(pynibs.optimization.workhorses.dist_mc, array=e_all_coil_pos,
|
|
546
|
+
ele_idx_1=ele_idx_1,
|
|
542
547
|
ele_idx_2=ele_idx_2,
|
|
543
548
|
mode="cols")
|
|
544
549
|
|
|
545
550
|
elif criterion == "dist_mc_rows":
|
|
546
551
|
if metrics_weights[0] == 0:
|
|
547
|
-
workhorse_partial = partial(workhorses.mc, array=e_all_coil_pos,
|
|
552
|
+
workhorse_partial = partial(pynibs.optimization.workhorses.mc, array=e_all_coil_pos,
|
|
553
|
+
ele_idx_1=ele_idx_2, mode="rows")
|
|
548
554
|
elif metrics_weights[1] == 0:
|
|
549
|
-
workhorse_partial = partial(workhorses.dist, array=e_all_coil_pos,
|
|
555
|
+
workhorse_partial = partial(pynibs.optimization.workhorses.dist, array=e_all_coil_pos,
|
|
556
|
+
ele_idx_1=ele_idx_1)
|
|
550
557
|
else:
|
|
551
|
-
workhorse_partial = partial(workhorses.dist_mc, array=e_all_coil_pos,
|
|
558
|
+
workhorse_partial = partial(pynibs.optimization.workhorses.dist_mc, array=e_all_coil_pos,
|
|
559
|
+
ele_idx_1=ele_idx_1,
|
|
552
560
|
ele_idx_2=ele_idx_2,
|
|
553
561
|
mode="rows")
|
|
554
562
|
|
|
555
563
|
elif criterion == "dist_svd":
|
|
556
564
|
if metrics_weights[0] == 0:
|
|
557
|
-
workhorse_partial = partial(workhorses.svd, array=e_all_coil_pos,
|
|
565
|
+
workhorse_partial = partial(pynibs.optimization.workhorses.svd, array=e_all_coil_pos,
|
|
566
|
+
ele_idx_1=ele_idx_2)
|
|
558
567
|
elif metrics_weights[1] == 0:
|
|
559
|
-
workhorse_partial = partial(workhorses.dist, array=e_all_coil_pos,
|
|
568
|
+
workhorse_partial = partial(pynibs.optimization.workhorses.dist, array=e_all_coil_pos,
|
|
569
|
+
ele_idx_1=ele_idx_1)
|
|
560
570
|
else:
|
|
561
|
-
workhorse_partial = partial(workhorses.dist_svd, array=e_all_coil_pos,
|
|
571
|
+
workhorse_partial = partial(pynibs.optimization.workhorses.dist_svd, array=e_all_coil_pos,
|
|
572
|
+
ele_idx_1=ele_idx_1,
|
|
562
573
|
ele_idx_2=ele_idx_2)
|
|
563
574
|
|
|
564
575
|
elif criterion == "fim":
|
|
565
|
-
workhorse_partial = partial(workhorses.fim, array=e_all_coil_pos, ele_idx_1=ele_idx_1,
|
|
576
|
+
workhorse_partial = partial(pynibs.optimization.workhorses.fim, array=e_all_coil_pos, ele_idx_1=ele_idx_1,
|
|
566
577
|
e_opt=e_opt_real_in_hotspot, c=regression_cmap_normalized)
|
|
567
578
|
|
|
568
579
|
elif criterion == "fim_svd":
|
|
569
580
|
if metrics_weights[0] == 0:
|
|
570
|
-
workhorse_partial = partial(workhorses.svd, array=e_all_coil_pos,
|
|
581
|
+
workhorse_partial = partial(pynibs.optimization.workhorses.svd, array=e_all_coil_pos,
|
|
582
|
+
ele_idx_1=ele_idx_2)
|
|
571
583
|
elif metrics_weights[1] == 0:
|
|
572
|
-
workhorse_partial = partial(workhorses.fim, array=e_all_coil_pos,
|
|
584
|
+
workhorse_partial = partial(pynibs.optimization.workhorses.fim, array=e_all_coil_pos,
|
|
585
|
+
ele_idx_1=ele_idx_1,
|
|
573
586
|
e_opt=e_opt_real_in_hotspot, c=regression_cmap_normalized)
|
|
574
587
|
else:
|
|
575
|
-
workhorse_partial = partial(workhorses.fim_svd, array=e_all_coil_pos,
|
|
588
|
+
workhorse_partial = partial(pynibs.optimization.workhorses.fim_svd, array=e_all_coil_pos,
|
|
589
|
+
ele_idx_1=ele_idx_1,
|
|
576
590
|
ele_idx_2=ele_idx_2, e_opt=e_opt_real_in_hotspot,
|
|
577
591
|
c=regression_cmap_normalized)
|
|
578
592
|
|
|
579
593
|
elif criterion == "fim_mc_rows":
|
|
580
594
|
if metrics_weights[0] == 0:
|
|
581
|
-
workhorse_partial = partial(workhorses.mc, array=e_all_coil_pos,
|
|
595
|
+
workhorse_partial = partial(pynibs.optimization.workhorses.mc, array=e_all_coil_pos,
|
|
596
|
+
ele_idx_1=ele_idx_2, mode="rows")
|
|
582
597
|
elif metrics_weights[1] == 0:
|
|
583
|
-
workhorse_partial = partial(workhorses.fim, array=e_all_coil_pos,
|
|
598
|
+
workhorse_partial = partial(pynibs.optimization.workhorses.fim, array=e_all_coil_pos,
|
|
599
|
+
ele_idx_1=ele_idx_1,
|
|
584
600
|
e_opt=e_opt_real_in_hotspot, c=regression_cmap_normalized)
|
|
585
601
|
else:
|
|
586
|
-
workhorse_partial = partial(workhorses.fim_mc, array=e_all_coil_pos,
|
|
602
|
+
workhorse_partial = partial(pynibs.optimization.workhorses.fim_mc, array=e_all_coil_pos,
|
|
603
|
+
ele_idx_1=ele_idx_1,
|
|
587
604
|
ele_idx_2=ele_idx_2, e_opt=e_opt_real_in_hotspot,
|
|
588
605
|
c=regression_cmap_normalized,
|
|
589
606
|
mode="rows")
|
|
590
607
|
|
|
591
608
|
elif criterion == "fim_mc_cols":
|
|
592
609
|
if metrics_weights[0] == 0:
|
|
593
|
-
workhorse_partial = partial(workhorses.mc, array=e_all_coil_pos,
|
|
610
|
+
workhorse_partial = partial(pynibs.optimization.workhorses.mc, array=e_all_coil_pos,
|
|
611
|
+
ele_idx_1=ele_idx_2, mode="cols")
|
|
594
612
|
elif metrics_weights[1] == 0:
|
|
595
|
-
workhorse_partial = partial(workhorses.fim, array=e_all_coil_pos,
|
|
613
|
+
workhorse_partial = partial(pynibs.optimization.workhorses.fim, array=e_all_coil_pos,
|
|
614
|
+
ele_idx_1=ele_idx_1,
|
|
596
615
|
e_opt=e_opt_real_in_hotspot, c=regression_cmap_normalized)
|
|
597
616
|
else:
|
|
598
|
-
workhorse_partial = partial(workhorses.fim_mc, array=e_all_coil_pos,
|
|
617
|
+
workhorse_partial = partial(pynibs.optimization.workhorses.fim_mc, array=e_all_coil_pos,
|
|
618
|
+
ele_idx_1=ele_idx_1,
|
|
599
619
|
ele_idx_2=ele_idx_2, e_opt=e_opt_real_in_hotspot,
|
|
600
620
|
c=regression_cmap_normalized,
|
|
601
621
|
mode="cols")
|
|
@@ -632,7 +652,7 @@ def get_optimal_coil_positions(
|
|
|
632
652
|
|
|
633
653
|
if not fim_use_gpu:
|
|
634
654
|
workhorse_partial = partial(
|
|
635
|
-
workhorses.roi_elmt_wise_corr,
|
|
655
|
+
pynibs.optimization.workhorses.roi_elmt_wise_corr,
|
|
636
656
|
array=e_all_coil_pos_scaled,
|
|
637
657
|
# hotspot idx must be last element of "ele_idx_1" to decorrelate
|
|
638
658
|
# only the hotspot idx with all other ROI elements
|
|
@@ -658,7 +678,7 @@ def get_optimal_coil_positions(
|
|
|
658
678
|
index_lists[:] = cp.asarray(idx_list[:], dtype=cp.int32)
|
|
659
679
|
|
|
660
680
|
# execute optimization
|
|
661
|
-
res_all = workhorses.roi_elmt_wise_corr(
|
|
681
|
+
res_all = pynibs.optimization.workhorses.roi_elmt_wise_corr(
|
|
662
682
|
idx_list=index_lists,
|
|
663
683
|
array=e,
|
|
664
684
|
ele_idx_1=subsampling_idcs,
|
|
@@ -697,7 +717,7 @@ def get_optimal_coil_positions(
|
|
|
697
717
|
for j in range(len(idcs_to_check)):
|
|
698
718
|
idx_list.append(zap_idx_e_opt[:idx_in_result_list - 1] + [idcs_to_check[j]])
|
|
699
719
|
|
|
700
|
-
idx_list_chunks = pynibs.compute_chunks(idx_list, n_cpu)
|
|
720
|
+
idx_list_chunks = pynibs.util.utils.compute_chunks(idx_list, n_cpu)
|
|
701
721
|
res = pool.map(workhorse_partial, idx_list_chunks)
|
|
702
722
|
|
|
703
723
|
# extract best solution (multiple objectives)
|
|
@@ -754,7 +774,8 @@ def get_optimal_coil_positions(
|
|
|
754
774
|
f.create_dataset("criterion", data=crit)
|
|
755
775
|
|
|
756
776
|
try:
|
|
757
|
-
f.create_dataset(f"zap_index_list/{idx_in_result_list}",
|
|
777
|
+
f.create_dataset(f"zap_index_list/{idx_in_result_list}",
|
|
778
|
+
data=zap_idx_e_opt[:idx_in_result_list])
|
|
758
779
|
except (RuntimeError, KeyError):
|
|
759
780
|
if overwrite:
|
|
760
781
|
del f[f"zap_index_list/{idx_in_result_list}"]
|
|
@@ -829,7 +850,7 @@ def get_optimal_coil_positions(
|
|
|
829
850
|
def online_optimization(fn_subject_hdf5, fn_roi_ss_indices_hdf5, fn_out_hdf5, fn_stimsites_hdf5, e_matrix, mep,
|
|
830
851
|
mesh_idx, roi_idx, n_zaps_init=3, criterion_init="mc_rows", criterion="coverage", n_cpu=4,
|
|
831
852
|
threshold=0.8, weights=None, eps0=0.01, eps0_dist=1, exponent=5, perc=99,
|
|
832
|
-
n_refit=0, fun=
|
|
853
|
+
n_refit=0, fun=None, verbose=True):
|
|
833
854
|
"""
|
|
834
855
|
Performs virtual online optimization to determine the congruence factor. After an initial set of coil positions,
|
|
835
856
|
the algorithm iteratively optimizes the next coil position based on the virtually measured MEP data.
|
|
@@ -867,7 +888,7 @@ def online_optimization(fn_subject_hdf5, fn_roi_ss_indices_hdf5, fn_out_hdf5, fn
|
|
|
867
888
|
Weights of optimization criteria in case of multiple goal functions (e.g. fim_svd). Higher weight means higher
|
|
868
889
|
importance for the respective criteria. By default, both optimization criteria are weighted equally [0.5, 0.5].
|
|
869
890
|
eps0 : float, default: 0.01
|
|
870
|
-
First error threshold to terminate the online optimization. The normalized root
|
|
891
|
+
First error threshold to terminate the online optimization. The normalized root-mean-square deviation is
|
|
871
892
|
calculated between the current and the previous solution. If the error is lower than eps0 for 3 times in a row,
|
|
872
893
|
the online optimization terminates and returns the results.
|
|
873
894
|
eps0_dist : float, default: 1
|
|
@@ -881,7 +902,7 @@ def online_optimization(fn_subject_hdf5, fn_roi_ss_indices_hdf5, fn_out_hdf5, fn
|
|
|
881
902
|
n_refit : int, default: 0
|
|
882
903
|
Number of refit iterations. No refit is applied if ``n_refit=0``.
|
|
883
904
|
fun : function object, default: pynibs.linear
|
|
884
|
-
Function to use to determine the congruence factor (e.g. pynibs.linear, pynibs.sigmoid, ...).
|
|
905
|
+
Function to use to determine the congruence factor (e.g. pynibs.linear, pynibs.expio.sigmoid, ...).
|
|
885
906
|
verbose : bool, default: True
|
|
886
907
|
Plot output messages.
|
|
887
908
|
|
|
@@ -889,8 +910,10 @@ def online_optimization(fn_subject_hdf5, fn_roi_ss_indices_hdf5, fn_out_hdf5, fn
|
|
|
889
910
|
-------
|
|
890
911
|
<file> .hdf5 file
|
|
891
912
|
Results output file containing the coil positions and the congruence factor maps for every iteration.
|
|
892
|
-
|
|
893
913
|
"""
|
|
914
|
+
from matplotlib import pyplot as plt
|
|
915
|
+
if fun is None:
|
|
916
|
+
fun = pynibs.expio.sigmoid
|
|
894
917
|
if weights is None:
|
|
895
918
|
weights = [0.5, 0.5]
|
|
896
919
|
print("Starting online congruence factor optimization:")
|
|
@@ -962,32 +985,32 @@ def online_optimization(fn_subject_hdf5, fn_roi_ss_indices_hdf5, fn_out_hdf5, fn
|
|
|
962
985
|
if verbose:
|
|
963
986
|
print(f"Determine optimal coil positions for initial number of {n_zaps_init} samples using {criterion_init}")
|
|
964
987
|
|
|
965
|
-
zap_idx_opt =
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
988
|
+
zap_idx_opt = get_optimal_coil_positions(e_matrix=e_matrix,
|
|
989
|
+
ele_idx_1=ele_idx_ss,
|
|
990
|
+
ele_idx_2=None,
|
|
991
|
+
criterion=criterion_init,
|
|
992
|
+
n_stim=n_zaps_init,
|
|
993
|
+
fn_out_hdf5=None,
|
|
994
|
+
n_cpu=n_cpu,
|
|
995
|
+
zap_idx_opt=None,
|
|
996
|
+
metrics_weights=weights,
|
|
997
|
+
overwrite=False,
|
|
998
|
+
verbose=True)
|
|
976
999
|
|
|
977
1000
|
# determine initial c-factor map for all N (not existing in real life)
|
|
978
1001
|
if verbose:
|
|
979
1002
|
print(f"Determine reference c-factor map (N)")
|
|
980
1003
|
|
|
981
|
-
c_ref_n = pynibs.regress_data(elm_idx_list=np.arange(n_ele),
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
1004
|
+
c_ref_n = pynibs.regression.regress_data(elm_idx_list=np.arange(n_ele),
|
|
1005
|
+
e_matrix=e_matrix,
|
|
1006
|
+
mep=mep,
|
|
1007
|
+
zap_idx=None,
|
|
1008
|
+
fun=fun,
|
|
1009
|
+
n_refit=n_refit,
|
|
1010
|
+
n_cpu=n_cpu,
|
|
1011
|
+
con=con,
|
|
1012
|
+
return_fits=False,
|
|
1013
|
+
refit_discontinuities=True)
|
|
991
1014
|
|
|
992
1015
|
ref_n = c_ref_n.flatten() ** exponent
|
|
993
1016
|
ref_n = ref_n / np.percentile(ref_n, perc)
|
|
@@ -1004,16 +1027,16 @@ def online_optimization(fn_subject_hdf5, fn_roi_ss_indices_hdf5, fn_out_hdf5, fn
|
|
|
1004
1027
|
if verbose:
|
|
1005
1028
|
print(f"Determine initial c-factor map")
|
|
1006
1029
|
|
|
1007
|
-
c_init, p = pynibs.regress_data(elm_idx_list=np.arange(n_ele),
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1030
|
+
c_init, p = pynibs.regression.regress_data(elm_idx_list=np.arange(n_ele),
|
|
1031
|
+
e_matrix=e_matrix,
|
|
1032
|
+
mep=mep,
|
|
1033
|
+
zap_idx=zap_idx_opt,
|
|
1034
|
+
fun=fun,
|
|
1035
|
+
n_refit=n_refit,
|
|
1036
|
+
n_cpu=n_cpu,
|
|
1037
|
+
con=con,
|
|
1038
|
+
return_fits=True,
|
|
1039
|
+
refit_discontinuities=True)
|
|
1017
1040
|
ref = c_init.flatten() ** exponent
|
|
1018
1041
|
ref = ref / np.percentile(ref, perc)
|
|
1019
1042
|
|
|
@@ -1050,20 +1073,20 @@ def online_optimization(fn_subject_hdf5, fn_roi_ss_indices_hdf5, fn_out_hdf5, fn
|
|
|
1050
1073
|
f"using {criterion}")
|
|
1051
1074
|
|
|
1052
1075
|
n_zaps.append(n_zaps[-1] + 1)
|
|
1053
|
-
zap_idx_opt =
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1076
|
+
zap_idx_opt = get_optimal_coil_positions(e_matrix=e_matrix,
|
|
1077
|
+
ele_idx_1=ele_idx_1,
|
|
1078
|
+
ele_idx_2=ele_idx_2,
|
|
1079
|
+
criterion=criterion,
|
|
1080
|
+
n_stim=n_zaps[-1],
|
|
1081
|
+
fn_out_hdf5=None,
|
|
1082
|
+
n_cpu=n_cpu,
|
|
1083
|
+
zap_idx_opt=zap_idx_opt,
|
|
1084
|
+
fim_fit_fun=fun,
|
|
1085
|
+
regression_fit_parameters=p,
|
|
1086
|
+
regression_cmap=ref,
|
|
1087
|
+
metrics_weights=weights,
|
|
1088
|
+
overwrite=False,
|
|
1089
|
+
verbose=True)
|
|
1067
1090
|
key = str(len(zap_idx_opt) - 1)
|
|
1068
1091
|
zap_idx[key] = zap_idx_opt
|
|
1069
1092
|
|
|
@@ -1077,16 +1100,16 @@ def online_optimization(fn_subject_hdf5, fn_roi_ss_indices_hdf5, fn_out_hdf5, fn
|
|
|
1077
1100
|
if verbose:
|
|
1078
1101
|
print(f"Determine c-factor map for {len(zap_idx_opt)} zaps")
|
|
1079
1102
|
|
|
1080
|
-
c[key], p = pynibs.regress_data(elm_idx_list=np.arange(n_ele),
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1103
|
+
c[key], p = pynibs.regression.regress_data(elm_idx_list=np.arange(n_ele),
|
|
1104
|
+
e_matrix=e_matrix,
|
|
1105
|
+
mep=mep,
|
|
1106
|
+
zap_idx=zap_idx_opt,
|
|
1107
|
+
fun=fun,
|
|
1108
|
+
n_refit=n_refit,
|
|
1109
|
+
n_cpu=n_cpu,
|
|
1110
|
+
con=con,
|
|
1111
|
+
return_fits=True,
|
|
1112
|
+
refit_discontinuities=True)
|
|
1090
1113
|
arr = c[key].flatten() ** exponent
|
|
1091
1114
|
arr = arr / np.percentile(arr, perc)
|
|
1092
1115
|
|
|
@@ -1097,20 +1120,20 @@ def online_optimization(fn_subject_hdf5, fn_roi_ss_indices_hdf5, fn_out_hdf5, fn
|
|
|
1097
1120
|
##########################################################################
|
|
1098
1121
|
|
|
1099
1122
|
# determine NRMSD w.r.t. previous solution
|
|
1100
|
-
eps.append(pynibs.nrmsd(arr, ref))
|
|
1123
|
+
eps.append(pynibs.util.quality_measures.nrmsd(arr, ref))
|
|
1101
1124
|
|
|
1102
1125
|
if verbose:
|
|
1103
1126
|
print(f"NRMSD to previous solution: {eps[-1]}")
|
|
1104
1127
|
|
|
1105
1128
|
# determine NRMSD w.r.t. global solution (not existing in real life)
|
|
1106
|
-
eps_n.append(pynibs.nrmsd(arr, ref_n))
|
|
1129
|
+
eps_n.append(pynibs.util.quality_measures.nrmsd(arr, ref_n))
|
|
1107
1130
|
|
|
1108
1131
|
if verbose:
|
|
1109
1132
|
print(f"NRMSD to global solution: {eps_n[-1]}")
|
|
1110
1133
|
|
|
1111
1134
|
# determine geodesic distance w.r.t. previous solution
|
|
1112
|
-
nodes_dist, tris_dist = pynibs.geodesic_dist(nodes=points, tris=con, source=np.argmax(c[key]),
|
|
1113
|
-
|
|
1135
|
+
nodes_dist, tris_dist = pynibs.util.quality_measures.geodesic_dist(nodes=points, tris=con, source=np.argmax(c[key]),
|
|
1136
|
+
source_is_node=False)
|
|
1114
1137
|
gdist.append(tris_dist[np.argmax(c[str(len(zap_idx_opt) - 2)])])
|
|
1115
1138
|
|
|
1116
1139
|
if verbose:
|
|
@@ -1281,11 +1304,11 @@ def calc_opt_gain_map(e_matrix_ref, e_matrix_opt, points, con, fn_out=None, thre
|
|
|
1281
1304
|
"""
|
|
1282
1305
|
assert e_matrix_ref.shape[1] == e_matrix_opt.shape[1], "e_matrix_ref and e_matrix_opt do not have the same number" \
|
|
1283
1306
|
"columns, i.e. elements."
|
|
1284
|
-
n_ele = e_matrix_ref.shape[1]
|
|
1285
|
-
|
|
1286
|
-
# determine correlation matrices
|
|
1287
|
-
corr_matrix_ref = np.abs(np.corrcoef(e_matrix_ref.T)) ** 2
|
|
1288
|
-
corr_matrix_opt = np.abs(np.corrcoef(e_matrix_opt.T)) ** 2
|
|
1307
|
+
# n_ele = e_matrix_ref.shape[1]
|
|
1308
|
+
#
|
|
1309
|
+
# # determine correlation matrices
|
|
1310
|
+
# corr_matrix_ref = np.abs(np.corrcoef(e_matrix_ref.T)) ** 2
|
|
1311
|
+
# corr_matrix_opt = np.abs(np.corrcoef(e_matrix_opt.T)) ** 2
|
|
1289
1312
|
|
|
1290
1313
|
# determine element areas
|
|
1291
1314
|
p1_tri = points[con[:, 0], :]
|
|
@@ -1360,14 +1383,12 @@ def optimal_coilplacement_region(e, fmri_stats, best_n=1, metric='dot', non_nega
|
|
|
1360
1383
|
|
|
1361
1384
|
Notes
|
|
1362
1385
|
-----
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1386
|
+
The function normalizes the input E-field matrix and fMRI statistics for evaluation.
|
|
1387
|
+
If `res_folder` is specified, visualizations of the results are saved to the directory.
|
|
1388
|
+
The function supports saving and writing TMS navigator instrument marker files using `simnibs.localite`.
|
|
1366
1389
|
"""
|
|
1367
1390
|
# Import localite utility to write TMS navigator instrument marker files
|
|
1368
|
-
from
|
|
1369
|
-
loc = localite()
|
|
1370
|
-
write_tms_navigator_im = loc.write
|
|
1391
|
+
from matplotlib import pyplot as plt
|
|
1371
1392
|
|
|
1372
1393
|
# Normalize fMRI statistics
|
|
1373
1394
|
fmri_stats /= fmri_stats.max()
|
|
@@ -1427,6 +1448,9 @@ def optimal_coilplacement_region(e, fmri_stats, best_n=1, metric='dot', non_nega
|
|
|
1427
1448
|
|
|
1428
1449
|
# If position matrices are provided, identify the best and N best positions
|
|
1429
1450
|
if pos_matrices is not None:
|
|
1451
|
+
from simnibs.utils.nnav import localite
|
|
1452
|
+
loc = localite()
|
|
1453
|
+
write_tms_navigator_im = loc.write
|
|
1430
1454
|
nth_opt_matsim = pos_matrices[:, :, nth_best_coil_pos]
|
|
1431
1455
|
|
|
1432
1456
|
# Transpose the matrices for proper alignment
|
|
@@ -1439,7 +1463,7 @@ def optimal_coilplacement_region(e, fmri_stats, best_n=1, metric='dot', non_nega
|
|
|
1439
1463
|
# Write the instrument marker file for the top N positions
|
|
1440
1464
|
write_tms_navigator_im(nth_opt_matsim, os.path.join(res_folder, f'opt_coil_pos_nth_{metric}.xml'),
|
|
1441
1465
|
names=[f'opt_{i:0>2}' for i in range(best_n)], overwrite=True)
|
|
1442
|
-
pynibs.create_stimsite_from_matsimnibs(os.path.join(res_folder, f'opt_coil_pos_nth_{metric}.hdf5'),
|
|
1443
|
-
|
|
1444
|
-
|
|
1466
|
+
pynibs.coil.create_stimsite_from_matsimnibs(os.path.join(res_folder, f'opt_coil_pos_nth_{metric}.hdf5'),
|
|
1467
|
+
nth_opt_matsim, overwrite=True, data=np.array(range(1, best_n + 1)),
|
|
1468
|
+
datanames='Best coil id')
|
|
1445
1469
|
return nth_best_coil_pos
|