-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathquaternion.py
More file actions
169 lines (133 loc) · 5.91 KB
/
quaternion.py
File metadata and controls
169 lines (133 loc) · 5.91 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
import numpy as np
from mytypes import ArrayLike
#import utils
def cross_product_matrix(n: ArrayLike, debug: bool = True) -> np.ndarray:
assert len(n) == 3, f"utils.cross_product_matrix: Vector not of length 3: {n}"
vector = np.array(n, dtype=float).reshape(3)
#S = np.zeros((3, 3)) # TODO: Create the cross product matrix
n=vector
S=np.array([[0,-vector[2],vector[1]],
[vector[2],0,-vector[0]],
[-vector[1],vector[0],0]])
if debug:
assert S.shape == (
3,
3,
), f"utils.cross_product_matrix: Result is not a 3x3 matrix: {S}, \n{S.shape}"
assert np.allclose(
S.T, -S
), f"utils.cross_product_matrix: Result is not skew-symmetric: {S}"
return S
def quaternion_product(ql: np.ndarray, qr: np.ndarray) -> np.ndarray:
"""Perform quaternion product according to either (10.21) or (10.34).
Args:
ql (np.ndarray): Left quaternion of the product of either shape (3,) (pure quaternion) or (4,)
qr (np.ndarray): Right quaternion of the product of either shape (3,) (pure quaternion) or (4,)
Raises:
RuntimeError: Left or right quaternion are of the wrong shape
AssertionError: Resulting quaternion is of wrong shape
Returns:
np.ndarray: Quaternion product of ql and qr of shape (4,)s
"""
if ql.shape == (4,):
eta_left = ql[0]
epsilon_left = ql[1:].reshape((3, 1))
elif ql.shape == (3,):
eta_left = 0
epsilon_left = ql.reshape((3, 1))
else:
raise RuntimeError(
f"utils.quaternion_product: Quaternion multiplication error, left quaternion shape incorrect: {ql.shape}"
)
if qr.shape == (4,):
q_right = qr.copy()
elif qr.shape == (3,):
q_right = np.concatenate(([0], qr))
else:
raise RuntimeError(
f"utils.quaternion_product: Quaternion multiplication error, right quaternion wrong shape: {qr.shape}"
)
quaternion=(eta_left*np.eye(4)+np.block([[0,-epsilon_left.T],[epsilon_left,cross_product_matrix(epsilon_left)]]))@q_right
# Ensure result is of correct shape
quaternion = quaternion.ravel()
assert quaternion.shape == (
4,
), f"utils.quaternion_product: Quaternion multiplication error, result quaternion wrong shape: {quaternion.shape}"
return quaternion
def quaternion_to_rotation_matrix(
quaternion: np.ndarray, debug: bool = True
) -> np.ndarray:
"""Convert a quaternion to a rotation matrix
Args:
quaternion (np.ndarray): Quaternion of either shape (3,) (pure quaternion) or (4,)
debug (bool, optional): Debug flag, could speed up by setting to False. Defaults to True.
Raises:
RuntimeError: Quaternion is of the wrong shape
AssertionError: Debug assert fails, rotation matrix is not element of SO(3)
Returns:
np.ndarray: Rotation matrix of shape (3, 3)
"""
if quaternion.shape == (4,):
eta = quaternion[0]
epsilon = quaternion[1:]
elif quaternion.shape == (3,):
eta = 0
epsilon = quaternion.copy()
else:
raise RuntimeError(
f"quaternion.quaternion_to_rotation_matrix: Quaternion to multiplication error, quaternion shape incorrect: {quaternion.shape}"
)
cross_eps=cross_product_matrix(epsilon)
R = np.eye(3)+2*eta*cross_eps+2*cross_eps@cross_eps # TODO: Convert from quaternion to rotation matrix
if debug:
assert np.allclose(
np.linalg.det(R), 1
), f"quaternion.quaternion_to_rotation_matrix: Determinant of rotation matrix not close to 1"
assert np.allclose(
R.T, np.linalg.inv(R)
), f"quaternion.quaternion_to_rotation_matrix: Transpose of rotation matrix not close to inverse"
return R
def quaternion_to_euler(quaternion: np.ndarray) -> np.ndarray:
"""Convert quaternion into euler angles
Args:
quaternion (np.ndarray): Quaternion of shape (4,)
Returns:
np.ndarray: Euler angles of shape (3,)
"""
assert quaternion.shape == (
4,
), f"quaternion.quaternion_to_euler: Quaternion shape incorrect {quaternion.shape}"
quaternion_squared = quaternion ** 2
phi = np.arctan2(2*quaternion[3]*quaternion[2]+2*quaternion[0]*quaternion[1],quaternion_squared[0] - quaternion_squared[1] - quaternion_squared[2] + quaternion_squared[3]) # TODO: Convert from quaternion to euler angles
theta = np.arcsin(2*(quaternion[0]*quaternion[2]-2*quaternion[1]*quaternion[3])) # TODO: Convert from quaternion to euler angles
psi = np.arctan2(2*quaternion[1]*quaternion[2]+2*quaternion[0]*quaternion[3],quaternion_squared[0] + quaternion_squared[1] - quaternion_squared[2] - quaternion_squared[3])
euler_angles = np.array([phi, theta, psi])
assert euler_angles.shape == (
3,
), f"quaternion.quaternion_to_euler: Euler angles shape incorrect: {euler_angles.shape}"
return euler_angles
def euler_to_quaternion(euler_angles: np.ndarray) -> np.ndarray:
"""Convert euler angles into quaternion
Args:
euler_angles (np.ndarray): Euler angles of shape (3,)
Returns:
np.ndarray: Quaternion of shape (4,)
"""
assert euler_angles.shape == (
3,
), f"quaternion.euler_to_quaternion: euler_angles shape wrong {euler_angles.shape}"
half_angles = 0.5 * euler_angles
c_phi2, c_theta2, c_psi2 = np.cos(half_angles)
s_phi2, s_theta2, s_psi2 = np.sin(half_angles)
quaternion = np.array(
[
c_phi2 * c_theta2 * c_psi2 + s_phi2 * s_theta2 * s_psi2,
s_phi2 * c_theta2 * c_psi2 - c_phi2 * s_theta2 * s_psi2,
c_phi2 * s_theta2 * c_psi2 + s_phi2 * c_theta2 * s_psi2,
c_phi2 * c_theta2 * s_psi2 - s_phi2 * s_theta2 * c_psi2,
]
)
assert quaternion.shape == (
4,
), f"quaternion.euler_to_quaternion: Quaternion shape incorrect {quaternion.shape}"
return quaternion