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
3031from sensor_msgs .msg import PointCloud2
3132from 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
3636def to_msg_msg (msg ):
@@ -47,24 +47,71 @@ def from_msg_msg(msg):
4747tf2_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
7091tf2_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