Skip to content

Commit fdece22

Browse files
committed
Port point cloud transformation to numpy
1 parent 69f78ce commit fdece22

File tree

3 files changed

+69
-23
lines changed

3 files changed

+69
-23
lines changed

tf2_sensor_msgs/CMakeLists.txt

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ endif()
1212
find_package(ament_cmake_auto REQUIRED)
1313
set(required_dependencies
1414
"sensor_msgs"
15-
#"python_orocos_kdl"
1615
"tf2"
1716
"tf2_ros"
1817
)
@@ -37,9 +36,8 @@ if(BUILD_TESTING)
3736
set(ament_cmake_cppcheck_LANGUAGE c++)
3837
ament_lint_auto_find_test_dependencies()
3938

40-
# TODO(ros2/orocos_kinematics_dynamics): reenable when PyKDL is ready to use
41-
#find_package(ament_cmake_pytest REQUIRED)
42-
#ament_add_pytest_test(test_tf2_sensor_msgs_py test/test_tf2_sensor_msgs.py)
39+
find_package(ament_cmake_pytest REQUIRED)
40+
ament_add_pytest_test(test_tf2_sensor_msgs_py test/test_tf2_sensor_msgs.py)
4341

4442
ament_add_gtest(test_tf2_sensor_msgs_cpp test/test_tf2_sensor_msgs.cpp)
4543
if(TARGET test_tf2_sensor_msgs_cpp)

tf2_sensor_msgs/package.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
<depend>sensor_msgs</depend>
2424
<depend>tf2</depend>
2525
<depend>tf2_ros</depend>
26+
<depend>python3-numpy</depend>
2627

2728
<exec_depend>tf2_ros_py</exec_depend>
2829

tf2_sensor_msgs/src/tf2_sensor_msgs/tf2_sensor_msgs.py

Lines changed: 66 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,11 @@
2626
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
2727
# POSSIBILITY OF SUCH DAMAGE.
2828

29-
import PyKDL
29+
import tf2_ros
30+
import numpy as np
3031
from sensor_msgs.msg import PointCloud2
3132
from sensor_msgs.point_cloud2 import create_cloud, read_points
32-
import rospy # noqa(E401)
33-
import tf2_ros
33+
from geometry_msgs.msg import Transform
3434

3535

3636
def to_msg_msg(msg):
@@ -47,24 +47,71 @@ def from_msg_msg(msg):
4747
tf2_ros.ConvertRegistration().add_from_msg(PointCloud2, from_msg_msg)
4848

4949

50-
def transform_to_kdl(t):
51-
return PyKDL.Frame(PyKDL.Rotation.Quaternion(
52-
t.transform.rotation.x, t.transform.rotation.y,
53-
t.transform.rotation.z, t.transform.rotation.w),
54-
PyKDL.Vector(t.transform.translation.x,
55-
t.transform.translation.y,
56-
t.transform.translation.z))
50+
def transform_points(point_cloud: np.ndarray, transform: Transform) -> np.ndarray:
51+
"""
52+
Transforms a bulk of points from an numpy array using a provided `Transform`.
53+
54+
:param point_cloud: nx3 Array of points where n is the number of points
55+
:param transform: TF2 transform used for the transformation
56+
:returns: Array with the same shape as the input array, but with the transformation applied
57+
"""
58+
# Build affine transformation
59+
transform_translation = np.array([
60+
transform.translation.x,
61+
transform.translation.y,
62+
transform.translation.z
63+
])
64+
transform_rotation_matrix = _get_mat_from_quat(
65+
np.array([
66+
transform.rotation.w,
67+
transform.rotation.x,
68+
transform.rotation.y,
69+
transform.rotation.z
70+
]))
71+
72+
# "Batched" matmul meaning a matmul for each point
73+
# First we offset all points by the translation part followed by a rotation using the rotation matrix
74+
return np.einsum('ij, pj -> pi', transform_rotation_matrix, point_cloud + transform_translation)
75+
76+
5777

78+
def do_transform_cloud(cloud: PointCloud2, transform: Transform) -> PointCloud2:
79+
"""
80+
Applies a `Transform` on a `PointCloud2`.
5881
59-
# PointStamped
60-
def do_transform_cloud(cloud, transform):
61-
t_kdl = transform_to_kdl(transform)
62-
points_out = []
63-
for p_in in read_points(cloud):
64-
p_out = t_kdl * PyKDL.Vector(p_in[0], p_in[1], p_in[2])
65-
points_out.append(p_out)
66-
res = create_cloud(transform.header, cloud.fields, points_out)
67-
return res
82+
:param cloud: The point cloud that should be transformed
83+
:param transform: The transform which will applied to the point cloud
84+
:returns: The transformed point cloud
85+
"""
86+
points = read_points(cloud)
87+
points_out = transform_points(points, transform)
88+
return create_cloud(transform.header, cloud.fields, points_out)
6889

6990

7091
tf2_ros.TransformRegistration().add(PointCloud2, do_transform_cloud)
92+
93+
94+
def _get_mat_from_quat(quaternion: np.ndarray):
95+
"""
96+
Converts a quaternion to a rotation matrix
97+
98+
This method is currently needed because transforms3d is not released as a `.dep` and
99+
would require user interaction to set up.
100+
101+
:param quaternion: A numpy array containing the w, x, y, and z components of the quaternion
102+
:returns: An array containing an X, Y, and Z translation component
103+
"""
104+
Nq = np.linalg.norm(quaternion)
105+
if Nq < np.finfo(np.float64).eps:
106+
return np.eye(3)
107+
108+
XYZ = quaternion[1:] * 2.0 / Nq
109+
wXYZ = XYZ * quaternion[0]
110+
xXYZ = XYZ * quaternion[1]
111+
yYZ = XYZ[1:] * quaternion[2]
112+
zZ = XYZ[2] * quaternion[3]
113+
114+
return np.array(
115+
[[ 1.0-(yYZ[0]+zZ), xXYZ[1]-wXYZ[2], xXYZ[2]+wXYZ[1]],
116+
[ xXYZ[1]+wXYZ[2], 1.0-(xXYZ[0]+zZ), yYZ[1]-wXYZ[0]],
117+
[ xXYZ[2]-wXYZ[1], yYZ[1]+wXYZ[0], 1.0-(xXYZ[0]+yYZ[0])]])

0 commit comments

Comments
 (0)