-
Notifications
You must be signed in to change notification settings - Fork 9
/
rotations.py
126 lines (108 loc) · 4.77 KB
/
rotations.py
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
# Utitlity file with functions for handling rotations
#
# Author: Trevor Ablett
# University of Toronto Institute for Aerospace Studies
import numpy as np
def skew_symmetric(v):
""" Skew symmetric operator for a 3x1 vector. """
return np.array(
[[0, -v[2], v[1]],
[v[2], 0, -v[0]],
[-v[1], v[0], 0]]
)
class Quaternion():
def __init__(self, w=1., x=0., y=0., z=0., axis_angle=None, euler=None):
"""
Allow initialization either with explicit wxyz, axis angle, or euler XYZ (RPY) angles.
:param w: w (real) of a quaternion.
:param x: x (i) of a quaternion.
:param y: y (j) of a quaternion.
:param z: z (k) of a quaternion.
:param axis_angle: Set of three values from axis-angle representation, as list or [3,] or [3,1] np.ndarray.
See C2M5L2 Slide 7 for details.
:param euler: Set of three angles from euler XYZ rotating frame angles.
"""
if axis_angle is None and euler is None:
self.w = w
self.x = x
self.y = y
self.z = z
elif euler is not None and axis_angle is not None:
raise AttributeError("Only one of axis_angle and euler can be specified.")
elif axis_angle is not None:
if not (type(axis_angle) == list or type(axis_angle) == np.ndarray) or len(axis_angle) != 3:
raise ValueError("axis_angle must be list or np.ndarray with length 3.")
axis_angle = np.array(axis_angle)
norm = np.linalg.norm(axis_angle)
self.w = np.cos(norm / 2)
if norm < 1e-50: # to avoid instabilities and nans
self.x = 0
self.y = 0
self.z = 0
else:
imag = axis_angle / norm * np.sin(norm / 2)
self.x = imag[0].item()
self.y = imag[1].item()
self.z = imag[2].item()
else:
roll = euler[0]
pitch = euler[1]
yaw = euler[2]
cy = np.cos(yaw * 0.5)
sy = np.sin(yaw * 0.5)
cr = np.cos(roll * 0.5)
sr = np.sin(roll * 0.5)
cp = np.cos(pitch * 0.5)
sp = np.sin(pitch * 0.5)
# static frame
self.w = cr * cp * cy + sr * sp * sy
self.x = sr * cp * cy - cr * sp * sy
self.y = cr * sp * cy + sr * cp * sy
self.z = cr * cp * sy - sr * sp * cy
# rotating frame
# self.w = cr * cp * cy - sr * sp * sy
# self.x = cr * sp * sy + sr * cp * cy
# self.y = cr * sp * cy - sr * cp * sy
# self.z = cr * cp * sy + sr * sp * cy
def __repr__(self):
return "Quaternion (wxyz): [%2.5f, %2.5f, %2.5f, %2.5f]" % (self.w, self.x, self.y, self.z)
def to_mat(self):
v = np.array([self.x, self.y, self.z]).reshape(3,1)
return (self.w ** 2 - np.dot(v.T, v)) * np.eye(3) + \
2 * np.dot(v, v.T) + 2 * self.w * skew_symmetric(v)
def to_euler(self):
""" Return as xyz (roll pitch yaw) Euler angles, with a static frame """
roll = np.arctan2(2 * (self.w * self.x + self.y * self.z), 1 - 2 * (self.x**2 + self.y**2))
pitch = np.arcsin(2 * (self.w * self.y - self.z * self.x))
yaw = np.arctan2(2 * (self.w * self.z + self.x * self.y), 1 - 2 * (self.y**2 + self.z**2))
return np.array([roll, pitch, yaw])
def to_numpy(self):
""" Return numpy wxyz representation. """
return np.array([self.w, self.x, self.y, self.z])
def normalize(self):
""" Return a normalized version of this quaternion to ensure that it is valid. """
norm = np.linalg.norm([self.w, self.x, self.y, self.z])
return Quaternion(self.w / norm, self.x / norm, self.y / norm, self.z / norm)
def quat_mult(self, q, out='np'):
"""
Give output of multiplying this quaternion by another quaternion.
See equation from C2M5L2 Slide 7 for details.
:param q: The quaternion to multiply by, either a Quaternion or 4x1 ndarray.
:param out: Output type, either np or Quaternion.
:return: Returns quaternion of desired type
"""
v = np.array([self.x, self.y, self.z]).reshape(3, 1)
sum_term = np.zeros([4,4])
sum_term[0,1:] = -v[:,0]
sum_term[1:, 0] = v[:,0]
sum_term[1:, 1:] = -skew_symmetric(v)
sigma = self.w * np.eye(4) + sum_term
if type(q).__name__ == "Quaternion":
quat_np = np.dot(sigma, q.to_numpy())
else:
quat_np = np.dot(sigma, q)
if out == 'np':
return quat_np
elif out == 'Quaternion':
quat_obj = Quaternion(quat_np[0], quat_np[1], quat_np[2], quat_np[3])
return quat_obj