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
pynibs/util/rotations.py CHANGED
@@ -38,7 +38,6 @@ def quat_rotation_angle(q):
38
38
  alpha : float
39
39
  Rotation angle of quaternion in rad.
40
40
  """
41
-
42
41
  if len(q) == 3:
43
42
  return 2 * np.arcsin(np.linalg.norm(q))
44
43
  elif len(q) == 4:
@@ -87,7 +86,6 @@ def rot_to_quat(rot):
87
86
  q : np.ndarray of float
88
87
  Quaternion, full (length=4).
89
88
  """
90
-
91
89
  rot = rot.flatten()
92
90
  t = 1. + rot[0] + rot[4] + rot[8]
93
91
  if t > np.finfo(rot.dtype).eps:
@@ -121,10 +119,15 @@ def quaternion_conjugate(q):
121
119
  """
122
120
  https://stackoverflow.com/questions/15425313/inverse-quaternion
123
121
 
124
- :param q:
125
- :type q:
126
- :return:
127
- :rtype:
122
+ Parameters
123
+ ----------
124
+ q : np.ndarray
125
+ Input quaternion.
126
+
127
+ Returns
128
+ -------
129
+ np.ndarray
130
+ Conjugate of the input quaternion.
128
131
  """
129
132
  return np.array((-q[0], -q[1], -q[2]))
130
133
 
@@ -185,7 +188,6 @@ def euler_angles_to_rotation_matrix(theta):
185
188
  r : np.ndarray
186
189
  (3) Rotation matrix (z, y', x'').
187
190
  """
188
-
189
191
  # theta in rad
190
192
  r_x = np.array([[1., 0., 0.],
191
193
  [0., math.cos(theta[0]), -math.sin(theta[0])],
@@ -277,7 +279,6 @@ def rotate_matsimnibs_euler(axis, angle, matsimnibs, metric='rad'):
277
279
  rotated_matsimnibs : np.ndarray
278
280
  (4, 4) Rotated system.
279
281
  """
280
-
281
282
  if metric.lower().startswith('deg'):
282
283
  angle = np.deg2rad(angle)
283
284
  elif not metric.lower().startswith('rad'):
@@ -317,7 +318,7 @@ def rotate_matsimnibs_euler(axis, angle, matsimnibs, metric='rad'):
317
318
 
318
319
  def rotmat_from_vecs(vec1, vec2):
319
320
  """
320
- Find the rotation matrix that aligns vec1 to vec2
321
+ Find the rotation matrix that aligns vec1 to vec2.
321
322
 
322
323
  Parameters
323
324
  ----------
@@ -338,3 +339,109 @@ def rotmat_from_vecs(vec1, vec2):
338
339
  kmat = np.array([[0, -v[2], v[1]], [v[2], 0, -v[0]], [-v[1], v[0], 0]])
339
340
  rotation_matrix = np.eye(3) + kmat + kmat.dot(kmat) * ((1 - c) / (s ** 2))
340
341
  return Rotation.from_matrix(rotation_matrix)
342
+
343
+
344
+ def matsim2paraview(matsim):
345
+ """
346
+ Convert a matsimnibs matrix to a format that can be used in ParaView.
347
+
348
+ Parameters
349
+ ----------
350
+ matsim
351
+ (4, 4) matsimnibs matrix.
352
+ """
353
+ print(f"rotations: {Rotation.from_matrix(matsim[:3, :3]).as_euler('xyz', degrees=True)}")
354
+ print(f"position: {matsim[:3, 3]}")
355
+
356
+
357
+ def points_to_triangles(points, triangles):
358
+ """
359
+ Compute the closest points on a triangle mesh to a set of points.
360
+
361
+ Taken from https://stackoverflow.com/questions/32342620/closest-point-projection-of-a-3d-point-to-3d-triangles-with-numpy-scipy
362
+
363
+ Parameters
364
+ ----------
365
+ points : np.ndarray
366
+ (n, 3) Points to project.
367
+ triangles : np.ndarray
368
+ (m, 3, 3) Triangle points.
369
+
370
+ Returns
371
+ -------
372
+ np.ndarray
373
+ (n, 3) Closest points on the triangle mesh.
374
+ """
375
+ with np.errstate(all='ignore'):
376
+ # Unpack triangle points
377
+ p0, p1, p2 = np.asarray(triangles).swapaxes(0, 1)
378
+
379
+ # Calculate triangle edges
380
+ e0 = p1 - p0
381
+ e1 = p2 - p0
382
+ a = np.einsum('...i,...i', e0, e0)
383
+ b = np.einsum('...i,...i', e0, e1)
384
+ c = np.einsum('...i,...i', e1, e1)
385
+
386
+ # Calculate determinant and denominator
387
+ det = a * c - b * b
388
+ invDet = 1. / det
389
+ denom = a - 2 * b + c
390
+
391
+ # Project to the edges
392
+ p = p0 - points[:, np.newaxis]
393
+ d = np.einsum('...i,...i', e0, p)
394
+ e = np.einsum('...i,...i', e1, p)
395
+ u = b * e - c * d
396
+ v = b * d - a * e
397
+
398
+ # Calculate numerators
399
+ bd = b + d
400
+ ce = c + e
401
+ numer0 = (ce - bd) / denom
402
+ numer1 = (c + e - b - d) / denom
403
+ da = -d / a
404
+ ec = -e / c
405
+
406
+ # Vectorize test conditions
407
+ m0 = u + v < det
408
+ m1 = u < 0
409
+ m2 = v < 0
410
+ m3 = d < 0
411
+ m4 = (a + d > b + e)
412
+ m5 = ce > bd
413
+
414
+ t0 = m0 & m1 & m2 & m3
415
+ t1 = m0 & m1 & m2 & ~m3
416
+ t2 = m0 & m1 & ~m2
417
+ t3 = m0 & ~m1 & m2
418
+ t4 = m0 & ~m1 & ~m2
419
+ t5 = ~m0 & m1 & m5
420
+ t6 = ~m0 & m1 & ~m5
421
+ t7 = ~m0 & m2 & m4
422
+ t8 = ~m0 & m2 & ~m4
423
+ t9 = ~m0 & ~m1 & ~m2
424
+
425
+ u = np.where(t0, np.clip(da, 0, 1), u)
426
+ v = np.where(t0, 0, v)
427
+ u = np.where(t1, 0, u)
428
+ v = np.where(t1, 0, v)
429
+ u = np.where(t2, 0, u)
430
+ v = np.where(t2, np.clip(ec, 0, 1), v)
431
+ u = np.where(t3, np.clip(da, 0, 1), u)
432
+ v = np.where(t3, 0, v)
433
+ u *= np.where(t4, invDet, 1)
434
+ v *= np.where(t4, invDet, 1)
435
+ u = np.where(t5, np.clip(numer0, 0, 1), u)
436
+ v = np.where(t5, 1 - u, v)
437
+ u = np.where(t6, 0, u)
438
+ v = np.where(t6, 1, v)
439
+ u = np.where(t7, np.clip(numer1, 0, 1), u)
440
+ v = np.where(t7, 1 - u, v)
441
+ u = np.where(t8, 1, u)
442
+ v = np.where(t8, 0, v)
443
+ u = np.where(t9, np.clip(numer1, 0, 1), u)
444
+ v = np.where(t9, 1 - u, v)
445
+
446
+ # Return closest points
447
+ return (p0.T + u[:, np.newaxis] * e0.T + v[:, np.newaxis] * e1.T).swapaxes(2, 1)
@@ -2,19 +2,17 @@ import gc
2
2
  import os
3
3
  import re
4
4
  import copy
5
-
6
5
  import h5py
7
6
  import tqdm
8
- import trimesh
9
-
10
- try:
11
- import simnibs
12
- except (ModuleNotFoundError, ImportError):
13
- print("SimNIBS not found. Some functions might not work.")
14
7
  import warnings
15
8
  import collections
16
9
  import numpy as np
17
-
10
+ try:
11
+ from simnibs import ElementData
12
+ except ImportError:
13
+ from simnibs.mesh_tools.mesh_io import ElementData
14
+ except (ModuleNotFoundError, ImportError) as e:
15
+ print("SimNIBS not found. Some functions might not work.")
18
16
  import pynibs
19
17
 
20
18
 
@@ -78,7 +76,7 @@ def e_field_gradient_between_wm_gm(
78
76
  e_field_gradient_per_mm = e_field_gradient / gray_matter_thickness
79
77
  relative_e_field_gradient_per_mm = e_field_gradient_per_mm / e_field_center * 100
80
78
 
81
- return simnibs.ElementData(relative_e_field_gradient_per_mm, name='rel_gradient_per_mm', mesh=roi_surf)
79
+ return ElementData(relative_e_field_gradient_per_mm, name='rel_gradient_per_mm', mesh=roi_surf)
82
80
 
83
81
 
84
82
  def e_field_angle_theta(surface, mesh):
@@ -107,7 +105,7 @@ def e_field_angle_theta(surface, mesh):
107
105
  angle = np.array([np.arccos(np.dot(a, b)) / np.pi * 180 for a, b in
108
106
  zip(tri_normal, e_at_roi_cell_centers_normalized)])
109
107
 
110
- return simnibs.ElementData(angle, name="theta", mesh=surface)
108
+ return ElementData(angle, name="theta", mesh=surface)
111
109
 
112
110
 
113
111
  def precompute_geo_info_for_layer_field_interpolation(simnibs_mesh, roi):
@@ -300,6 +298,11 @@ def calc_e_in_midlayer_roi(
300
298
  (roi.n_tris, 4) : np.vstack((e_mag, e_norm, e_tan, e_angle)).transpose() for the midlayer
301
299
  (len(roi.layers)x(roi.layers[idx].surface.n_tris,4)) : np.vstack((e_mag, e_norm, e_tan, e_angle)).transpose()
302
300
  """
301
+ try:
302
+ from simnibs import ElementData
303
+ except ImportError:
304
+ from simnibs.mesh_tools.mesh_io import ElementData
305
+ import simnibs
303
306
  # set default return quantities
304
307
  if qoi is None:
305
308
  qoi = ['E', 'mag', 'norm', 'tan', 'angle']
@@ -340,15 +343,15 @@ def calc_e_in_midlayer_roi(
340
343
  # We used to use (v, dadt) but nowadays E is enough
341
344
  if isinstance(e, tuple):
342
345
  e = e[0]
343
- # simnibs calls this with empty data so find out about the results dimensions
346
+ # simnibs calls this with empty data to find out about the results dimensions
344
347
  if (e == 0).all():
345
348
  return np.zeros((max_num_elmts, ret_arr_num_components))
346
349
 
347
350
  # calc QOIs (all calculations are performed in the element centers)
348
- data = simnibs.ElementData(e, name='E', mesh=mesh)
351
+ data = ElementData(e, name='E', mesh=mesh)
349
352
 
350
353
  interp_res_per_surface = dict()
351
- mesh.elmdata.append(simnibs.ElementData(e, name='E', mesh=mesh))
354
+ mesh.elmdata.append(ElementData(e, name='E', mesh=mesh))
352
355
 
353
356
  for surface_id, surface in interpolation_surfaces.items():
354
357
  print(f"Computing {surface_id}")
@@ -473,6 +476,9 @@ def read_coil_geo(fn_coil_geo):
473
476
  mag_or_vec = [float(f) for f in mag_or_vec.split(', ')]
474
477
  if len(mag_or_vec) > 1:
475
478
  mag_or_vec = np.linalg.norm(mag_or_vec)
479
+ elif isinstance(mag_or_vec, list):
480
+ mag_or_vec = mag_or_vec[0]
481
+ # elif
476
482
  # else:
477
483
  # mag_or_vec = mag_or_vec[0]
478
484
  dipole_pos.append(pos)
@@ -512,24 +518,25 @@ def check_mesh(mesh, verbose=False):
512
518
  neg_tets : np.ndarray
513
519
  Element indicies for negative volume tets (0-indexed)
514
520
  """
521
+ import simnibs
515
522
  if isinstance(mesh, str):
516
523
  mesh = simnibs.read_msh(mesh)
517
524
  tris = mesh.elm.node_number_list[mesh.elm.triangles - 1][:, :3]
518
525
  points_tri = mesh.nodes[tris]
519
- tri_area = pynibs.calc_tri_surface(points_tri)
526
+ tri_area = pynibs.mesh.calc_tri_surface(points_tri)
520
527
  zero_tris = np.argwhere(np.isclose(tri_area, 0, atol=1e-13))
521
528
  if verbose:
522
529
  print(f"{len(zero_tris)} zero surface triangles found.")
523
530
 
524
531
  tets = mesh.elm.node_number_list[mesh.elm.tetrahedra - 1]
525
532
  points_tets = mesh.nodes[tets]
526
- tets_volume = pynibs.calc_tet_volume(points_tets)
533
+ tets_volume = pynibs.mesh.calc_tet_volume(points_tets)
527
534
  zero_tets = np.argwhere(np.isclose(tets_volume, 0, atol=1e-13))
528
535
  if verbose:
529
536
  print(f"{len(zero_tets)} zero volume tetrahedra found.")
530
537
 
531
538
  tet_idx = mesh.elm.node_number_list[mesh.elm.tetrahedra - 1]
532
- vol = pynibs.calc_tet_volume(mesh.nodes.node_coord[tet_idx - 1], abs=False)
539
+ vol = pynibs.mesh.calc_tet_volume(mesh.nodes.node_coord[tet_idx - 1], abs=False)
533
540
  neg_idx = np.argwhere(vol > 0)
534
541
  if verbose:
535
542
  print(f"{len(neg_idx)} negative tets found.")
@@ -556,6 +563,7 @@ def fix_mesh(mesh, verbose=False):
556
563
  -------
557
564
  fixed_mesh : simnibs.Mesh
558
565
  """
566
+ import simnibs
559
567
  if isinstance(mesh, str):
560
568
  mesh = simnibs.read_msh(mesh)
561
569
 
@@ -611,6 +619,8 @@ def smooth_mesh(mesh, output_fn, smooth=.8, approach='taubin', skin_only_output=
611
619
 
612
620
  Left: original, spiky mesh. Right: smoothed mesh.
613
621
  """
622
+ import trimesh
623
+ import simnibs
614
624
  if isinstance(mesh, str):
615
625
  mesh = simnibs.mesh_io.read_msh(mesh)
616
626
 
@@ -689,7 +699,7 @@ def get_opt_mat(folder, roi=0):
689
699
  """
690
700
  Load optimal coil position/orientation matsimnibs from SimNIBS online FEM.
691
701
 
692
- Parameter:
702
+ Parameters
693
703
  ----------
694
704
  folder : str
695
705
  Folder with optimization results.
@@ -737,7 +747,7 @@ def get_skin_cortex_distance(mesh, coords, radius=5):
737
747
  mesh : str or simnibs.Mesh
738
748
  Mesh object or .msh filename.
739
749
  coords : np.ndarray
740
- [3,] x,y,z coordinates to compute skin-cortex-distacnce for.
750
+ [3,] x,y,z coordinates to compute skin-cortex-distance for.
741
751
  radius : float, default: 5
742
752
  Spherical radius around ``coords`` to include points in for SCD computation.
743
753
 
@@ -746,7 +756,7 @@ def get_skin_cortex_distance(mesh, coords, radius=5):
746
756
  SCD : np.ndarray of float
747
757
  Skin-cortex distance.
748
758
  elm_in_roi : np.ndarray of int
749
- Elelement numbers from mesh.elm.elm_number that where used to calculate PCD for.
759
+ Element numbers from mesh.elm.elm_number that where used to calculate PCD for.
750
760
  """
751
761
  from simnibs import read_msh
752
762
  if isinstance(mesh, str):
@@ -7,8 +7,6 @@ import warnings
7
7
  import itertools
8
8
  import subprocess
9
9
  import numpy as np
10
- from scipy.special import binom
11
- from sklearn.neighbors import KernelDensity
12
10
  import pynibs
13
11
 
14
12
 
@@ -23,7 +21,7 @@ def tal2mni(coords, direction='tal2mni', style='nonlinear'):
23
21
  ----------
24
22
  coords : np.ndarray or list
25
23
  x,y,z coordinates.
26
- direction : str, default: 'tal2mni
24
+ direction : str, default: 'tal2mni'
27
25
  Transformation direction. One of ('tal2mni', 'mni2tal').
28
26
  style : str, default: 'nonlinear'
29
27
  Transformation style. One of ('linear', 'nonlinear').
@@ -31,7 +29,7 @@ def tal2mni(coords, direction='tal2mni', style='nonlinear'):
31
29
  Returns
32
30
  -------
33
31
  coords_trans : np.ndarray
34
-
32
+ Transformed coordinates.
35
33
  """
36
34
  assert direction in ['tal2mni',
37
35
  'mni2tal'], f"direction parameter '{direction}' invalid. Choose 'tal2mni' or 'mni2tal'."
@@ -84,7 +82,6 @@ def rd(array, array_ref):
84
82
  rd : ndarray of float
85
83
  (array.shape[1]) Relative difference between the columns of array and array_ref.
86
84
  """
87
-
88
85
  return np.linalg.norm(array - array_ref) / np.linalg.norm(array_ref)
89
86
 
90
87
 
@@ -115,7 +112,7 @@ def generalized_extreme_value_distribution(x, mu, sigma, k):
115
112
 
116
113
  def differential_evolution(fobj, bounds, mut=0.8, crossp=0.7, popsize=20, its=1000, **kwargs):
117
114
  """
118
- Differential evolution optimization algorithm
115
+ Differential evolution optimization algorithm.
119
116
 
120
117
  Parameters
121
118
  ----------
@@ -139,7 +136,7 @@ def differential_evolution(fobj, bounds, mut=0.8, crossp=0.7, popsize=20, its=10
139
136
  best : dict
140
137
  Dictionary containing the best values.
141
138
  fitness : float
142
- Fitness value of best solution.
139
+ Fitness value of the best solution.
143
140
  """
144
141
  if kwargs is None:
145
142
  kwargs = dict()
@@ -299,6 +296,7 @@ def likelihood_posterior(x, y, fun, bounds=None, verbose=True, normalized_params
299
296
  x_bins_loc = np.linspace(np.min(x_pre) + dx_bins / 2, np.max(x_pre) - dx_bins / 2, n_bins)
300
297
 
301
298
  # determine probabilities of observations
299
+ from sklearn.neighbors import KernelDensity
302
300
  kde = KernelDensity(bandwidth=0.01, kernel='gaussian')
303
301
 
304
302
  likelihood = []
@@ -314,7 +312,7 @@ def likelihood_posterior(x, y, fun, bounds=None, verbose=True, normalized_params
314
312
  try:
315
313
  kde_bins = kde.fit(y_post[mask][:, np.newaxis])
316
314
  except ValueError:
317
- warnings.warn("kde.fit(y_post[mask][:, np.newaxis]) yield NaN ... skipping bin")
315
+ warnings.warn("kde.fit(y_post[mask][:, np.newaxis]) yield nan ... skipping bin")
318
316
  continue
319
317
 
320
318
  # get probability densities at data
@@ -345,7 +343,7 @@ def mutual_coherence(array):
345
343
  Calculate the mutual coherence of a matrix A. It can also be referred as the cosine of the smallest angle
346
344
  between two columns.
347
345
 
348
- mutual_coherence = mutual_coherence(array)
346
+ ``mutual_coherence = mutual_coherence(array)``
349
347
 
350
348
  Parameters
351
349
  ----------
@@ -357,7 +355,6 @@ def mutual_coherence(array):
357
355
  mutual_coherence : float
358
356
  Mutual coherence.
359
357
  """
360
-
361
358
  array = array / np.linalg.norm(array, axis=0)[np.newaxis, :]
362
359
  t = np.matmul(array.conj().T, array)
363
360
  np.fill_diagonal(t, 0.0)
@@ -374,7 +371,7 @@ def get_cartesian_product(array_list):
374
371
  """
375
372
  Generate a cartesian product of input arrays (all combinations).
376
373
 
377
- cartesian_product = get_cartesian_product(array_list)
374
+ ``cartesian_product = get_cartesian_product(array_list)``
378
375
 
379
376
  Parameters
380
377
  ----------
@@ -388,10 +385,13 @@ def get_cartesian_product(array_list):
388
385
 
389
386
  Examples
390
387
  --------
391
- .. code-block:: python
388
+
389
+ .. code-block:: python
390
+
392
391
  import pygpc
393
392
  out = pygpc.get_cartesian_product(([1, 2, 3], [4, 5], [6, 7]))
394
393
  out
394
+
395
395
  """
396
396
  cartesian_product = [element for element in itertools.product(*array_list)]
397
397
  return np.array(cartesian_product)
@@ -419,7 +419,7 @@ def norm_percentile(data, percentile):
419
419
  def compute_chunks(seq, num):
420
420
  """
421
421
  Splits up a sequence _seq_ into _num_ chunks of similar size.
422
- If len(seq) < num, (num-len(seq)) empty chunks are returned so that len(out) == num.
422
+ If ``len(seq) < num``, (num-len(seq)) empty chunks are returned so that ``len(out) == num``.
423
423
 
424
424
  Parameters
425
425
  ----------
@@ -485,9 +485,6 @@ def bash(command):
485
485
  """
486
486
  print(("Running " + command))
487
487
  return os.popen(command).read()
488
- # process = subprocess.Popen(command.split(), stdout=subprocess.PIPE, shell=True)
489
- # output, error = process.communicate()
490
- # return output, error
491
488
 
492
489
 
493
490
  def bash_call(command):
@@ -516,7 +513,7 @@ def invert(trans):
516
513
  rot_inv : np.ndarray of float
517
514
  (3, 3) Inverse rotation matrix.
518
515
  """
519
- rot = pynibs.normalize_rot(trans[:3, :3].flatten())
516
+ rot = pynibs.utils.rotation.normalize_rot(trans[:3, :3].flatten())
520
517
  result = np.zeros((4, 4))
521
518
  result[3, 3] = 1.0
522
519
  t = -rot.T.dot(trans[:3, 3])
@@ -599,7 +596,7 @@ def unique_rows(a):
599
596
 
600
597
  Returns
601
598
  -------
602
- a_unique : np.array
599
+ a_unique : np.ndarray
603
600
  (k, n) array a with only unique rows.
604
601
  """
605
602
  b = np.ascontiguousarray(a).view(np.dtype((np.void, a.dtype.itemsize * a.shape[1])))
@@ -627,6 +624,7 @@ def calc_n_network_combs(n_e, n_c, n_i):
627
624
  n_comb : int
628
625
  Number of combinations.
629
626
  """
627
+ from scipy.special import binom
630
628
  return binom(n_e, n_i) * np.sum([((n_i - 1) ** k) * binom(n_c, k) for k in range(1, n_c)])
631
629
 
632
630
 
@@ -634,13 +632,13 @@ def load_muaps(fn_muaps, fs=1e6, fs_downsample=1e5):
634
632
  # load MUAPs and downsample
635
633
  with h5py.File(fn_muaps, "r") as f:
636
634
  muaps_orig = f["MUAPShapes"][:]
637
- N_MU = muaps_orig.shape[1]
635
+ n_mu = muaps_orig.shape[1]
638
636
 
639
637
  t_muap_orig = np.linspace(0, 1 / fs * (muaps_orig.shape[0] - 1), muaps_orig.shape[0])
640
638
  t_muap = np.linspace(0, t_muap_orig[-1], int(t_muap_orig[-1] * fs_downsample + 1))
641
- muaps = np.zeros((len(t_muap), N_MU))
639
+ muaps = np.zeros((len(t_muap), n_mu))
642
640
 
643
- for i in range(N_MU):
641
+ for i in range(n_mu):
644
642
  muaps[:, i] = np.interp(t_muap, t_muap_orig, muaps_orig[:, i])
645
643
 
646
644
  return muaps, t_muap
@@ -653,7 +651,7 @@ def cross_product(A, B):
653
651
  Parameters
654
652
  ----------
655
653
  A : np.ndarray of float
656
- (2, (N, 3))
654
+ (2, (N, 3)) Input vectors, the cross product is evaluated between.
657
655
  B : np.ndarray of float
658
656
  (2, (N, 3)) Input vectors, the cross product is evaluated between.
659
657
 
@@ -856,7 +856,7 @@ def surface_vector_plot(ps):
856
856
  t2LUTColorBar.AddRangeAnnotations = 0
857
857
  t2LUTColorBar.AutomaticAnnotations = 0
858
858
  t2LUTColorBar.DrawNanAnnotation = 0
859
- t2LUTColorBar.NanAnnotation = 'NaN'
859
+ t2LUTColorBar.NanAnnotation = 'nan'
860
860
  t2LUTColorBar.TextPosition = 'Ticks right/top, annotations left/bottom'
861
861
  # t2LUTColorBar.AspectRatio = ps['colorbar_aspectratio'] # paraview.NotSupportedException: 'AspectRatio' is obsolete as of ParaView 5.4. Use the 'ScalarBarThickness' property to set the width instead.
862
862
 
@@ -1746,7 +1746,7 @@ def surface_vector_plot_vtu(ps):
1746
1746
  eLUTColorBar.AddRangeAnnotations = 0
1747
1747
  eLUTColorBar.AutomaticAnnotations = 0
1748
1748
  eLUTColorBar.DrawNanAnnotation = 0
1749
- eLUTColorBar.NanAnnotation = 'NaN'
1749
+ eLUTColorBar.NanAnnotation = 'nan'
1750
1750
  eLUTColorBar.TextPosition = 'Ticks right/top, annotations left/bottom'
1751
1751
  # eLUTColorBar.AspectRatio = ps['colorbar_aspectratio'] # paraview.NotSupportedException: 'AspectRatio' is obsolete as of ParaView 5.4. Use the 'ScalarBarThickness' property to set the width instead.
1752
1752
 
@@ -2854,7 +2854,7 @@ def volume_plot(ps):
2854
2854
  eLUTColorBar.AddRangeAnnotations = 0
2855
2855
  eLUTColorBar.AutomaticAnnotations = 0
2856
2856
  eLUTColorBar.DrawNanAnnotation = 0
2857
- eLUTColorBar.NanAnnotation = 'NaN'
2857
+ eLUTColorBar.NanAnnotation = 'nan'
2858
2858
  eLUTColorBar.TextPosition = 'Ticks right/top, annotations left/bottom'
2859
2859
  # eLUTColorBar.AspectRatio = ps['colorbar_aspectratio'] # paraview.NotSupportedException: 'AspectRatio' is obsolete as of ParaView 5.4. Use the 'ScalarBarThickness' property to set the width instead.
2860
2860
  eLUTColorBar.Title = ps['colorbar_label']
@@ -3868,7 +3868,7 @@ def volume_plot_vtu(ps):
3868
3868
  eLUTColorBar.AddRangeAnnotations = 0
3869
3869
  eLUTColorBar.AutomaticAnnotations = 0
3870
3870
  eLUTColorBar.DrawNanAnnotation = 0
3871
- eLUTColorBar.NanAnnotation = 'NaN'
3871
+ eLUTColorBar.NanAnnotation = 'nan'
3872
3872
  eLUTColorBar.TextPosition = 'Ticks right/top, annotations left/bottom'
3873
3873
  # eLUTColorBar.AspectRatio = ps['colorbar_aspectratio'] # paraview.NotSupportedException: 'AspectRatio' is obsolete as of ParaView 5.4. Use the 'ScalarBarThickness' property to set the width instead.
3874
3874
  eLUTColorBar.Title = ps['colorbar_label']
@@ -22,8 +22,8 @@ def render_coil_positions(
22
22
  optionally also display a second set of coil positions,
23
23
  optionally also display a surface (e.g. gray matter or skin)
24
24
 
25
- Parameters:
26
- -----------
25
+ Parameters
26
+ ----------
27
27
  coil_conf_set_1_positions : np.ndarray
28
28
  The "center" coordinates of the primary coil configurations
29
29
  coil_conf_set_1_orientations : np.ndarray
@@ -38,8 +38,8 @@ def render_coil_positions(
38
38
  Name of the surface to display. Currently supported: "skin", "skull", "csf", "gm", "wm"
39
39
  Only valid if 'fn_mesh' has been provided.
40
40
 
41
- Returns:
42
- --------
41
+ Returns
42
+ -------
43
43
  True if successful, False in case of a fatal error.
44
44
  """
45
45
  try:
@@ -0,0 +1,105 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyNIBS
3
+ Version: 0.2026.1
4
+ Summary: A python toolbox to conduct non-invasive brain stimulation (NIBS) experiments.
5
+ Author: Konstantin Weise, Ole Numssen, Benjamin Kalloch
6
+ Maintainer-email: Konstantin Weise <weise@cbs.mpg.de>, Ole Numssen <numssen@cbs.mpg.de>
7
+ License: GPL3
8
+ Project-URL: Home, https://gitlab.gwdg.de/tms-localization/pynibs
9
+ Project-URL: Docs, https://pynibs.readthedocs.io/
10
+ Project-URL: Twitter, https://www.twitter.com/num_ole
11
+ Project-URL: Bluesky, https://bsky.app/profile/numole.bsky.social
12
+ Project-URL: Download, https://pypi.org/project/pynibs/
13
+ Keywords: NIBS,non-invasive brain stimulation,TMS,FEM
14
+ Classifier: Development Status :: 3 - Alpha
15
+ Classifier: Intended Audience :: Science/Research
16
+ Classifier: Topic :: Scientific/Engineering
17
+ Classifier: Topic :: Software Development :: Build Tools
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Requires-Python: >=3.11
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: cython>=0.29.20
23
+ Requires-Dist: h5py>=2.10.0
24
+ Requires-Dist: lmfit
25
+ Requires-Dist: matplotlib>=3.2.1
26
+ Requires-Dist: meshio
27
+ Requires-Dist: nibabel>=3.0.2
28
+ Requires-Dist: nilearn>=0.6.2
29
+ Requires-Dist: numpy<2
30
+ Requires-Dist: ortools
31
+ Requires-Dist: pandas>=1.0.3
32
+ Requires-Dist: pyyaml>=5.3.1
33
+ Requires-Dist: scikit-learn>=0.22.1
34
+ Requires-Dist: scipy>=1.4.1
35
+ Requires-Dist: scikit-image
36
+ Requires-Dist: setuptools>=49.1.0
37
+ Requires-Dist: tqdm>=4.45.0
38
+ Requires-Dist: trimesh>=3.8.11
39
+ Requires-Dist: transformations
40
+ Requires-Dist: fslpy
41
+ Requires-Dist: pygpc>=0.4.1
42
+ Requires-Dist: uncertainties
43
+ Requires-Dist: asteval
44
+ Requires-Dist: jsonschema
45
+ Requires-Dist: tables
46
+ Requires-Dist: vtk
47
+ Requires-Dist: rtree
48
+ Requires-Dist: tvb-gdist
49
+ Dynamic: license-file
50
+
51
+ # pyNIBS
52
+ Preprocessing, postprocessing, and analyses routines for non-invasive brain stimulation experiments.
53
+
54
+ [![Latest Release](https://gitlab.gwdg.de/tms-localization/pynibs/-/badges/release.svg)](https://gitlab.gwdg.de/tms-localization/pynibs)
55
+ [![Documentation](https://readthedocs.org/projects/pynibs/badge/)](https://pynibs.readthedocs.io/)
56
+ [![pipeline status](https://gitlab.gwdg.de/tms-localization/pynibs/badges/master/pipeline.svg)](https://gitlab.gwdg.de/tms-localization/pynibs/commits/master)
57
+ [![coverage report](https://gitlab.gwdg.de/tms-localization/pynibs/badges/master/coverage.svg)](https://tms-localization.pages.gwdg.de/pynibs)
58
+
59
+ ![](https://gitlab.gwdg.de/uploads/-/system/project/avatar/9753/Fig_4.png?width=128)
60
+
61
+ `pyNIBS` provides the functions to allow **cortical mappings** with transcranial magnetic stimulation (TMS) via **functional analysis**. `pyNIBS` is developed to work with [SimNIBS](http://www.simnibs.org), i.e. SimNIBS' meshes and FEM results can directly be used.
62
+ Currently, [SimNIBS >=4.0](https://github.com/simnibs/simnibs/releases/latest) is supported.
63
+
64
+ See the [documentation](https://pynibs.readthedocs.io/) for package details and our [protocol](https://doi.org/10.1038/s41596-022-00776-6) publication for a extensive usage examples. Free view only version of the paper: https://t.co/uv7CmVw6tp.
65
+
66
+ ## Installation
67
+ Via PiP:
68
+
69
+ ``` bash
70
+ pip install pynibs
71
+ ```
72
+
73
+ Or clone the source repository and install the development branch for the most recent version:
74
+
75
+ ``` bash
76
+ git clone https://gitlab.gwdg.de/tms-localization/pynibs
77
+ cd pynibs
78
+ git checkout dev
79
+ pip install -e .
80
+ ```
81
+
82
+ See [here](https://gitlab.gwdg.de/tms-localization/papers/tmsloc_proto/-/blob/2aec4763edf17e7a2ac604403287bc7fb10285ff/README.md) for more detailed installation instructions.
83
+
84
+ To import CED Signal EMG data use the `export to .mat` feature of Signal.
85
+ To read `.cfs` files exported with CED Signal you might need to [manually](HOW_TO_INSTALL_BIOSIG.txt) compile the libbiosig package.
86
+
87
+
88
+ ## Bugs
89
+ For sure. Please open an [issue](https://gitlab.gwdg.de/tms-localization/pynibs/-/issues) or feel free to file a PR.
90
+
91
+
92
+ ## Citation
93
+ Please cite _Numssen, O., Zier, A. L., Thielscher, A., Hartwigsen, G., Knösche, T. R., & Weise, K. (2021). Efficient high-resolution TMS mapping of the human motor cortex by nonlinear regression. NeuroImage, 245, 118654. doi:[10.1016/j.neuroimage.2021.118654](https://doi.org/10.1016/j.neuroimage.2021.118654)_ when using this toolbox in your research.
94
+
95
+
96
+ ## References
97
+ - Weise*, K., Numssen*, O., Thielscher, A., Hartwigsen, G., & Knösche, T. R. (2020). A novel approach to localize cortical TMS effects. *NeuroImage*, 209, 116486. doi: [10.1016/j.neuroimage.2019.116486](https://doi.org/10.1016/j.neuroimage.2019.116486)
98
+ - Numssen, O., Zier, A. L., Thielscher, A., Hartwigsen, G., Knösche, T. R., & Weise, K. (2021). Efficient high-resolution TMS mapping of the human motor cortex by nonlinear regression. *NeuroImage*, 245, 118654. doi: [10.1016/j.neuroimage.2021.118654](https://doi.org/10.1016/j.neuroimage.2021.118654)
99
+ - Weise*, K., Numssen*, O., Kalloch, B., Zier, A. L., Thielscher, A., Hartwigsen°, G., Knösche°, T. R. (2023). Precise transcranial magnetic stimulation motor-mapping. *Nature Protocols*. doi: [10.1038/s41596-022-00776-6](https://doi.org/10.1038/s41596-022-00776-6)
100
+ - Jing, Y., Numssen, O., Weise, K., Kalloch, B., Buchberger, L., Haueisen, J., Hartwigsen, G., Knösche, T. (2023). Modeling the Effects of Transcranial Magnetic Stimulation on Spatial Attention. *Physics in Medicine & Biology*. doi: [10.1088/1361-6560/acff34](https://doi.org/10.1088/1361-6560/acff34)
101
+ - Numssen*, O., Kuhnke*, P., Weise, K., & Hartwigsen, G. (2024). Electric field based dosing for TMS. *Imaging Neuroscience*. doi: [10.1162/imag_a_00106](https://doi.org/10.1162/imag_a_00106)
102
+ - Numssen, O., Martin, S., Williams, K., Knösche, T. R., & Hartwigsen, G. (2024). Quantification of subject motion during TMS via pulsewise coil displacement. *Brain Stimulation, 17*(5), 1045–1047. doi: [10.1016/j.brs.2024.08.009](https://doi.org/10.1016/j.brs.2024.08.009)
103
+ - Weise, K., Makaroff, S. N., Numssen, O., Bikson, M., & Knösche, T. R. (2025). Statistical method accounts for microscopic electric field distortions around neurons when simulating activation thresholds. *Brain Stimulation, 18*(2), 280–286. doi: [10.1016/j.brs.2025.02.007](https://doi.org/10.1016/j.brs.2025.02.007)
104
+ - Jing, Y., Numssen, O., Hartwigsen, G., Knösche, T. R., & Weise, K. (2024). Effects of Electric Field Direction on TMS-based Motor Cortex Mapping. *bioRxiv*. doi: [10.1101/2024.12.10.627753](https://doi.org/10.1101/2024.12.10.627753)
105
+ - Numssen*, O., Martin*, C. W., Worbs, T., Thielscher, A., Weise, K., & Knösche, T. R. (2025). Optimizing and assessing multichannel TMS focality. *bioRxiv*. doi: [10.1101/2025.09.19.677136](https://doi.org/10.1101/2025.09.19.677136)