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.
Files changed (101) hide show
  1. pynibs/__init__.py +26 -14
  2. pynibs/coil/__init__.py +6 -0
  3. pynibs/{coil.py → coil/coil.py} +213 -543
  4. pynibs/coil/export.py +508 -0
  5. pynibs/congruence/__init__.py +4 -1
  6. pynibs/congruence/congruence.py +37 -45
  7. pynibs/congruence/ext_metrics.py +40 -11
  8. pynibs/congruence/stimulation_threshold.py +1 -2
  9. pynibs/expio/Mep.py +120 -370
  10. pynibs/expio/__init__.py +10 -0
  11. pynibs/expio/brainsight.py +34 -37
  12. pynibs/expio/cobot.py +25 -25
  13. pynibs/expio/exp.py +10 -7
  14. pynibs/expio/fit_funs.py +3 -0
  15. pynibs/expio/invesalius.py +70 -0
  16. pynibs/expio/localite.py +190 -91
  17. pynibs/expio/neurone.py +139 -0
  18. pynibs/expio/signal_ced.py +345 -2
  19. pynibs/expio/visor.py +16 -15
  20. pynibs/freesurfer.py +34 -33
  21. pynibs/hdf5_io/hdf5_io.py +149 -132
  22. pynibs/hdf5_io/xdmf.py +35 -31
  23. pynibs/mesh/__init__.py +1 -1
  24. pynibs/mesh/mesh_struct.py +77 -92
  25. pynibs/mesh/transformations.py +121 -21
  26. pynibs/mesh/utils.py +191 -99
  27. pynibs/models/_TMS.py +2 -1
  28. pynibs/muap.py +1 -2
  29. pynibs/neuron/__init__.py +10 -0
  30. pynibs/neuron/models/mep.py +566 -0
  31. pynibs/neuron/neuron_regression.py +98 -8
  32. pynibs/optimization/__init__.py +12 -2
  33. pynibs/optimization/{optimization.py → coil_opt.py} +157 -133
  34. pynibs/optimization/multichannel.py +1174 -24
  35. pynibs/optimization/workhorses.py +7 -8
  36. pynibs/regression/__init__.py +4 -2
  37. pynibs/regression/dual_node_detection.py +229 -219
  38. pynibs/regression/regression.py +92 -61
  39. pynibs/roi/__init__.py +4 -1
  40. pynibs/roi/roi_structs.py +19 -21
  41. pynibs/roi/{roi.py → roi_utils.py} +56 -33
  42. pynibs/subject.py +24 -14
  43. pynibs/util/__init__.py +20 -4
  44. pynibs/util/dosing.py +4 -5
  45. pynibs/util/quality_measures.py +39 -38
  46. pynibs/util/rotations.py +116 -9
  47. pynibs/util/{simnibs.py → simnibs_io.py} +29 -19
  48. pynibs/util/{util.py → utils.py} +20 -22
  49. pynibs/visualization/para.py +4 -4
  50. pynibs/visualization/render_3D.py +4 -4
  51. pynibs-0.2026.1.dist-info/METADATA +105 -0
  52. pynibs-0.2026.1.dist-info/RECORD +69 -0
  53. {pyNIBS-0.2024.8.dist-info → pynibs-0.2026.1.dist-info}/WHEEL +1 -1
  54. pyNIBS-0.2024.8.dist-info/METADATA +0 -723
  55. pyNIBS-0.2024.8.dist-info/RECORD +0 -107
  56. pynibs/data/configuration_exp0.yaml +0 -59
  57. pynibs/data/configuration_linear_MEP.yaml +0 -61
  58. pynibs/data/configuration_linear_RT.yaml +0 -61
  59. pynibs/data/configuration_sigmoid4.yaml +0 -68
  60. pynibs/data/network mapping configuration/configuration guide.md +0 -238
  61. pynibs/data/network mapping configuration/configuration_TEMPLATE.yaml +0 -42
  62. pynibs/data/network mapping configuration/configuration_for_testing.yaml +0 -43
  63. pynibs/data/network mapping configuration/configuration_modelTMS.yaml +0 -43
  64. pynibs/data/network mapping configuration/configuration_reg_isi_05.yaml +0 -43
  65. pynibs/data/network mapping configuration/output_documentation.md +0 -185
  66. pynibs/data/network mapping configuration/recommendations_for_accuracy_threshold.md +0 -77
  67. pynibs/data/neuron/models/L23_PC_cADpyr_biphasic_v1.csv +0 -1281
  68. pynibs/data/neuron/models/L23_PC_cADpyr_monophasic_v1.csv +0 -1281
  69. pynibs/data/neuron/models/L4_LBC_biphasic_v1.csv +0 -1281
  70. pynibs/data/neuron/models/L4_LBC_monophasic_v1.csv +0 -1281
  71. pynibs/data/neuron/models/L4_NBC_biphasic_v1.csv +0 -1281
  72. pynibs/data/neuron/models/L4_NBC_monophasic_v1.csv +0 -1281
  73. pynibs/data/neuron/models/L4_SBC_biphasic_v1.csv +0 -1281
  74. pynibs/data/neuron/models/L4_SBC_monophasic_v1.csv +0 -1281
  75. pynibs/data/neuron/models/L5_TTPC2_cADpyr_biphasic_v1.csv +0 -1281
  76. pynibs/data/neuron/models/L5_TTPC2_cADpyr_monophasic_v1.csv +0 -1281
  77. pynibs/tests/data/InstrumentMarker20200225163611937.xml +0 -19
  78. pynibs/tests/data/TriggerMarkers_Coil0_20200225163443682.xml +0 -14
  79. pynibs/tests/data/TriggerMarkers_Coil1_20200225170337572.xml +0 -6373
  80. pynibs/tests/data/Xdmf.dtd +0 -89
  81. pynibs/tests/data/brainsight_niiImage_nifticoord.txt +0 -145
  82. pynibs/tests/data/brainsight_niiImage_nifticoord_largefile.txt +0 -1434
  83. pynibs/tests/data/brainsight_niiImage_niifticoord_mixedtargets.txt +0 -47
  84. pynibs/tests/data/create_subject_testsub.py +0 -332
  85. pynibs/tests/data/data.hdf5 +0 -0
  86. pynibs/tests/data/geo.hdf5 +0 -0
  87. pynibs/tests/test_coil.py +0 -474
  88. pynibs/tests/test_elements2nodes.py +0 -100
  89. pynibs/tests/test_hdf5_io/test_xdmf.py +0 -61
  90. pynibs/tests/test_mesh_transformations.py +0 -123
  91. pynibs/tests/test_mesh_utils.py +0 -143
  92. pynibs/tests/test_nnav_imports.py +0 -101
  93. pynibs/tests/test_quality_measures.py +0 -117
  94. pynibs/tests/test_regressdata.py +0 -289
  95. pynibs/tests/test_roi.py +0 -17
  96. pynibs/tests/test_rotations.py +0 -86
  97. pynibs/tests/test_subject.py +0 -71
  98. pynibs/tests/test_util.py +0 -24
  99. /pynibs/{regression/score_types.py → neuron/models/m1_montbrio.py} +0 -0
  100. {pyNIBS-0.2024.8.dist-info → pynibs-0.2026.1.dist-info/licenses}/LICENSE +0 -0
  101. {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 `optimizhat.py` module provides functions for optimizing coil placements in Transcranial Magnetic Stimulation
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
- :param candidate_coil_idcs: np.ndarry[int]
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
- :param selected_coil_idcs: np.ndarray[int]
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
- :param efields_diff_mat: np.ndarray[float], [n_coil,n_coil]
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 consiered.
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 consiered.
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 Magventure Pro with an MCF-B65 coil.
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 the
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, ele_idx_1=ele_idx_1)
292
- coil_idx_list_chunks = pynibs.compute_chunks(list(range(e_all_coil_pos.shape[0])), n_cpu)
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, ele_idx_1=ele_idx_1)
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, ele_idx_1=ele_idx_1)
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, ele_idx_1=ele_idx_1, mode="cols")
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, ele_idx_1=ele_idx_1, mode="rows")
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, ele_idx_1=ele_idx_1)
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, ele_idx_1=ele_idx_2, mode="cols")
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, ele_idx_1=ele_idx_1)
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, ele_idx_1=ele_idx_1,
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, ele_idx_1=ele_idx_2, mode="rows")
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, ele_idx_1=ele_idx_1)
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, ele_idx_1=ele_idx_1,
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, ele_idx_1=ele_idx_2)
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, ele_idx_1=ele_idx_1)
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, ele_idx_1=ele_idx_1,
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, ele_idx_1=ele_idx_2)
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, ele_idx_1=ele_idx_1,
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, ele_idx_1=ele_idx_1,
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, ele_idx_1=ele_idx_2, mode="rows")
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, ele_idx_1=ele_idx_1,
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, ele_idx_1=ele_idx_1,
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, ele_idx_1=ele_idx_2, mode="cols")
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, ele_idx_1=ele_idx_1,
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, ele_idx_1=ele_idx_1,
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}", data=zap_idx_e_opt[: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=pynibs.sigmoid, verbose=True):
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 mean square deviation is
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 = pynibs.get_optimal_coil_positions(e_matrix=e_matrix,
966
- ele_idx_1=ele_idx_ss,
967
- ele_idx_2=None,
968
- criterion=criterion_init,
969
- n_stim=n_zaps_init,
970
- fn_out_hdf5=None,
971
- n_cpu=n_cpu,
972
- zap_idx_opt=None,
973
- metrics_weights=weights,
974
- overwrite=False,
975
- verbose=True)
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
- e_matrix=e_matrix,
983
- mep=mep,
984
- zap_idx=None,
985
- fun=fun,
986
- n_refit=n_refit,
987
- n_cpu=n_cpu,
988
- con=con,
989
- return_fits=False,
990
- refit_discontinuities=True)
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
- e_matrix=e_matrix,
1009
- mep=mep,
1010
- zap_idx=zap_idx_opt,
1011
- fun=fun,
1012
- n_refit=n_refit,
1013
- n_cpu=n_cpu,
1014
- con=con,
1015
- return_fits=True,
1016
- refit_discontinuities=True)
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 = pynibs.get_optimal_coil_positions(e_matrix=e_matrix,
1054
- ele_idx_1=ele_idx_1,
1055
- ele_idx_2=ele_idx_2,
1056
- criterion=criterion,
1057
- n_stim=n_zaps[-1],
1058
- fn_out_hdf5=None,
1059
- n_cpu=n_cpu,
1060
- zap_idx_opt=zap_idx_opt,
1061
- fim_fit_fun=fun,
1062
- regression_fit_parameters=p,
1063
- regression_cmap=ref,
1064
- metrics_weights=weights,
1065
- overwrite=False,
1066
- verbose=True)
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
- e_matrix=e_matrix,
1082
- mep=mep,
1083
- zap_idx=zap_idx_opt,
1084
- fun=fun,
1085
- n_refit=n_refit,
1086
- n_cpu=n_cpu,
1087
- con=con,
1088
- return_fits=True,
1089
- refit_discontinuities=True)
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
- source_is_node=False)
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
- - The function normalizes the input E-field matrix and fMRI statistics for evaluation.
1364
- - If `res_folder` is specified, visualizations of the results are saved to the directory.
1365
- - The function supports saving and writing TMS navigator instrument marker files using `simnibs.localite`.
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 simnibs.utils.nnav import localite
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
- nth_opt_matsim, overwrite=True, data=np.array(range(1, best_n + 1)),
1444
- datanames='Best coil id')
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