pyNIBS 0.2024.8__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 (107) hide show
  1. pyNIBS-0.2024.8.dist-info/LICENSE +623 -0
  2. pyNIBS-0.2024.8.dist-info/METADATA +723 -0
  3. pyNIBS-0.2024.8.dist-info/RECORD +107 -0
  4. pyNIBS-0.2024.8.dist-info/WHEEL +5 -0
  5. pyNIBS-0.2024.8.dist-info/top_level.txt +1 -0
  6. pynibs/__init__.py +34 -0
  7. pynibs/coil.py +1367 -0
  8. pynibs/congruence/__init__.py +15 -0
  9. pynibs/congruence/congruence.py +1108 -0
  10. pynibs/congruence/ext_metrics.py +257 -0
  11. pynibs/congruence/stimulation_threshold.py +318 -0
  12. pynibs/data/configuration_exp0.yaml +59 -0
  13. pynibs/data/configuration_linear_MEP.yaml +61 -0
  14. pynibs/data/configuration_linear_RT.yaml +61 -0
  15. pynibs/data/configuration_sigmoid4.yaml +68 -0
  16. pynibs/data/network mapping configuration/configuration guide.md +238 -0
  17. pynibs/data/network mapping configuration/configuration_TEMPLATE.yaml +42 -0
  18. pynibs/data/network mapping configuration/configuration_for_testing.yaml +43 -0
  19. pynibs/data/network mapping configuration/configuration_modelTMS.yaml +43 -0
  20. pynibs/data/network mapping configuration/configuration_reg_isi_05.yaml +43 -0
  21. pynibs/data/network mapping configuration/output_documentation.md +185 -0
  22. pynibs/data/network mapping configuration/recommendations_for_accuracy_threshold.md +77 -0
  23. pynibs/data/neuron/models/L23_PC_cADpyr_biphasic_v1.csv +1281 -0
  24. pynibs/data/neuron/models/L23_PC_cADpyr_monophasic_v1.csv +1281 -0
  25. pynibs/data/neuron/models/L4_LBC_biphasic_v1.csv +1281 -0
  26. pynibs/data/neuron/models/L4_LBC_monophasic_v1.csv +1281 -0
  27. pynibs/data/neuron/models/L4_NBC_biphasic_v1.csv +1281 -0
  28. pynibs/data/neuron/models/L4_NBC_monophasic_v1.csv +1281 -0
  29. pynibs/data/neuron/models/L4_SBC_biphasic_v1.csv +1281 -0
  30. pynibs/data/neuron/models/L4_SBC_monophasic_v1.csv +1281 -0
  31. pynibs/data/neuron/models/L5_TTPC2_cADpyr_biphasic_v1.csv +1281 -0
  32. pynibs/data/neuron/models/L5_TTPC2_cADpyr_monophasic_v1.csv +1281 -0
  33. pynibs/expio/Mep.py +1518 -0
  34. pynibs/expio/__init__.py +8 -0
  35. pynibs/expio/brainsight.py +979 -0
  36. pynibs/expio/brainvis.py +71 -0
  37. pynibs/expio/cobot.py +239 -0
  38. pynibs/expio/exp.py +1876 -0
  39. pynibs/expio/fit_funs.py +287 -0
  40. pynibs/expio/localite.py +1987 -0
  41. pynibs/expio/signal_ced.py +51 -0
  42. pynibs/expio/visor.py +624 -0
  43. pynibs/freesurfer.py +502 -0
  44. pynibs/hdf5_io/__init__.py +10 -0
  45. pynibs/hdf5_io/hdf5_io.py +1857 -0
  46. pynibs/hdf5_io/xdmf.py +1542 -0
  47. pynibs/mesh/__init__.py +3 -0
  48. pynibs/mesh/mesh_struct.py +1394 -0
  49. pynibs/mesh/transformations.py +866 -0
  50. pynibs/mesh/utils.py +1103 -0
  51. pynibs/models/_TMS.py +211 -0
  52. pynibs/models/__init__.py +0 -0
  53. pynibs/muap.py +392 -0
  54. pynibs/neuron/__init__.py +2 -0
  55. pynibs/neuron/neuron_regression.py +284 -0
  56. pynibs/neuron/util.py +58 -0
  57. pynibs/optimization/__init__.py +5 -0
  58. pynibs/optimization/multichannel.py +278 -0
  59. pynibs/optimization/opt_mep.py +152 -0
  60. pynibs/optimization/optimization.py +1445 -0
  61. pynibs/optimization/workhorses.py +698 -0
  62. pynibs/pckg/__init__.py +0 -0
  63. pynibs/pckg/biosig/biosig4c++-1.9.5.src_fixed.tar.gz +0 -0
  64. pynibs/pckg/libeep/__init__.py +0 -0
  65. pynibs/pckg/libeep/pyeep.so +0 -0
  66. pynibs/regression/__init__.py +11 -0
  67. pynibs/regression/dual_node_detection.py +2375 -0
  68. pynibs/regression/regression.py +2984 -0
  69. pynibs/regression/score_types.py +0 -0
  70. pynibs/roi/__init__.py +2 -0
  71. pynibs/roi/roi.py +895 -0
  72. pynibs/roi/roi_structs.py +1233 -0
  73. pynibs/subject.py +1009 -0
  74. pynibs/tensor_scaling.py +144 -0
  75. pynibs/tests/data/InstrumentMarker20200225163611937.xml +19 -0
  76. pynibs/tests/data/TriggerMarkers_Coil0_20200225163443682.xml +14 -0
  77. pynibs/tests/data/TriggerMarkers_Coil1_20200225170337572.xml +6373 -0
  78. pynibs/tests/data/Xdmf.dtd +89 -0
  79. pynibs/tests/data/brainsight_niiImage_nifticoord.txt +145 -0
  80. pynibs/tests/data/brainsight_niiImage_nifticoord_largefile.txt +1434 -0
  81. pynibs/tests/data/brainsight_niiImage_niifticoord_mixedtargets.txt +47 -0
  82. pynibs/tests/data/create_subject_testsub.py +332 -0
  83. pynibs/tests/data/data.hdf5 +0 -0
  84. pynibs/tests/data/geo.hdf5 +0 -0
  85. pynibs/tests/test_coil.py +474 -0
  86. pynibs/tests/test_elements2nodes.py +100 -0
  87. pynibs/tests/test_hdf5_io/test_xdmf.py +61 -0
  88. pynibs/tests/test_mesh_transformations.py +123 -0
  89. pynibs/tests/test_mesh_utils.py +143 -0
  90. pynibs/tests/test_nnav_imports.py +101 -0
  91. pynibs/tests/test_quality_measures.py +117 -0
  92. pynibs/tests/test_regressdata.py +289 -0
  93. pynibs/tests/test_roi.py +17 -0
  94. pynibs/tests/test_rotations.py +86 -0
  95. pynibs/tests/test_subject.py +71 -0
  96. pynibs/tests/test_util.py +24 -0
  97. pynibs/tms_pulse.py +34 -0
  98. pynibs/util/__init__.py +4 -0
  99. pynibs/util/dosing.py +233 -0
  100. pynibs/util/quality_measures.py +562 -0
  101. pynibs/util/rotations.py +340 -0
  102. pynibs/util/simnibs.py +763 -0
  103. pynibs/util/util.py +727 -0
  104. pynibs/visualization/__init__.py +2 -0
  105. pynibs/visualization/para.py +4372 -0
  106. pynibs/visualization/plot_2D.py +137 -0
  107. pynibs/visualization/render_3D.py +347 -0
@@ -0,0 +1,340 @@
1
+ """Some helper functions to take care of geometric rotations"""
2
+ import math
3
+ import numpy as np
4
+ from scipy.spatial.transform import Rotation
5
+
6
+
7
+ def normalize_rot(rot):
8
+ """
9
+ Normalize rotation matrix.
10
+
11
+ Parameters
12
+ ----------
13
+ rot : np.ndarray of float
14
+ (3, 3) Rotation matrix.
15
+
16
+ Returns
17
+ -------
18
+ rot_norm : np.ndarray of float
19
+ (3, 3) Normalized rotation matrix.
20
+ """
21
+ q = rot_to_quat(rot)
22
+ q /= np.sqrt(np.sum(q ** 2))
23
+ return quat_to_rot(q)
24
+
25
+
26
+ def quat_rotation_angle(q):
27
+ """
28
+ Computes the rotation angle from the quaternion in rad.
29
+
30
+ Parameters
31
+ ----------
32
+ q : np.ndarray of float
33
+ Quaternion, either only the imaginary part (length=3) [qx, qy, qz]
34
+ or the full quaternion (length=4) [qw, qx, qy, qz].
35
+
36
+ Returns
37
+ -------
38
+ alpha : float
39
+ Rotation angle of quaternion in rad.
40
+ """
41
+
42
+ if len(q) == 3:
43
+ return 2 * np.arcsin(np.linalg.norm(q))
44
+ elif len(q) == 4:
45
+ return q[0]
46
+ else:
47
+ raise ValueError('Please check size of quaternion')
48
+
49
+
50
+ def quat_to_rot(q):
51
+ """
52
+ Computes the rotation matrix from quaternions.
53
+
54
+ Parameters
55
+ ----------
56
+ q : np.ndarray of float
57
+ Quaternion, either only the imaginary part (length=3) or the full quaternion (length=4).
58
+
59
+ Returns
60
+ -------
61
+ rot : np.ndarray of float
62
+ (3, 3) Rotation matrix, containing the x, y, z axis in the columns.
63
+ """
64
+ if q.size == 3:
65
+ q = np.hstack([np.sqrt(1 - np.sum(q ** 2)), q])
66
+ rot = np.array([[q[0] ** 2 + q[1] ** 2 - q[2] ** 2 - q[3] ** 2, 2 * (q[1] * q[2] - q[0] * q[3]),
67
+ 2 * (q[1] * q[3] + q[0] * q[2])],
68
+ [2 * (q[2] * q[1] + q[0] * q[3]), q[0] ** 2 - q[1] ** 2 + q[2] ** 2 - q[3] ** 2,
69
+ 2 * (q[2] * q[3] - q[0] * q[1])],
70
+ [2 * (q[3] * q[1] - q[0] * q[2]), 2 * (q[3] * q[2] + q[0] * q[1]),
71
+ q[0] ** 2 - q[1] ** 2 - q[2] ** 2 + q[3] ** 2]])
72
+ return rot
73
+
74
+
75
+ def rot_to_quat(rot):
76
+ """
77
+ Computes the quaternions from rotation matrix
78
+ (see e.g. https://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/).
79
+
80
+ Parameters
81
+ ----------
82
+ rot : np.ndarray of float
83
+ (3, 3) Rotation matrix, containing the x, y, z axis in the columns.
84
+
85
+ Returns
86
+ -------
87
+ q : np.ndarray of float
88
+ Quaternion, full (length=4).
89
+ """
90
+
91
+ rot = rot.flatten()
92
+ t = 1. + rot[0] + rot[4] + rot[8]
93
+ if t > np.finfo(rot.dtype).eps:
94
+ s = np.sqrt(t) * 2.
95
+ qx = (rot[7] - rot[5]) / s
96
+ qy = (rot[2] - rot[6]) / s
97
+ qz = (rot[3] - rot[1]) / s
98
+ qw = 0.25 * s
99
+ elif rot[0] > rot[4] and rot[0] > rot[8]:
100
+ s = np.sqrt(1. + rot[0] - rot[4] - rot[8]) * 2.
101
+ qx = 0.25 * s
102
+ qy = (rot[1] + rot[3]) / s
103
+ qz = (rot[2] + rot[6]) / s
104
+ qw = (rot[7] - rot[5]) / s
105
+ elif rot[4] > rot[8]:
106
+ s = np.sqrt(1. - rot[0] + rot[4] - rot[8]) * 2
107
+ qx = (rot[1] + rot[3]) / s
108
+ qy = 0.25 * s
109
+ qz = (rot[5] + rot[7]) / s
110
+ qw = (rot[2] - rot[6]) / s
111
+ else:
112
+ s = np.sqrt(1. - rot[0] - rot[4] + rot[8]) * 2.
113
+ qx = (rot[2] + rot[6]) / s
114
+ qy = (rot[5] + rot[7]) / s
115
+ qz = 0.25 * s
116
+ qw = (rot[3] - rot[1]) / s
117
+ return np.array((qw, qx, qy, qz))
118
+
119
+
120
+ def quaternion_conjugate(q):
121
+ """
122
+ https://stackoverflow.com/questions/15425313/inverse-quaternion
123
+
124
+ :param q:
125
+ :type q:
126
+ :return:
127
+ :rtype:
128
+ """
129
+ return np.array((-q[0], -q[1], -q[2]))
130
+
131
+
132
+ def quaternion_inverse(q):
133
+ """
134
+ Compute the inverse of a quaternion.
135
+
136
+ The inverse of a quaternion is computed by taking the conjugate of the quaternion and dividing it by the norm of
137
+ the quaternion.
138
+ https://stackoverflow.com/questions/15425313/inverse-quaternion
139
+
140
+ Parameters
141
+ ----------
142
+ q : np.ndarray
143
+ Input quaternion.
144
+
145
+ Returns
146
+ -------
147
+ np.ndarray
148
+ Inverse of the input quaternion.
149
+ """
150
+ return quaternion_conjugate(q) / np.linalg.norm(q)
151
+
152
+
153
+ def quaternion_diff(q1, q2):
154
+ """
155
+ https://math.stackexchange.com/questions/2581668/
156
+ error-measure-between-two-rotations-when-one-matrix-might-not-be-a-valid-rotatio
157
+
158
+ Parameters
159
+ ----------
160
+ q1 : np.ndarray
161
+ Quaternion 1.
162
+ q2 : np.ndarray
163
+ Quaternion 2.
164
+
165
+ Returns
166
+ -------
167
+ float
168
+ Difference between the two quaternions.
169
+ """
170
+ return np.linalg.norm(q1 * quaternion_inverse(q2) - 1)
171
+
172
+
173
+ def euler_angles_to_rotation_matrix(theta):
174
+ """
175
+ Determines the rotation matrix from the three Euler angles theta = [Psi, Theta, Phi] (in rad), which rotate the
176
+ coordinate system in the order z, y', x''.
177
+
178
+ Parameters
179
+ ----------
180
+ theta : np.ndarray
181
+ (3) Euler angles in rad.
182
+
183
+ Returns
184
+ -------
185
+ r : np.ndarray
186
+ (3) Rotation matrix (z, y', x'').
187
+ """
188
+
189
+ # theta in rad
190
+ r_x = np.array([[1., 0., 0.],
191
+ [0., math.cos(theta[0]), -math.sin(theta[0])],
192
+ [0., math.sin(theta[0]), math.cos(theta[0])]
193
+ ])
194
+
195
+ r_y = np.array([[math.cos(theta[1]), 0, math.sin(theta[1])],
196
+ [0., 1., 0.],
197
+ [-math.sin(theta[1]), 0, math.cos(theta[1])]
198
+ ])
199
+
200
+ r_z = np.array([[math.cos(theta[2]), -math.sin(theta[2]), 0],
201
+ [math.sin(theta[2]), math.cos(theta[2]), 0],
202
+ [0., 0., 1.]
203
+ ])
204
+
205
+ r = np.dot(r_z, np.dot(r_y, r_x))
206
+
207
+ return r
208
+
209
+
210
+ def rotation_matrix_to_euler_angles(r):
211
+ """
212
+ Calculates the euler angles theta = [Psi, Theta, Phi] (in rad) from the rotation matrix R which, rotate the
213
+ coordinate system in the order z, y', x'' (https://www.learnopencv.com/rotation-matrix-to-euler-angles/).
214
+
215
+ Parameters
216
+ ----------
217
+ r : np.ndarray
218
+ (3, 3) Rotation matrix (z, y', x'').
219
+
220
+ Returns
221
+ -------
222
+ theta : np.ndarray
223
+ (3) Euler angles in rad.
224
+ """
225
+ sy = math.sqrt(r[0, 0] * r[0, 0] + r[1, 0] * r[1, 0])
226
+
227
+ singular = sy < 1e-6
228
+
229
+ if not singular:
230
+ x = math.atan2(r[2, 1], r[2, 2])
231
+ y = math.atan2(-r[2, 0], sy)
232
+ z = math.atan2(r[1, 0], r[0, 0])
233
+ else:
234
+ x = math.atan2(-r[1, 2], r[1, 1])
235
+ y = math.atan2(-r[2, 0], sy)
236
+ z = 0
237
+
238
+ return np.array([x, y, z])
239
+
240
+
241
+ def bases2rotmat(v1, v2):
242
+ """
243
+ Computes rotation matrix to rotate basis 1 (``v1``) to another basis (``v2``).
244
+
245
+ Parameters
246
+ ----------
247
+ v1 : np.ndarray
248
+ (3, 3) original basis.
249
+ v2 : np.ndarray
250
+ (3, 3) rotated basis.
251
+
252
+ Returns
253
+ -------
254
+ rot_mat : np.ndarray
255
+ (3, 3) rotation matrix to go from v1 to v2.
256
+ """
257
+ return np.linalg.solve(v1, v2).T
258
+
259
+
260
+ def rotate_matsimnibs_euler(axis, angle, matsimnibs, metric='rad'):
261
+ """
262
+ Rotates a matsimnibs matrix around ``axis`` by ``angle``.
263
+
264
+ Parameters
265
+ ----------
266
+ axis : str
267
+ One of ('x','y','z').
268
+ angle : float
269
+ Angle to rotate system around ``axis``.
270
+ matsimnibs : np.ndarray
271
+ (4, 4) SimNIBS matsimnibs coil orientation and position matrix.
272
+ metric : str, default: 'rad'
273
+ One of ('rad', 'deg'). If ``deg``, ``angle`` is transformed to radians.
274
+
275
+ Returns
276
+ -------
277
+ rotated_matsimnibs : np.ndarray
278
+ (4, 4) Rotated system.
279
+ """
280
+
281
+ if metric.lower().startswith('deg'):
282
+ angle = np.deg2rad(angle)
283
+ elif not metric.lower().startswith('rad'):
284
+ raise Exception(ValueError)
285
+
286
+ if axis == 'x':
287
+ # rotate around x
288
+ rotated_system = np.array((
289
+ (1, 0, 0),
290
+ (0, np.cos(angle), -np.sin(angle)),
291
+ (0, np.sin(angle), np.cos(angle)),
292
+ ))
293
+
294
+ elif axis == 'y':
295
+ # rotate around y
296
+ rotated_system = np.array((
297
+ (np.cos(angle), 0, np.sin(angle)),
298
+ (0, 1, 0),
299
+ (-np.sin(angle), 0, np.cos(angle)),
300
+ ))
301
+ elif axis == 'z':
302
+ # rotate aroun z
303
+ rotated_system = np.array((
304
+ (np.cos(angle), -np.sin(angle), 0),
305
+ (np.sin(angle), np.cos(angle), 0),
306
+ (0, 0, 1),
307
+ ))
308
+ else:
309
+ raise Exception(ValueError)
310
+
311
+ rot_vecs = matsimnibs[0:3, :3].dot(rotated_system)
312
+
313
+ return np.vstack((np.hstack((rot_vecs,
314
+ matsimnibs[:3, 3, None])),
315
+ [0, 0, 0, 1]))
316
+
317
+
318
+ def rotmat_from_vecs(vec1, vec2):
319
+ """
320
+ Find the rotation matrix that aligns vec1 to vec2
321
+
322
+ Parameters
323
+ ----------
324
+ vec 1: np.ndarray
325
+ A 3D vector ('source').
326
+ vec 2: np.ndarray
327
+ A 3D vector ('destination').
328
+
329
+ Returns
330
+ -------
331
+ rot_mat: scipy.Rotation
332
+ A transform matrix (3x3) which when applied to vec1, aligns it with vec2.
333
+ """
334
+ a, b = (vec1 / np.linalg.norm(vec1)).reshape(3), (vec2 / np.linalg.norm(vec2)).reshape(3)
335
+ v = np.cross(a, b)
336
+ c = np.dot(a, b)
337
+ s = np.linalg.norm(v)
338
+ kmat = np.array([[0, -v[2], v[1]], [v[2], 0, -v[0]], [-v[1], v[0], 0]])
339
+ rotation_matrix = np.eye(3) + kmat + kmat.dot(kmat) * ((1 - c) / (s ** 2))
340
+ return Rotation.from_matrix(rotation_matrix)