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
@@ -3,10 +3,10 @@ import time
3
3
  import h5py
4
4
  import inspect
5
5
  import warnings
6
- import multiprocessing
7
6
  import pandas as pd
8
7
  import numpy as np
9
8
  import scipy.stats
9
+ import multiprocessing
10
10
  from lmfit import Model
11
11
  from scipy.linalg import svd
12
12
  from functools import partial
@@ -21,11 +21,13 @@ class Element(object):
21
21
  """
22
22
  Fit Element object class
23
23
  """
24
- def __init__(self, x, y, ele_id, fun=pynibs.expio.fit_funs.sigmoid4, score_type="R2", select_signed_data=False, constants=None,
24
+ def __init__(self, x, y, ele_id, fun=None, score_type="R2", select_signed_data=False, constants=None,
25
25
  **kwargs):
26
26
  """
27
27
  Initializes Fit Element instance
28
28
  """
29
+ if fun is None:
30
+ fun=pynibs.expio.sigmoid4
29
31
  self.x = x
30
32
  self.y = y
31
33
  self.y_passed = self.y
@@ -129,15 +131,28 @@ class Element(object):
129
131
 
130
132
  elif self.fun == pynibs.expio.fit_funs.exp0:
131
133
  if self.limits == {}:
132
- self.set_limits({"x0": [0, 1000], "r": [1e-12, 100]})
134
+ self.set_limits({"x0": [0, 100], "r": [1e-12, 100]})
133
135
  else:
134
136
  self.set_limits(self.limits)
135
137
  if self.init_vals == {}:
136
138
  self.set_init_vals({"x0": 10, "r": .1})
137
139
  else:
138
140
  self.set_init_vals(self.init_vals)
141
+
142
+ if self.random_vals_init_range == {}:
143
+ self.random_vals_init_range = {"x0": [0, 100], "r": [0, .2], "y0": [0, 10]}
144
+
145
+ elif self.fun == pynibs.expio.fit_funs.exp:
146
+ if self.limits == {}:
147
+ self.set_limits({"x0": [0, 100], "r": [1e-12, 100], "y0": [1e-12, 100]})
148
+ else:
149
+ self.set_limits(self.limits)
150
+ if self.init_vals == {}:
151
+ self.set_init_vals({"x0": 10, "r": .1, "y0": 1e-12})
152
+ else:
153
+ self.set_init_vals(self.init_vals)
139
154
  if self.random_vals_init_range == {}:
140
- self.random_vals_init_range = {"x0": [0, 10], "r": [0, .2]}
155
+ self.random_vals_init_range = {"x0": [0, 100], "r": [0, 1], "y0": [0, 10]}
141
156
 
142
157
  elif self.fun in [pynibs.expio.fit_funs.sigmoid, pynibs.expio.fit_funs.sigmoid_log,
143
158
  pynibs.expio.fit_funs.sigmoid4, pynibs.expio.fit_funs.sigmoid4_log]:
@@ -148,7 +163,8 @@ class Element(object):
148
163
 
149
164
  if self.fun == pynibs.expio.fit_funs.sigmoid4_log and y_min <= 0:
150
165
  y0 = 1e-3
151
- elif (self.fun == pynibs.expio.fit_funs.sigmoid4_log and y_min > 0) or self.fun == pynibs.expio.fit_funs.sigmoid4:
166
+ elif ((self.fun == pynibs.expio.fit_funs.sigmoid4_log and y_min > 0) or
167
+ self.fun == pynibs.expio.fit_funs.sigmoid4):
152
168
  y0 = y_min
153
169
  else:
154
170
  y0 = 0
@@ -237,6 +253,17 @@ class Element(object):
237
253
 
238
254
  if "a" not in self.random_vals_init_range:
239
255
  self.random_vals_init_range["a"] = [0, 1]
256
+ elif self.fun == pynibs.expio.fit_funs.step:
257
+ if self.limits == {}:
258
+ self.set_limits({"a": [-100, 100]})
259
+ else:
260
+ self.set_limits(self.limits)
261
+ if self.init_vals == {}:
262
+ self.set_init_vals({"a": 40})
263
+ else:
264
+ self.set_init_vals(self.init_vals)
265
+ if self.random_vals_init_range == {}:
266
+ self.random_vals_init_range = {"a": [0, 200]}
240
267
  else:
241
268
  raise NotImplementedError(self.fun)
242
269
 
@@ -271,7 +298,7 @@ class Element(object):
271
298
  r2_pos = 1 - np.var(residual) / np.var(self.y[mask_pos])
272
299
  else:
273
300
  p_pos = 1
274
- r2_pos = np.NaN
301
+ r2_pos = np.nan
275
302
  b_pos = -1
276
303
 
277
304
  # fit negative data (perform the regression when we have at least 20 data points to stabilize results)
@@ -283,11 +310,11 @@ class Element(object):
283
310
  r2_neg = 1 - np.var(residual) / np.var(self.y[mask_neg])
284
311
  else:
285
312
  p_neg = 1
286
- r2_neg = np.NaN
313
+ r2_neg = np.nan
287
314
  b_neg = 1
288
315
 
289
316
  # only use data with p < 0.001 and when slopes show an increase of y-data with increasing |x|-data otherwise,
290
- # set status to False to indicate that the fit is omitted and set score to NaN
317
+ # set status to False to indicate that the fit is omitted and set score to nan
291
318
  pos_valid = False
292
319
  neg_valid = False
293
320
 
@@ -314,7 +341,7 @@ class Element(object):
314
341
  self.r2_lin = r2_neg
315
342
  else:
316
343
  self.status = False
317
- self.score = np.NaN
344
+ self.score = np.nan
318
345
 
319
346
  self.var_y = np.var(self.y)
320
347
  self.norm_y = np.linalg.norm(self.y)
@@ -375,8 +402,8 @@ def workhorse_element_init(ele_id, e_matrix, mep, fun, score_type, select_signed
375
402
  return element
376
403
 
377
404
 
378
- def regress_data(e_matrix, mep, elm_idx_list=None, element_list=None, fun=pynibs.expio.fit_funs.sigmoid4, n_cpu=4, con=None,
379
- n_refit=50, zap_idx=None, return_fits=False, score_type="R2",
405
+ def regress_data(e_matrix, mep, elm_idx_list=None, element_list=None, fun=pynibs.expio.fit_funs.sigmoid4, n_cpu=4,
406
+ con=None, n_refit=50, zap_idx=None, return_fits=False, score_type="R2",
380
407
  verbose=False, pool=None, refit_discontinuities=True, select_signed_data=False,
381
408
  mp_context="fork", **kwargs):
382
409
  """
@@ -395,7 +422,7 @@ def regress_data(e_matrix, mep, elm_idx_list=None, element_list=None, fun=pynibs
395
422
  (n_zaps,) List containing the element indices the fit is performed for.
396
423
  element_list : list of Element object instances, optional
397
424
  [n_ele] pynibs.Element objects ot skip initialization here.
398
- fun : pynibs.exp.Mep, default: pynibs.sigmoid4
425
+ fun : pynibs.exp.Mep, default: pynibs.expio.sigmoid4
399
426
  A pynibs.exp.Mep function (exp0, sigmoid, sigmoid4, ...).
400
427
  n_cpu : int, default: 4
401
428
  Number of threads, if n_cpu=1 no parallel pool will be opened and all calculations are done in serial.
@@ -517,19 +544,18 @@ def regress_data(e_matrix, mep, elm_idx_list=None, element_list=None, fun=pynibs
517
544
  ) for ele_id in elm_idx_list]
518
545
 
519
546
  else:
547
+ # element_list = tqdm.tqdm(pool.imap(workhorse_init_partial, elm_idx_list), total=len(elm_idx_list))
520
548
  element_list = pool.map(workhorse_init_partial, elm_idx_list)
521
549
  stop = time.time()
522
550
  if verbose:
523
551
  print(f"Initialized {len(elm_idx_list)} elements: {stop - start:2.2f} s")
524
552
 
525
553
  # run fit
526
- ####################################################################
527
- start = time.time()
528
-
529
554
  if n_cpu <= 1:
530
555
  for ele in element_list:
531
556
  ele.run_fit(max_nfev=10 * len(ele.x))
532
557
  else:
558
+ # element_list = tqdm.tqdm(pool.imap(workhorse_partial, element_list), total=len(element_list))
533
559
  element_list = pool.map(workhorse_partial, element_list)
534
560
 
535
561
  stop = time.time()
@@ -596,11 +622,11 @@ def regress_data(e_matrix, mep, elm_idx_list=None, element_list=None, fun=pynibs
596
622
  if len(element_list) > 1:
597
623
  score = np.array([ele.score for ele in element_list])
598
624
  not_fitted_elms = np.array([idx for idx, ele in enumerate(element_list) if np.isnan(ele.score)])
599
- idx_disc, idx_neighbor = pynibs.get_indices_discontinuous_data(data=score,
600
- con=con,
601
- neighbor=True,
602
- deviation_factor=2,
603
- not_fitted_elms=not_fitted_elms)
625
+ idx_disc, idx_neighbor = pynibs.mesh.get_indices_discontinuous_data(data=score,
626
+ con=con,
627
+ neighbor=True,
628
+ deviation_factor=2,
629
+ not_fitted_elms=not_fitted_elms)
604
630
  element_list_disc = [element_list[i_ele] for i_ele in idx_disc]
605
631
 
606
632
  if len(idx_disc) > 0:
@@ -1145,7 +1171,7 @@ def fit_elms(elm_idx_list, e_matrix, mep, zap_idx=None,
1145
1171
  param_hints[p]['value'] = constants_elmwise[p][i]
1146
1172
  make_params()
1147
1173
 
1148
- # perform fit (for some reason, sigmoid4_log function generates NaN, which I can not reproduce)
1174
+ # perform fit (for some reason, sigmoid4_log function generates nan, which I can not reproduce)
1149
1175
  # I catch the fitting error (ValueError) and set an r2 score of -1, such that it will go into the refit
1150
1176
 
1151
1177
  e_elm = e_matrix[:, elm_idx]
@@ -1201,7 +1227,7 @@ def fit_elms(elm_idx_list, e_matrix, mep, zap_idx=None,
1201
1227
  # Electric field matrix
1202
1228
  # mep : np.ndarray of float [n_zaps]
1203
1229
  # Motor evoked potentials for every stimulation
1204
- # zap_idx : np.array [n_zaps], default: None
1230
+ # zap_idx : np.ndarray [n_zaps], default: None
1205
1231
  # Indices of zaps the congruence factor is calculated with (default: all)
1206
1232
  # fun : str
1207
1233
  # A function name of pynibs.exp.Mep (exp0, sigmoid)
@@ -1306,7 +1332,7 @@ def fit_elms(elm_idx_list, e_matrix, mep, zap_idx=None,
1306
1332
  #
1307
1333
  # make_params()
1308
1334
  #
1309
- # # perform fit (for some reason, sigmoid4_log function generates NaN, which I can not reproduce)
1335
+ # # perform fit (for some reason, sigmoid4_log function generates nan, which I can not reproduce)
1310
1336
  # # I catch the fitting error (ValueError) and set an r2 score of -1, such that it will go into the refit
1311
1337
  #
1312
1338
  # try:
@@ -1439,7 +1465,7 @@ def nl_hdf5(elm_idx_list=None, fn_reg_hdf5=None, qoi_path_hdf5=None, e_matrix=No
1439
1465
  if verbose:
1440
1466
  print(" > Using provided pool object")
1441
1467
  if n_cpu > 1:
1442
- elm_idx_list_chunks = pynibs.compute_chunks(elm_idx_list, n_cpu)
1468
+ elm_idx_list_chunks = pynibs.util.utils.compute_chunks(elm_idx_list, n_cpu)
1443
1469
  elif len(elm_idx_list) == 1:
1444
1470
  elm_idx_list_chunks = [elm_idx_list]
1445
1471
  elif n_cpu == 1:
@@ -1530,8 +1556,9 @@ def nl_hdf5(elm_idx_list=None, fn_reg_hdf5=None, qoi_path_hdf5=None, e_matrix=No
1530
1556
  # max_vals_refit[p][idx_re] * (np.random.rand() - 0.5)
1531
1557
 
1532
1558
  if n_cpu > 1:
1533
- elm_idx_list_chunks_zero = pynibs.compute_chunks(np.array(elm_idx_list)[idx_refit].tolist(),
1534
- n_cpu)
1559
+ elm_idx_list_chunks_zero = pynibs.util.utils.compute_chunks(
1560
+ np.array(elm_idx_list)[idx_refit].tolist(),
1561
+ n_cpu)
1535
1562
  elif len(elm_idx_list[idx_refit]) == 1:
1536
1563
  elm_idx_list_chunks_zero = [elm_idx_list[idx_refit]]
1537
1564
  else: # n_cpu == 1:
@@ -1584,10 +1611,10 @@ def nl_hdf5(elm_idx_list=None, fn_reg_hdf5=None, qoi_path_hdf5=None, e_matrix=No
1584
1611
  # find discontinuities and refit
1585
1612
  ##################################################################
1586
1613
  if refit_discontinuities and len(c) > 1:
1587
- idx_disc, idx_neighbor = pynibs.get_indices_discontinuous_data(data=c,
1588
- con=con,
1589
- neighbor=True,
1590
- deviation_factor=2)
1614
+ idx_disc, idx_neighbor = pynibs.mesh.get_indices_discontinuous_data(data=c,
1615
+ con=con,
1616
+ neighbor=True,
1617
+ deviation_factor=2)
1591
1618
  idx_disc = np.array(idx_disc)
1592
1619
 
1593
1620
  if len(idx_disc) > 0:
@@ -1602,8 +1629,8 @@ def nl_hdf5(elm_idx_list=None, fn_reg_hdf5=None, qoi_path_hdf5=None, e_matrix=No
1602
1629
  init_vals[p][idx_re] = best_values[idx_ne][p]
1603
1630
 
1604
1631
  if n_cpu > 1:
1605
- elm_idx_list_chunks_disc = pynibs.compute_chunks(np.array(elm_idx_list)[idx_disc].tolist(),
1606
- n_cpu)
1632
+ elm_idx_list_chunks_disc = pynibs.util.utils.compute_chunks(np.array(elm_idx_list)[idx_disc].tolist(),
1633
+ n_cpu)
1607
1634
  elif len(elm_idx_list[idx_disc]) == 1:
1608
1635
  elm_idx_list_chunks_disc = [elm_idx_list[idx_disc]]
1609
1636
  else: # n_cpu == 1:
@@ -1845,7 +1872,7 @@ def nl_hdf5_single_core_write(i, elm_idx_list, fn_reg_hdf5=None, qoi_path_hdf5=N
1845
1872
  mep : pandas.DataFrame, optional
1846
1873
  The motor evoked potential (MEP) data.
1847
1874
  fun : function, default: sigmoid4
1848
- The non-linear optimization function to use (default: pynibs.sigmoid4).
1875
+ The non-linear optimization function to use (default: pynibs.expio.sigmoid4).
1849
1876
  con : object, optional
1850
1877
  Constraints for optimization, if applicable.
1851
1878
  n_refit : int, default: 50
@@ -1973,8 +2000,8 @@ def get_bad_elms(x, y, method="lstsq", verbose=False):
1973
2000
  raise NotImplementedError(f'Method {method} unknown.')
1974
2001
 
1975
2002
  if verbose:
1976
- print(
1977
- f"Proc{str_pref}: > {len(idx)}/{x.shape[1]} bad elms removed from fitting ({time.time() - start:2.4} s).")
2003
+ print(f"Proc{str_pref}: > {len(idx)}/{x.shape[1]} "
2004
+ f"bad elms removed from fitting ({time.time() - start:2.4} s).")
1978
2005
 
1979
2006
  return idx
1980
2007
 
@@ -2086,7 +2113,7 @@ def nl_hdf5_single_core(zap_idx, elm_idx_list, fn_reg_hdf5=None, qoi_path_hdf5=N
2086
2113
  # get e field stats
2087
2114
  e_stats_dicts = None
2088
2115
  if return_e_field_stats:
2089
- mc = pynibs.mutual_coherence(e_matrix.transpose())
2116
+ mc = pynibs.util.utils.mutual_coherence(e_matrix.transpose())
2090
2117
  _, sv_rat, _ = svd(e_matrix)
2091
2118
  sv_rat = np.max(sv_rat) / np.min(sv_rat)
2092
2119
  e_stats_dicts = {'mc': mc,
@@ -2189,9 +2216,9 @@ def nl_hdf5_single_core(zap_idx, elm_idx_list, fn_reg_hdf5=None, qoi_path_hdf5=N
2189
2216
 
2190
2217
  # find discontinuities and refit
2191
2218
 
2192
- idx_disc, idx_neighbor = pynibs.get_indices_discontinuous_data(data=c, con=con, neighbor=True,
2193
- deviation_factor=2, min_val=1e-12,
2194
- not_fitted_elms=bad_elm_idx)
2219
+ idx_disc, idx_neighbor = pynibs.mesh.get_indices_discontinuous_data(data=c, con=con, neighbor=True,
2220
+ deviation_factor=2, min_val=1e-12,
2221
+ not_fitted_elms=bad_elm_idx)
2195
2222
  idx_disc = np.setdiff1d(idx_disc, bad_elm_idx)
2196
2223
 
2197
2224
  if len(idx_disc) > 0:
@@ -2271,7 +2298,7 @@ def stepdown_approach(zap_idx, elm_idx_list, fn_reg_hdf5=None, qoi_path_hdf5=Non
2271
2298
  (n_zaps, n_ele) Electric field matrix.
2272
2299
  mep : np.ndarray of float
2273
2300
  (n_zaps) Motor evoked potentials for every stimulation.
2274
- zap_idx : np.array, optional
2301
+ zap_idx : np.ndarray, optional
2275
2302
  (n_zaps) Indices of zaps the congruence factor is calculated with (default: all).
2276
2303
  fun : function object
2277
2304
  A function of pynibs.exp.Mep (exp0, sigmoid).
@@ -2297,6 +2324,7 @@ def stepdown_approach(zap_idx, elm_idx_list, fn_reg_hdf5=None, qoi_path_hdf5=Non
2297
2324
  * "R2": R2 score (Model variance / Total variance); linear fits: [0, 1], 1 ... perfect fit
2298
2325
  * "SR": Relative standard error of regression (1 - Error 2-norm / Data 2-norm); [-Inf, 1], 1 ... perfect fit
2299
2326
  * "rho": Spearman correlation coefficient [-1, 1]; finds any monotonous correlation (0 means no correlation)
2327
+
2300
2328
  return_progress : bool, default: False
2301
2329
  Return c maps for all steps to allow visualization over e-fitting over timesteps.
2302
2330
  smooth_data : bool, default: False
@@ -2307,18 +2335,21 @@ def stepdown_approach(zap_idx, elm_idx_list, fn_reg_hdf5=None, qoi_path_hdf5=Non
2307
2335
  Returns
2308
2336
  -------
2309
2337
  dict:
2338
+ A dictionary containing the following elements:
2310
2339
 
2311
2340
  * r2 : np.ndarray of float
2312
2341
  (n_roi, n_qoi) R2 for each element in elm_idx_list.
2313
2342
  * best_values: list of dict
2314
- Fit information, if wanted.
2343
+ Fit information, if requested.
2315
2344
  * stats : dict
2316
- If wanted:
2317
- 'mc': float
2345
+ If requested:
2346
+
2347
+ ``mc``: float
2318
2348
  Mutual coherence for e fields.
2319
- 'sv_rat' : float
2349
+ ``sv_rat`` : float
2320
2350
  SVD singular value ratio.
2321
2351
  * progress : cmaps for each step.
2352
+
2322
2353
  """
2323
2354
  starttime = time.time()
2324
2355
 
@@ -2380,7 +2411,7 @@ def stepdown_approach(zap_idx, elm_idx_list, fn_reg_hdf5=None, qoi_path_hdf5=Non
2380
2411
  e_stats_dicts = None
2381
2412
  if return_e_field_stats:
2382
2413
  stats_start = time.time()
2383
- mc = pynibs.mutual_coherence(e_matrix.transpose())
2414
+ mc = pynibs.util.utils.mutual_coherence(e_matrix.transpose())
2384
2415
 
2385
2416
  sv_rat = svd(e_matrix, check_finite=False, compute_uv=False)
2386
2417
  sv_rat = np.max(sv_rat) / np.min(sv_rat)
@@ -2436,7 +2467,7 @@ def stepdown_approach(zap_idx, elm_idx_list, fn_reg_hdf5=None, qoi_path_hdf5=Non
2436
2467
  if verbose:
2437
2468
  print(f"Proc{str_pref}: > Initial c-factor map done. ({stop - start:2.2f} s)")
2438
2469
 
2439
- # pick the best elements and compute their neibors' fits
2470
+ # pick the best elements and compute their neighbors' fits
2440
2471
  n_top = int(len(start_sample) * .2)
2441
2472
 
2442
2473
  elms_done = start_sample[np.argpartition(c[start_sample], -n_top)[-n_top:]]
@@ -2497,13 +2528,13 @@ def stepdown_approach(zap_idx, elm_idx_list, fn_reg_hdf5=None, qoi_path_hdf5=Non
2497
2528
  # refit already here some elements
2498
2529
  not_fitted_elms = c[np.array(list(elms_done))] <= 0
2499
2530
  # get random neighbor to refit
2500
- idx_disc, idx_neighbor = pynibs.get_indices_discontinuous_data(data=c[np.array(list(elms_done))],
2501
- con=con[np.array(list(elms_done))],
2502
- neighbor=True,
2503
- deviation_factor=3, min_val=1e-12,
2504
- not_fitted_elms=not_fitted_elms,
2505
- crit='randmax',
2506
- neigh_style='point')
2531
+ idx_disc, idx_neighbor = pynibs.mesh.get_indices_discontinuous_data(data=c[np.array(list(elms_done))],
2532
+ con=con[np.array(list(elms_done))],
2533
+ neighbor=True,
2534
+ deviation_factor=3, min_val=1e-12,
2535
+ not_fitted_elms=not_fitted_elms,
2536
+ crit='randmax',
2537
+ neigh_style='point')
2507
2538
  idx_disc = np.setdiff1d(idx_disc, bad_elm_idx)
2508
2539
  # set init values from neighbors best values
2509
2540
  for p in params:
@@ -2547,10 +2578,10 @@ def stepdown_approach(zap_idx, elm_idx_list, fn_reg_hdf5=None, qoi_path_hdf5=Non
2547
2578
 
2548
2579
  # refit the discontinues elemens
2549
2580
  not_fitted_elms = c == 0
2550
- idx_disc, idx_neighbor = pynibs.get_indices_discontinuous_data(data=c, con=con[elm_idx_list], neighbor=True,
2551
- deviation_factor=3, min_val=1e-12,
2552
- not_fitted_elms=not_fitted_elms, crit='max',
2553
- neigh_style='point')
2581
+ idx_disc, idx_neighbor = pynibs.mesh.get_indices_discontinuous_data(data=c, con=con[elm_idx_list], neighbor=True,
2582
+ deviation_factor=3, min_val=1e-12,
2583
+ not_fitted_elms=not_fitted_elms, crit='max',
2584
+ neigh_style='point')
2554
2585
  idx_disc = np.setdiff1d(idx_disc, bad_elm_idx)
2555
2586
 
2556
2587
  disc_fit_i, last_disc_idx = 1, idx_disc
@@ -2580,10 +2611,10 @@ def stepdown_approach(zap_idx, elm_idx_list, fn_reg_hdf5=None, qoi_path_hdf5=Non
2580
2611
 
2581
2612
  # prepare the next iteration
2582
2613
  last_disc_n = len(idx_disc)
2583
- idx_disc, idx_neighbor = pynibs.get_indices_discontinuous_data(data=c, con=con[elm_idx_list], neighbor=True,
2584
- deviation_factor=3, min_val=1e-12,
2585
- not_fitted_elms=not_fitted_elms, crit='max',
2586
- neigh_style='point')
2614
+ idx_disc, idx_neighbor = pynibs.mesh.get_indices_discontinuous_data(data=c, con=con[elm_idx_list], neighbor=True,
2615
+ deviation_factor=3, min_val=1e-12,
2616
+ not_fitted_elms=not_fitted_elms, crit='max',
2617
+ neigh_style='point')
2587
2618
  idx_disc = np.setdiff1d(idx_disc, bad_elm_idx)
2588
2619
  if np.all(last_disc_idx == idx_disc):
2589
2620
  break
pynibs/roi/__init__.py CHANGED
@@ -1,2 +1,5 @@
1
- from .roi import *
1
+ """
2
+ Functions that operate on region of interest (ROI) data.
3
+ """
4
+ from .roi_utils import *
2
5
  from .roi_structs import *
pynibs/roi/roi_structs.py CHANGED
@@ -6,7 +6,6 @@ import math
6
6
  import scipy
7
7
  import skimage
8
8
  import tqdm
9
- import trimesh
10
9
  import warnings
11
10
  import numpy as np
12
11
  import nibabel as nib
@@ -560,7 +559,6 @@ class RegionOfInterestSurface:
560
559
  """
561
560
  Initialize RegionOfInterestSurface class instance
562
561
  """
563
-
564
562
  self.node_coord_up = np.empty(0)
565
563
  self.node_coord_mid = np.empty(0)
566
564
  self.node_coord_low = np.empty(0)
@@ -702,7 +700,7 @@ class RegionOfInterestSurface:
702
700
  tri_center_coord_low : np.ndarray of float [N_roi_tri x 3]
703
701
  Coordinates (x, y, z) of triangle center of lower epsilon layer of ROI surface
704
702
  fn_mask : str
705
- Filename for freesurfer mask. If given, this is used instead of *_ROIs
703
+ Filename for freesurfer mask. If given, this is used instead of \\*_ROIs
706
704
  X_ROI : list of float
707
705
  Region of interest [Xmin, Xmax], whole X range if empty [0,0] or None
708
706
  (left - right)
@@ -720,6 +718,7 @@ class RegionOfInterestSurface:
720
718
  make_GM_WM_surface(self, gm_surf_fname, wm_surf_fname, delta, X_ROI, Y_ROI, Z_ROI)
721
719
  make_GM_WM_surface(self, gm_surf_fname, wm_surf_fname, delta, mask_fn, layer=3)
722
720
  """
721
+ import trimesh
723
722
  self.gm_surf_fname = gm_surf_fname
724
723
  self.wm_surf_fname = wm_surf_fname
725
724
  self.midlayer_surf_fname = midlayer_surf_fname
@@ -893,12 +892,12 @@ class RegionOfInterestSurface:
893
892
  mesh.export(roi_fn)
894
893
 
895
894
  roi_refined_fn = os.path.join(self.mesh_folder, "roi", "tmp", "roi_refined.stl")
896
- pynibs.refine_surface(fn_surf=roi_fn,
897
- fn_surf_refined=roi_refined_fn,
898
- center=[0, 0, 0],
899
- radius=np.inf,
900
- verbose=True,
901
- repair=False)
895
+ pynibs.mesh.refine_surface(fn_surf=roi_fn,
896
+ fn_surf_refined=roi_refined_fn,
897
+ center=[0, 0, 0],
898
+ radius=np.inf,
899
+ verbose=True,
900
+ repair=False)
902
901
 
903
902
  roi = trimesh.load(roi_refined_fn)
904
903
  con_cropped_reform = roi.faces
@@ -912,12 +911,12 @@ class RegionOfInterestSurface:
912
911
  faces=con_cropped_reform)
913
912
  mesh.export(roi_fn)
914
913
 
915
- pynibs.refine_surface(fn_surf=roi_fn,
916
- fn_surf_refined=roi_refined_fn,
917
- center=[0, 0, 0],
918
- radius=np.inf,
919
- verbose=True,
920
- repair=False)
914
+ pynibs.mesh.refine_surface(fn_surf=roi_fn,
915
+ fn_surf_refined=roi_refined_fn,
916
+ center=[0, 0, 0],
917
+ radius=np.inf,
918
+ verbose=True,
919
+ repair=False)
921
920
 
922
921
  roi.append(trimesh.load(roi_refined_fn))
923
922
 
@@ -1004,7 +1003,7 @@ class RegionOfInterestSurface:
1004
1003
  RegionOfInterestSurface.tet_idx_node_coord_mid : np.ndarray
1005
1004
  (N_tri) Tetrahedra indices of TetrahedraLinear object instance where the nodes of the middle
1006
1005
  surface are.
1007
- """
1006
+ """
1008
1007
  # determine tetrahedra indices of triangle center points of upper, middle and lower surface
1009
1008
  if self.tri_center_coord_low != [] and self.tri_center_coord_up != []:
1010
1009
  points = [self.tri_center_coord_low,
@@ -1049,7 +1048,7 @@ class RegionOfInterestSurface:
1049
1048
  ele_idx : np.ndarray of float
1050
1049
  [approx. fraction * n_ele] Element indices of the subsampled surface; sorted.
1051
1050
  """
1052
-
1051
+ import trimesh
1053
1052
  mesh = trimesh.Trimesh(
1054
1053
  vertices=self.node_coord_mid,
1055
1054
  faces=self.node_number_list
@@ -1079,7 +1078,6 @@ class RegionOfInterestSurface:
1079
1078
  ele_idx : ndarray of float
1080
1079
  (n_ele) Element indices of the subsampled surface.
1081
1080
  """
1082
-
1083
1081
  # load sphere surface
1084
1082
  sphere_coords, sphere_faces = nib.freesurfer.io.read_geometry(fn_sphere)
1085
1083
 
@@ -1140,7 +1138,6 @@ class RegionOfInterestVolume:
1140
1138
  the ROI mesh is not the same as the head mesh, the triangle center of the ROI mesh are always lying in a
1141
1139
  tetrahedra of the head mesh (these indices are given in this case).
1142
1140
  """
1143
-
1144
1141
  def __init__(self):
1145
1142
  """ Initialize RegionOfInterestVolume class instance """
1146
1143
 
@@ -1165,10 +1162,12 @@ class RegionOfInterestVolume:
1165
1162
 
1166
1163
  - type = 'box': [Xmin, Xmax] (in mm), whole X range if empty [0,0] or None (left - right)
1167
1164
  - type = 'sphere': origin [x,y,z]
1165
+
1168
1166
  y_roi: list of float
1169
1167
 
1170
1168
  - type = 'box': [Ymin, Ymax] (in mm), whole Y range if empty [0,0] or None (anterior - posterior)
1171
1169
  - type = 'sphere': radius (in mm)
1170
+
1172
1171
  z_roi: list of float
1173
1172
 
1174
1173
  - type = 'box': [Zmin, Zmax] (in mm), whole Z range if empty [0,0] or None (inferior - superior)
@@ -1189,12 +1188,11 @@ class RegionOfInterestVolume:
1189
1188
  lying in
1190
1189
  RegionOfInterestVolume.tet_idx_triangle_center : np.ndarray [N_tri_roi]
1191
1190
  Tetrahedra indices of TetrahedraLinear object instance where the center points of the ROI triangle are
1192
- . If the ROI is directly generated from the msh instance using "make_roi_volume_from_msh", these
1191
+ If the ROI is directly generated from the msh instance using "make_roi_volume_from_msh", these
1193
1192
  indices are the triangle indices of the head mesh since the ROI mesh and the head mesh are overlapping. If
1194
1193
  the ROI mesh is not the same as the head mesh, the triangle center of the ROI mesh are always. a
1195
1194
  tetrahedra of the head mesh (these indices are given in this case)
1196
1195
  """
1197
-
1198
1196
  if volume_type == 'box':
1199
1197
  roi_mask_bool = (msh.points[:, 0] > min(x_roi)) & (msh.points[:, 0] < max(x_roi)) & \
1200
1198
  (msh.points[:, 1] > min(y_roi)) & (msh.points[:, 1] < max(y_roi)) & \