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.
- pyNIBS-0.2024.8.dist-info/LICENSE +623 -0
- pyNIBS-0.2024.8.dist-info/METADATA +723 -0
- pyNIBS-0.2024.8.dist-info/RECORD +107 -0
- pyNIBS-0.2024.8.dist-info/WHEEL +5 -0
- pyNIBS-0.2024.8.dist-info/top_level.txt +1 -0
- pynibs/__init__.py +34 -0
- pynibs/coil.py +1367 -0
- pynibs/congruence/__init__.py +15 -0
- pynibs/congruence/congruence.py +1108 -0
- pynibs/congruence/ext_metrics.py +257 -0
- pynibs/congruence/stimulation_threshold.py +318 -0
- pynibs/data/configuration_exp0.yaml +59 -0
- pynibs/data/configuration_linear_MEP.yaml +61 -0
- pynibs/data/configuration_linear_RT.yaml +61 -0
- pynibs/data/configuration_sigmoid4.yaml +68 -0
- pynibs/data/network mapping configuration/configuration guide.md +238 -0
- pynibs/data/network mapping configuration/configuration_TEMPLATE.yaml +42 -0
- pynibs/data/network mapping configuration/configuration_for_testing.yaml +43 -0
- pynibs/data/network mapping configuration/configuration_modelTMS.yaml +43 -0
- pynibs/data/network mapping configuration/configuration_reg_isi_05.yaml +43 -0
- pynibs/data/network mapping configuration/output_documentation.md +185 -0
- pynibs/data/network mapping configuration/recommendations_for_accuracy_threshold.md +77 -0
- pynibs/data/neuron/models/L23_PC_cADpyr_biphasic_v1.csv +1281 -0
- pynibs/data/neuron/models/L23_PC_cADpyr_monophasic_v1.csv +1281 -0
- pynibs/data/neuron/models/L4_LBC_biphasic_v1.csv +1281 -0
- pynibs/data/neuron/models/L4_LBC_monophasic_v1.csv +1281 -0
- pynibs/data/neuron/models/L4_NBC_biphasic_v1.csv +1281 -0
- pynibs/data/neuron/models/L4_NBC_monophasic_v1.csv +1281 -0
- pynibs/data/neuron/models/L4_SBC_biphasic_v1.csv +1281 -0
- pynibs/data/neuron/models/L4_SBC_monophasic_v1.csv +1281 -0
- pynibs/data/neuron/models/L5_TTPC2_cADpyr_biphasic_v1.csv +1281 -0
- pynibs/data/neuron/models/L5_TTPC2_cADpyr_monophasic_v1.csv +1281 -0
- pynibs/expio/Mep.py +1518 -0
- pynibs/expio/__init__.py +8 -0
- pynibs/expio/brainsight.py +979 -0
- pynibs/expio/brainvis.py +71 -0
- pynibs/expio/cobot.py +239 -0
- pynibs/expio/exp.py +1876 -0
- pynibs/expio/fit_funs.py +287 -0
- pynibs/expio/localite.py +1987 -0
- pynibs/expio/signal_ced.py +51 -0
- pynibs/expio/visor.py +624 -0
- pynibs/freesurfer.py +502 -0
- pynibs/hdf5_io/__init__.py +10 -0
- pynibs/hdf5_io/hdf5_io.py +1857 -0
- pynibs/hdf5_io/xdmf.py +1542 -0
- pynibs/mesh/__init__.py +3 -0
- pynibs/mesh/mesh_struct.py +1394 -0
- pynibs/mesh/transformations.py +866 -0
- pynibs/mesh/utils.py +1103 -0
- pynibs/models/_TMS.py +211 -0
- pynibs/models/__init__.py +0 -0
- pynibs/muap.py +392 -0
- pynibs/neuron/__init__.py +2 -0
- pynibs/neuron/neuron_regression.py +284 -0
- pynibs/neuron/util.py +58 -0
- pynibs/optimization/__init__.py +5 -0
- pynibs/optimization/multichannel.py +278 -0
- pynibs/optimization/opt_mep.py +152 -0
- pynibs/optimization/optimization.py +1445 -0
- pynibs/optimization/workhorses.py +698 -0
- pynibs/pckg/__init__.py +0 -0
- pynibs/pckg/biosig/biosig4c++-1.9.5.src_fixed.tar.gz +0 -0
- pynibs/pckg/libeep/__init__.py +0 -0
- pynibs/pckg/libeep/pyeep.so +0 -0
- pynibs/regression/__init__.py +11 -0
- pynibs/regression/dual_node_detection.py +2375 -0
- pynibs/regression/regression.py +2984 -0
- pynibs/regression/score_types.py +0 -0
- pynibs/roi/__init__.py +2 -0
- pynibs/roi/roi.py +895 -0
- pynibs/roi/roi_structs.py +1233 -0
- pynibs/subject.py +1009 -0
- pynibs/tensor_scaling.py +144 -0
- pynibs/tests/data/InstrumentMarker20200225163611937.xml +19 -0
- pynibs/tests/data/TriggerMarkers_Coil0_20200225163443682.xml +14 -0
- pynibs/tests/data/TriggerMarkers_Coil1_20200225170337572.xml +6373 -0
- pynibs/tests/data/Xdmf.dtd +89 -0
- pynibs/tests/data/brainsight_niiImage_nifticoord.txt +145 -0
- pynibs/tests/data/brainsight_niiImage_nifticoord_largefile.txt +1434 -0
- pynibs/tests/data/brainsight_niiImage_niifticoord_mixedtargets.txt +47 -0
- pynibs/tests/data/create_subject_testsub.py +332 -0
- pynibs/tests/data/data.hdf5 +0 -0
- pynibs/tests/data/geo.hdf5 +0 -0
- pynibs/tests/test_coil.py +474 -0
- pynibs/tests/test_elements2nodes.py +100 -0
- pynibs/tests/test_hdf5_io/test_xdmf.py +61 -0
- pynibs/tests/test_mesh_transformations.py +123 -0
- pynibs/tests/test_mesh_utils.py +143 -0
- pynibs/tests/test_nnav_imports.py +101 -0
- pynibs/tests/test_quality_measures.py +117 -0
- pynibs/tests/test_regressdata.py +289 -0
- pynibs/tests/test_roi.py +17 -0
- pynibs/tests/test_rotations.py +86 -0
- pynibs/tests/test_subject.py +71 -0
- pynibs/tests/test_util.py +24 -0
- pynibs/tms_pulse.py +34 -0
- pynibs/util/__init__.py +4 -0
- pynibs/util/dosing.py +233 -0
- pynibs/util/quality_measures.py +562 -0
- pynibs/util/rotations.py +340 -0
- pynibs/util/simnibs.py +763 -0
- pynibs/util/util.py +727 -0
- pynibs/visualization/__init__.py +2 -0
- pynibs/visualization/para.py +4372 -0
- pynibs/visualization/plot_2D.py +137 -0
- pynibs/visualization/render_3D.py +347 -0
pynibs/util/rotations.py
ADDED
|
@@ -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)
|