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
@@ -2,9 +2,11 @@ import os
2
2
  import meshio
3
3
  import trimesh
4
4
  import warnings
5
- import nibabel
5
+ import itertools
6
6
  import numpy as np
7
+ from nibabel.affines import apply_affine
7
8
  from tqdm import tqdm
9
+ import nibabel as nib
8
10
  from scipy.interpolate import griddata
9
11
  from vtkmodules.util import numpy_support # don't import vtk due to opengl issues when running headless
10
12
  from vtkmodules.util.vtkConstants import VTK_TRIANGLE
@@ -102,7 +104,7 @@ def data_elements2nodes(data, con, precise=False):
102
104
  for i in range(n_elements):
103
105
  c[i, (con[i])] = 1.0 / con.shape[1]
104
106
 
105
- # filter out NaN from dataset
107
+ # filter out nan from dataset
106
108
  for i in range(len(data)):
107
109
  data[i][np.isnan(data[i])] = 0
108
110
 
@@ -159,7 +161,7 @@ def project_on_scalp_hdf5(coords, mesh, scalp_tag=1005):
159
161
  """
160
162
  # read head mesh and extract skin surface
161
163
  if isinstance(mesh, str):
162
- mesh = pynibs.load_mesh_hdf5(mesh)
164
+ mesh = pynibs.hdf5_io.load_mesh_hdf5(mesh)
163
165
 
164
166
  if coords.ndim == 1:
165
167
  coords = coords[np.newaxis,]
@@ -179,26 +181,26 @@ def project_on_scalp_hdf5(coords, mesh, scalp_tag=1005):
179
181
 
180
182
  def project_on_scalp(coords, mesh, scalp_tag=1005):
181
183
  """
182
- Find the node in the scalp closest to each coordinate
184
+ Find the node in the scalp closest to each coordinate.
183
185
 
184
186
  Parameters
185
187
  ----------
186
188
  coords: nx3 np.ndarray
187
- Vectors to be transformed
189
+ Vectors to be transformed.
188
190
  mesh: pynibs.TetrahedraLinear or simnibs.msh.mesh_io.Msh
189
- Mesh structure in simnibs or pynibs format
191
+ Mesh structure in simnibs or pynibs format.
190
192
  scalp_tag: int, default: 1005
191
193
  Tag in the mesh where the scalp is to be set.
192
194
 
193
195
  Returns
194
196
  -------
195
197
  points_closest: np.ndarry
196
- (n, 3) coordinates projected scalp (closest skin points)
198
+ (n, 3) coordinates projected scalp (closest skin points).
197
199
  """
198
200
  from simnibs.msh.transformations import project_on_scalp as project_on_scalp_msh
199
201
  from simnibs.msh.mesh_io import Msh
200
202
 
201
- if isinstance(mesh, pynibs.TetrahedraLinear):
203
+ if isinstance(mesh, pynibs.mesh.TetrahedraLinear):
202
204
  points_closest = project_on_scalp_hdf5(coords=coords, mesh=mesh, scalp_tag=scalp_tag)
203
205
  elif isinstance(mesh, Msh):
204
206
  points_closest = project_on_scalp_msh(coords=coords, mesh=mesh, scalp_tag=scalp_tag, distance=0.)
@@ -453,12 +455,12 @@ def map_data_to_surface(datasets, points_datasets, con_datasets, fname_fsl_gm, f
453
455
  Parameters
454
456
  ----------
455
457
  datasets : np.ndarray of float [N_points x N_data] or list of np.ndarray
456
- Data in nodes or center of triangles in ROI (specify this in "data_in_center")
458
+ Data in nodes or center of triangles in ROI (specify this in "data_in_center").
457
459
  points_datasets : np.ndarray of float [N_points x 3] or list of np.ndarray
458
460
  Point coordinates (x,y,z) of ROI where data in datasets list is given, the points have to be a subset of the
459
- GM/WM surface (has to be provided for each dataset)
461
+ GM/WM surface (has to be provided for each dataset).
460
462
  con_datasets : np.ndarray of int [N_tri x 3] or list of np.ndarray
461
- Connectivity matrix of dataset points (has to be provided for each dataset)
463
+ Connectivity matrix of dataset points (has to be provided for each dataset).
462
464
  fname_fsl_gm : str or list of str or list of None
463
465
  Filename of pial surface fsl file(s) (one or two hemispheres)
464
466
  e.g. in mri2msh: .../fs_ID/surf/lh.pial
@@ -473,18 +475,18 @@ def map_data_to_surface(datasets, points_datasets, con_datasets, fname_fsl_gm, f
473
475
  0 -> WM surface
474
476
  1 -> GM surface
475
477
  input_data_in_center : bool
476
- Flag if data in datasets in given in triangle centers or in points (Default: True)
478
+ Flag if data in datasets in given in triangle centers or in points (Default: True).
477
479
  return_data_in_center : bool
478
- Flag if data should be returned in nodes or in elements (Default: True)
480
+ Flag if data should be returned in nodes or in elements (Default: True).
479
481
  data_substitute : float
480
- Data substitute with this number for all points in the inflated brain, which do not belong to the given data set
482
+ Data substitute with this number for all points in the inflated brain,
483
+ which do not belong to the given data set.
481
484
 
482
485
  Returns
483
486
  -------
484
487
  data_mapped : np.ndarray of float [N_points_inf x N_data]
485
- Mapped data to target brain surface. In points or elements
488
+ Mapped data to target brain surface. In points or elements.
486
489
  """
487
-
488
490
  if type(fname_fsl_gm) is not list:
489
491
  fname_fsl_gm = [fname_fsl_gm]
490
492
 
@@ -502,8 +504,8 @@ def map_data_to_surface(datasets, points_datasets, con_datasets, fname_fsl_gm, f
502
504
  con_idx = 0
503
505
 
504
506
  for f_gm, f_wm in zip(fname_fsl_gm, fname_fsl_wm):
505
- p_gm, c_tar = nibabel.freesurfer.read_geometry(f_gm)
506
- p_wm, _ = nibabel.freesurfer.read_geometry(f_wm)
507
+ p_gm, c_tar = nib.freesurfer.read_geometry(f_gm)
508
+ p_wm, _ = nib.freesurfer.read_geometry(f_wm)
507
509
 
508
510
  points_gm.append(p_gm)
509
511
  points_wm.append(p_wm)
@@ -528,11 +530,11 @@ def map_data_to_surface(datasets, points_datasets, con_datasets, fname_fsl_gm, f
528
530
 
529
531
  for f_mid in fname_midlayer:
530
532
  if f_mid.endswith('.gii'):
531
- img = nibabel.gifti.giftiio.read(f_mid)
533
+ img = nib.load(f_mid)
532
534
  p_mid = img.agg_data('pointset')
533
535
  c_tar = img.agg_data('triangle')
534
536
  else:
535
- p_mid, c_tar = nibabel.freesurfer.read_geometry(f_mid)
537
+ p_mid, c_tar = nib.freesurfer.read_geometry(f_mid)
536
538
 
537
539
  points.append(p_mid)
538
540
  con_target.append(c_tar + con_idx)
@@ -679,7 +681,7 @@ def midlayer_2_surf(midlayer_data, coords_target, coords_midlayer, midlayer_con=
679
681
  """
680
682
  if not midlayer_data_in_nodes:
681
683
  assert midlayer_con is not None
682
- midlayer_data = np.squeeze(pynibs.data_elements2nodes(midlayer_data, midlayer_con, precise=precise_map))
684
+ midlayer_data = np.squeeze(pynibs.mesh.data_elements2nodes(midlayer_data, midlayer_con, precise=precise_map))
683
685
 
684
686
  data_target = np.zeros((coords_target.shape[0]))
685
687
  for i in tqdm(range(data_target.shape[0]), desc='Mapping midlayer2surface'):
@@ -864,3 +866,101 @@ def write_vtu(fn, vtk_grid):
864
866
  writer.SetFileName(fn)
865
867
  writer.SetInputData(vtk_grid)
866
868
  writer.Write()
869
+
870
+
871
+ def map_nii2surf(tri_node_coords, nii, datatype=float, nonzeroneighbors=False):
872
+ """
873
+ Map nifti data to mesh surface.
874
+
875
+ For each surface triangle,
876
+
877
+ - the center coordinate is determined
878
+ - and the corresponding voxel value from the nifti file is extracted.
879
+
880
+ Raw, unsmoothed data is returned. Nifti file and mesh needs to be in the same space.
881
+
882
+ .. figure:: ../../doc/images/nii_on_surf.png
883
+ :scale: 60 %
884
+ :alt: Nifti volume on mesh surface
885
+
886
+ Volume data from nifti file is mapped to mesh surface.
887
+
888
+ .. code-block:: python
889
+
890
+ fn_roi = "geo.hdf5"
891
+ fn_nii = "some_T_map.nii"
892
+
893
+ nii = nib.load(fn_nii)
894
+
895
+ with h5py.File(fn_roi, 'r') as f:
896
+ triangle_number_list = f['/mesh/elm/triangle_number_list'][:]
897
+ node_coord = f['/mesh/nodes/node_coord'][:]
898
+
899
+ nii_data_on_surf = pynibs.map_nii2surf(node_coord[triangle_number_list], nii)
900
+ output_fn = "some_T_map_on_surf.vtk"
901
+ point_data = {"T": [nii_data_on_surf]}
902
+ meshio.Mesh(np.squeeze(node_coord), [('triangle', triangle_number_list)], cell_data=point_data).write(output_fn)
903
+
904
+ Parameters
905
+ ----------
906
+ tri_node_coords: np.ndarray of int
907
+ (n,3,3) list of node coordinates for n trianlges (mesh surface).
908
+ nii: nibabel.Nifti1Image or str
909
+ Nifti image or filename of nifti.
910
+ datatype: type, default: float
911
+ Data type of the output array. For masks, use int.
912
+ nonzeroneighbors : boolean, default=False
913
+ If True, zero datapoints are replaced by largest absolute neihbors voxel data.
914
+ This is useful for masked niftis, where data outside of the mask is zero and you want to pull data
915
+ only from inside the mask.
916
+
917
+ Returns
918
+ -------
919
+ np.ndarray of float
920
+ (n,) array with voxel values from nifti file for each triangle center.
921
+ """
922
+ coords_center = np.average(tri_node_coords, axis=1)
923
+
924
+ if isinstance(nii, str):
925
+ nii = nib.load(nii)
926
+
927
+ # load nifti data
928
+ nii_data = nii.get_fdata().copy()
929
+ affine = nii.affine
930
+ affine_inv = np.linalg.inv(affine)
931
+
932
+ # create empty results arrays
933
+ fmri_surf = np.zeros_like(coords_center)
934
+
935
+ # pull voxel data for each triangle
936
+ t = tqdm(coords_center)
937
+ t.set_description("Mapping .nii to mesh")
938
+ for i, coord in enumerate(t):
939
+ # we need to apply an array2voxel coordinates transformations
940
+ fmri_surf[i, :] = apply_affine(affine_inv, coord)
941
+
942
+ fmri_stats = np.round(fmri_surf).astype(int)
943
+ for dim in range(3):
944
+ fmri_stats[:, dim] = np.clip(fmri_stats[:, dim], 0, nii_data.shape[dim] - 1)
945
+ # print(np.min(fmri_stats[:,0]), np.max(fmri_stats[:,0]))
946
+ # print(np.min(fmri_stats[:, 1]), np.max(fmri_stats[:, 1]))
947
+ # print(np.min(fmri_stats[:,2]), np.max(fmri_stats[:,2]))
948
+ # a = nii_data[fmri_stats[:, 0], fmri_stats[:, 1], fmri_stats[:, 2]].astype(datatype)
949
+ # b = nii_data[fmri_stats[:, 0], fmri_stats[:, 1], fmri_stats[:, 2]].astype(datatype)
950
+ # grab the data
951
+
952
+ plusminus = list(itertools.product(*[[1, -1]] * 3))
953
+
954
+ mapped_data = nii_data[fmri_stats[:, 0], fmri_stats[:, 1], fmri_stats[:, 2]]
955
+ for zero_idx in np.where(mapped_data == 0)[0]:
956
+ offset_data = []
957
+ zero_arr_coords = fmri_stats[zero_idx]
958
+ # mapped_data_zero = nii_data[zero_arr_coords[0], zero_arr_coords[1], zero_arr_coords[2]][0]
959
+ for x,y,z in plusminus:
960
+ offset_data.append(nii_data[zero_arr_coords[0]+y,
961
+ zero_arr_coords[1]+y,
962
+ zero_arr_coords[2]+z][0])
963
+ best_offset = np.argmax(np.abs(offset_data))
964
+ fmri_stats[zero_idx] += plusminus[best_offset]
965
+
966
+ return nii_data[fmri_stats[:, 0], fmri_stats[:, 1], fmri_stats[:, 2]].astype(datatype)