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/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
|
-
|
|
125
|
-
|
|
126
|
-
:
|
|
127
|
-
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
351
|
+
data = ElementData(e, name='E', mesh=mesh)
|
|
349
352
|
|
|
350
353
|
interp_res_per_surface = dict()
|
|
351
|
-
mesh.elmdata.append(
|
|
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
|
-
|
|
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-
|
|
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
|
-
|
|
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):
|
pynibs/util/{util.py → utils.py}
RENAMED
|
@@ -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
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
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),
|
|
639
|
+
muaps = np.zeros((len(t_muap), n_mu))
|
|
642
640
|
|
|
643
|
-
for i in range(
|
|
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
|
|
pynibs/visualization/para.py
CHANGED
|
@@ -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 = '
|
|
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 = '
|
|
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 = '
|
|
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 = '
|
|
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
|
+
[](https://gitlab.gwdg.de/tms-localization/pynibs)
|
|
55
|
+
[](https://pynibs.readthedocs.io/)
|
|
56
|
+
[](https://gitlab.gwdg.de/tms-localization/pynibs/commits/master)
|
|
57
|
+
[](https://tms-localization.pages.gwdg.de/pynibs)
|
|
58
|
+
|
|
59
|
+

|
|
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)
|