Skip to content

Commit 35a0ae4

Browse files
committed
(refactor) Removes numpy-quaternion dependency and implements transformations using numpy
1 parent bf716dc commit 35a0ae4

File tree

4 files changed

+63
-46
lines changed

4 files changed

+63
-46
lines changed

pyproject.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ keywords = ["robotics", "gpgpu", "navigation", "control", "planning", "mapping"]
2121

2222
dependencies = [
2323
"numpy<=1.26.4",
24-
"numpy-quaternion>=2023.0.3",
2524
"toml",
2625
"attrs>=23.2.0",
2726
"pyyaml",

src/kompass_core/datatypes/pose.py

Lines changed: 19 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,30 @@
11
from typing import Union
22

33
import numpy as np
4-
import quaternion
5-
from quaternion import quaternion as quat
64

75

86
def _equal_approx(
9-
v1, v2, is_quaternion=False, absolute_tolerance=0.01
7+
v1: np.ndarray,
8+
v2: np.ndarray,
9+
is_quaternion: bool = False,
10+
absolute_tolerance: float = 0.01,
1011
) -> Union[bool, np.bool_]:
1112
"""
12-
check if two vector/quaternion arrays are approximately equals
13-
to within absolute_tolerance
14-
15-
:param v1: vector 1
16-
:type v1: numpy/ numpy-quaternion
17-
:param v2: vector 2
18-
:type v2: vector numpy / numpy-quaternion
19-
:param is_quaternion: is it quaternion check?, defaults to False
20-
:type is_quaternion: bool, optional
21-
:param absolute_tolerance: tolerate some difference if exist, defaults to 0.01
22-
:type absolute_tolerance: float, optional
23-
24-
:return: is both vectors are equal?
25-
:rtype: bool
13+
Check if two vectors or quaternions are approximately equal within a tolerance.
14+
15+
:param v1: First vector or quaternion [w, x, y, z]
16+
:param v2: Second vector or quaternion
17+
:param is_quaternion: Set to True if comparing quaternions
18+
:param absolute_tolerance: Absolute tolerance for comparison
19+
:return: True if approximately equal
2620
"""
2721
if is_quaternion:
28-
equal = quaternion.allclose(a=v1, b=v2, rtol=0.0, atol=absolute_tolerance)
22+
# q and -q represent the same rotation
23+
return np.allclose(v1, v2, rtol=0.0, atol=absolute_tolerance) or np.allclose(
24+
v1, -v2, rtol=0.0, atol=absolute_tolerance
25+
)
2926
else:
30-
equal = np.allclose(a=v1, b=v2, rtol=0.0, atol=absolute_tolerance)
31-
32-
return equal
27+
return np.allclose(v1, v2, rtol=0.0, atol=absolute_tolerance)
3328

3429

3530
class PoseData:
@@ -134,14 +129,14 @@ def get_position(self) -> np.ndarray:
134129
"""
135130
return np.array([self.x, self.y, self.z], dtype=np.float32)
136131

137-
def get_orientation(self) -> quaternion.quaternion:
132+
def get_orientation(self) -> np.ndarray:
138133
"""
139134
Get the orientation represented as quaternion
140135
141136
:return: orientation represented as quaternion
142-
:rtype: quaternion.quaternion
137+
:rtype: np.ndarray
143138
"""
144-
return quat(self.qw, self.qx, self.qy, self.qz)
139+
return np.array([self.qw, self.qx, self.qy, self.qz])
145140

146141
def get_yaw(self) -> np.float64:
147142
"""

src/kompass_core/utils/geometry.py

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
from typing import List, Union, Tuple
33

44
import numpy as np
5-
import quaternion
6-
from quaternion import quaternion as quat
75

86
from ..datatypes.pose import PoseData
97
from ..datatypes.laserscan import LaserScanData
@@ -81,21 +79,41 @@ def probability_of_collision(
8179
return prop_col
8280

8381

84-
def _rotate_vector_by_quaternion(q: quaternion.quaternion, v: List) -> List:
82+
def _np_quaternion_multiply(q1: np.ndarray, q2: np.ndarray) -> np.ndarray:
8583
"""
86-
rotate a vector v by a rotation quaternion q
84+
Multiplies two quaternions q1 * q2
85+
Each quaternion is an array [w, x, y, z]
86+
"""
87+
w0, x0, y0, z0 = q1
88+
w1, x1, y1, z1 = q2
89+
return np.array([
90+
w0 * w1 - x0 * x1 - y0 * y1 - z0 * z1,
91+
w0 * x1 + x0 * w1 + y0 * z1 - z0 * y1,
92+
w0 * y1 - x0 * z1 + y0 * w1 + z0 * x1,
93+
w0 * z1 + x0 * y1 - y0 * x1 + z0 * w1,
94+
])
95+
96+
97+
def _np_quaternion_conjugate(q: np.ndarray) -> np.ndarray:
98+
"""
99+
Returns the conjugate of a quaternion
100+
"""
101+
w, x, y, z = q
102+
return np.array([w, -x, -y, -z])
103+
87104

88-
:param q: the rotation to perform
89-
:type q: quaternion.quaternion
90-
:param v: the vector to be rotated
91-
:type v: List
105+
def _rotate_vector_by_quaternion(q: np.ndarray, v: List[float]) -> List[float]:
106+
"""
107+
Rotate a 3D vector v by a quaternion q.
92108
93-
:return: the rotated position of the vector
94-
:rtype: List
109+
:param q: quaternion [w, x, y, z]
110+
:param v: vector [x, y, z]
111+
:return: rotated vector
95112
"""
96-
vq = quat(0, 0, 0, 0)
97-
vq.imag = v
98-
return (q * vq * q.inverse()).imag
113+
vq = np.array([0.0, *v])
114+
q_conj = _np_quaternion_conjugate(q)
115+
rotated_vq = _np_quaternion_multiply(_np_quaternion_multiply(q, vq), q_conj)
116+
return rotated_vq[1:].tolist()
99117

100118

101119
def get_pose_target_in_reference_frame(
@@ -119,21 +137,24 @@ def get_pose_target_in_reference_frame(
119137
reference_position = reference_pose.get_position()
120138
reference_rotation = reference_pose.get_orientation()
121139

122-
orientation_target_in_ref = reference_rotation.inverse() * orientation_target
140+
orientation_target_in_ref = (
141+
_np_quaternion_conjugate(reference_rotation) * orientation_target
142+
)
123143

124144
position_target_in_ref = _rotate_vector_by_quaternion(
125-
reference_rotation.inverse(), (position_target - reference_position).tolist()
145+
_np_quaternion_conjugate(reference_rotation),
146+
(position_target - reference_position).tolist(),
126147
)
127148

128149
target_pose_in_ref = PoseData()
129150
target_pose_in_ref.set_pose(
130151
x=position_target_in_ref[0],
131152
y=position_target_in_ref[1],
132153
z=position_target_in_ref[2],
133-
qw=orientation_target_in_ref.w,
134-
qx=orientation_target_in_ref.x,
135-
qy=orientation_target_in_ref.y,
136-
qz=orientation_target_in_ref.z,
154+
qw=orientation_target_in_ref[0],
155+
qx=orientation_target_in_ref[1],
156+
qy=orientation_target_in_ref[2],
157+
qz=orientation_target_in_ref[3],
137158
)
138159

139160
return target_pose_in_ref
@@ -209,7 +230,7 @@ def from_2d_to_PoseData(x: float, y: float, heading: float) -> PoseData:
209230
:rtype: PoseData
210231
"""
211232
pose_data = PoseData()
212-
quat_data: quat = from_euler_to_quaternion(heading, 0.0, 0.0)
233+
quat_data: np.ndarray = from_euler_to_quaternion(heading, 0.0, 0.0)
213234
pose_data.set_position(x, y, 0.0)
214235
pose_data.set_orientation(
215236
qw=quat_data[0], qx=quat_data[1], qy=quat_data[2], qz=quat_data[3]

src/kompass_cpp/tests/trajectory_sampler_plt.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ def plot_samples(figure_name, trajectories, reference=None):
3232
try:
3333
import matplotlib.pyplot as plt
3434
except ImportError:
35-
print("Matplotlib is not installed. Test figures will not be generated. To generate figures run 'pip install matplotlib'")
35+
print(
36+
"Matplotlib is not installed. Test figures will not be generated. To generate figures run 'pip install matplotlib'"
37+
)
3638
return
3739

3840
if reference:

0 commit comments

Comments
 (0)