pyNIBS 0.2024.8__py3-none-any.whl → 0.2026.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pynibs/__init__.py +26 -14
- pynibs/coil/__init__.py +6 -0
- pynibs/{coil.py → coil/coil.py} +213 -543
- pynibs/coil/export.py +508 -0
- pynibs/congruence/__init__.py +4 -1
- pynibs/congruence/congruence.py +37 -45
- pynibs/congruence/ext_metrics.py +40 -11
- pynibs/congruence/stimulation_threshold.py +1 -2
- pynibs/expio/Mep.py +120 -370
- pynibs/expio/__init__.py +10 -0
- pynibs/expio/brainsight.py +34 -37
- pynibs/expio/cobot.py +25 -25
- pynibs/expio/exp.py +10 -7
- pynibs/expio/fit_funs.py +3 -0
- pynibs/expio/invesalius.py +70 -0
- pynibs/expio/localite.py +190 -91
- pynibs/expio/neurone.py +139 -0
- pynibs/expio/signal_ced.py +345 -2
- pynibs/expio/visor.py +16 -15
- pynibs/freesurfer.py +34 -33
- pynibs/hdf5_io/hdf5_io.py +149 -132
- pynibs/hdf5_io/xdmf.py +35 -31
- pynibs/mesh/__init__.py +1 -1
- pynibs/mesh/mesh_struct.py +77 -92
- pynibs/mesh/transformations.py +121 -21
- pynibs/mesh/utils.py +191 -99
- pynibs/models/_TMS.py +2 -1
- pynibs/muap.py +1 -2
- pynibs/neuron/__init__.py +10 -0
- pynibs/neuron/models/mep.py +566 -0
- pynibs/neuron/neuron_regression.py +98 -8
- pynibs/optimization/__init__.py +12 -2
- pynibs/optimization/{optimization.py → coil_opt.py} +157 -133
- pynibs/optimization/multichannel.py +1174 -24
- pynibs/optimization/workhorses.py +7 -8
- pynibs/regression/__init__.py +4 -2
- pynibs/regression/dual_node_detection.py +229 -219
- pynibs/regression/regression.py +92 -61
- pynibs/roi/__init__.py +4 -1
- pynibs/roi/roi_structs.py +19 -21
- pynibs/roi/{roi.py → roi_utils.py} +56 -33
- pynibs/subject.py +24 -14
- pynibs/util/__init__.py +20 -4
- pynibs/util/dosing.py +4 -5
- pynibs/util/quality_measures.py +39 -38
- pynibs/util/rotations.py +116 -9
- pynibs/util/{simnibs.py → simnibs_io.py} +29 -19
- pynibs/util/{util.py → utils.py} +20 -22
- pynibs/visualization/para.py +4 -4
- pynibs/visualization/render_3D.py +4 -4
- pynibs-0.2026.1.dist-info/METADATA +105 -0
- pynibs-0.2026.1.dist-info/RECORD +69 -0
- {pyNIBS-0.2024.8.dist-info → pynibs-0.2026.1.dist-info}/WHEEL +1 -1
- pyNIBS-0.2024.8.dist-info/METADATA +0 -723
- pyNIBS-0.2024.8.dist-info/RECORD +0 -107
- pynibs/data/configuration_exp0.yaml +0 -59
- pynibs/data/configuration_linear_MEP.yaml +0 -61
- pynibs/data/configuration_linear_RT.yaml +0 -61
- pynibs/data/configuration_sigmoid4.yaml +0 -68
- pynibs/data/network mapping configuration/configuration guide.md +0 -238
- pynibs/data/network mapping configuration/configuration_TEMPLATE.yaml +0 -42
- pynibs/data/network mapping configuration/configuration_for_testing.yaml +0 -43
- pynibs/data/network mapping configuration/configuration_modelTMS.yaml +0 -43
- pynibs/data/network mapping configuration/configuration_reg_isi_05.yaml +0 -43
- pynibs/data/network mapping configuration/output_documentation.md +0 -185
- pynibs/data/network mapping configuration/recommendations_for_accuracy_threshold.md +0 -77
- pynibs/data/neuron/models/L23_PC_cADpyr_biphasic_v1.csv +0 -1281
- pynibs/data/neuron/models/L23_PC_cADpyr_monophasic_v1.csv +0 -1281
- pynibs/data/neuron/models/L4_LBC_biphasic_v1.csv +0 -1281
- pynibs/data/neuron/models/L4_LBC_monophasic_v1.csv +0 -1281
- pynibs/data/neuron/models/L4_NBC_biphasic_v1.csv +0 -1281
- pynibs/data/neuron/models/L4_NBC_monophasic_v1.csv +0 -1281
- pynibs/data/neuron/models/L4_SBC_biphasic_v1.csv +0 -1281
- pynibs/data/neuron/models/L4_SBC_monophasic_v1.csv +0 -1281
- pynibs/data/neuron/models/L5_TTPC2_cADpyr_biphasic_v1.csv +0 -1281
- pynibs/data/neuron/models/L5_TTPC2_cADpyr_monophasic_v1.csv +0 -1281
- pynibs/tests/data/InstrumentMarker20200225163611937.xml +0 -19
- pynibs/tests/data/TriggerMarkers_Coil0_20200225163443682.xml +0 -14
- pynibs/tests/data/TriggerMarkers_Coil1_20200225170337572.xml +0 -6373
- pynibs/tests/data/Xdmf.dtd +0 -89
- pynibs/tests/data/brainsight_niiImage_nifticoord.txt +0 -145
- pynibs/tests/data/brainsight_niiImage_nifticoord_largefile.txt +0 -1434
- pynibs/tests/data/brainsight_niiImage_niifticoord_mixedtargets.txt +0 -47
- pynibs/tests/data/create_subject_testsub.py +0 -332
- pynibs/tests/data/data.hdf5 +0 -0
- pynibs/tests/data/geo.hdf5 +0 -0
- pynibs/tests/test_coil.py +0 -474
- pynibs/tests/test_elements2nodes.py +0 -100
- pynibs/tests/test_hdf5_io/test_xdmf.py +0 -61
- pynibs/tests/test_mesh_transformations.py +0 -123
- pynibs/tests/test_mesh_utils.py +0 -143
- pynibs/tests/test_nnav_imports.py +0 -101
- pynibs/tests/test_quality_measures.py +0 -117
- pynibs/tests/test_regressdata.py +0 -289
- pynibs/tests/test_roi.py +0 -17
- pynibs/tests/test_rotations.py +0 -86
- pynibs/tests/test_subject.py +0 -71
- pynibs/tests/test_util.py +0 -24
- /pynibs/{regression/score_types.py → neuron/models/m1_montbrio.py} +0 -0
- {pyNIBS-0.2024.8.dist-info → pynibs-0.2026.1.dist-info/licenses}/LICENSE +0 -0
- {pyNIBS-0.2024.8.dist-info → pynibs-0.2026.1.dist-info}/top_level.txt +0 -0
pynibs/mesh/transformations.py
CHANGED
|
@@ -2,9 +2,11 @@ import os
|
|
|
2
2
|
import meshio
|
|
3
3
|
import trimesh
|
|
4
4
|
import warnings
|
|
5
|
-
import
|
|
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
|
|
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,
|
|
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 =
|
|
506
|
-
p_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 =
|
|
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 =
|
|
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)
|