├── CMakeLists.txt ├── README.md ├── launch └── transform.launch ├── nodelet_plugins.xml ├── package.xml └── src └── transform_pointcloud_nodelet.cpp /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8.3) 2 | project(transform_pointcloud) 3 | 4 | add_compile_options(-std=c++11) 5 | 6 | find_package(catkin REQUIRED COMPONENTS 7 | roscpp 8 | sensor_msgs 9 | tf 10 | nodelet 11 | tf2_sensor_msgs 12 | ) 13 | 14 | catkin_package( 15 | # INCLUDE_DIRS include 16 | LIBRARIES transformPointcloud 17 | # CATKIN_DEPENDS roscpp sensor_msgs tf 18 | # DEPENDS system_lib 19 | ) 20 | 21 | ########### 22 | ## Build ## 23 | ########### 24 | 25 | include_directories( 26 | include 27 | ${catkin_INCLUDE_DIRS} 28 | ) 29 | 30 | ## Declare a C++ library 31 | add_library(transformPointcloud 32 | src/transform_pointcloud_nodelet.cpp 33 | ) 34 | add_dependencies(transformPointcloud ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS}) 35 | target_link_libraries(transformPointcloud 36 | ${catkin_LIBRARIES} 37 | ) 38 | 39 | 40 | install(TARGETS transformPointcloud 41 | DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}) 42 | install(DIRECTORY include/${PROJECT_NAME}/ 43 | DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION}) 44 | 45 | install(DIRECTORY plugins 46 | DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}) 47 | 48 | install(DIRECTORY launch 49 | DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}) 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # transform_pointcloud # 2 | ROS nodelet that transforms pointcloud (`sensor_msgs/Pointcloud` or `sensor_msgs/Pointcloud`) to some other 3 | TF frame (e.g. from "camera_frame" to "base_link"). It uses `sensor_msgs/point_cloud_conversion.h` for conversion. 4 | It will publish both `Pointcloud` and `Pointcloud2` messages for any input type (if any node is subscribed to it) 5 | # Subscribed topics 6 | * `~input_pcl (sensor_msgs/Pointcloud)` input topic 7 | * `~inptu_pcl2 (sensor_msgs/Pointcloud2)` input topic 8 | 9 | # Published topics 10 | * `~output_pcl (sensor_msgs/Pointcloud)` output topic 11 | * `~output_pcl2 (sensor_msgs/Pointcloud2)` output topic 12 | 13 | # Parameters 14 | * `to_frame (string, default:base_link)` to what TF frame to transform the output 15 | 16 | # TF 17 | * required is the transform from frame that input pointcloud is to frame set by `to_frame` parameter 18 | 19 | # Usage 20 | See `transform_pointcloud/launch/transform.launch` for example 21 | -------------------------------------------------------------------------------- /launch/transform.launch: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /nodelet_plugins.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Transform pointcloud nodelet 5 | 6 | 7 | -------------------------------------------------------------------------------- /package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | transform_pointcloud 4 | 0.0.0 5 | The transform_pointcloud package 6 | Tim 7 | BSD 8 | 9 | 10 | catkin 11 | roscpp 12 | sensor_msgs 13 | tf 14 | tf2_sensor_msgs 15 | nodelet 16 | roscpp 17 | sensor_msgs 18 | tf 19 | roscpp 20 | sensor_msgs 21 | tf 22 | tf2_sensor_msgs 23 | nodelet 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/transform_pointcloud_nodelet.cpp: -------------------------------------------------------------------------------- 1 | //Copyright (c) 2018, Tim Kambic 2 | //All rights reserved. 3 | // 4 | //Redistribution and use in source and binary forms, with or without 5 | //modification, are permitted provided that the following conditions are met: 6 | //* Redistributions of source code must retain the above copyright 7 | // notice, this list of conditions and the following disclaimer. 8 | //* Redistributions in binary form must reproduce the above copyright 9 | // notice, this list of conditions and the following disclaimer in the 10 | // documentation and/or other materials provided with the distribution. 11 | //* Neither the name of the nor the 12 | // names of its contributors may be used to endorse or promote products 13 | // derived from this software without specific prior written permission. 14 | // 15 | //THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | //ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | //WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | //DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 19 | //DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | //(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | //LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | //ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | //(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | //SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | 26 | #include "ros/ros.h" 27 | #include "nodelet/nodelet.h" 28 | #include 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | 36 | namespace transform_pointcloud { 37 | class transformPointcloud : public nodelet::Nodelet { 38 | ros::Publisher pc_pub; 39 | ros::Publisher pc2_pub; 40 | ros::Subscriber pc_sub; 41 | ros::Subscriber pc2_sub; 42 | 43 | std::string REFERENCE_FRAME = "base_link"; 44 | 45 | public: 46 | transformPointcloud() = default; 47 | 48 | private: 49 | virtual void onInit() { 50 | ros::NodeHandle nh = getNodeHandle(); 51 | ros::NodeHandle nhp = getPrivateNodeHandle(); 52 | if (!getPrivateNodeHandle().getParam("to_frame", REFERENCE_FRAME)) { 53 | ROS_ERROR("You must specify 'to_frame' parameter !"); 54 | exit(-123); 55 | } 56 | pc_pub = nhp.advertise("output_pcl", 1); 57 | pc2_pub = nhp.advertise("output_pcl2", 1); 58 | pc_sub = nhp.subscribe("input_pcl", 1, &transformPointcloud::pointcloudCB, this); 59 | pc2_sub = nhp.subscribe("input_pcl2", 1, &transformPointcloud::pointcloud2CB, this); 60 | }; 61 | 62 | bool getTransform(const std::string &refFrame, const std::string &childFrame, tf::StampedTransform &transform) { 63 | std::string errMsg; 64 | tf::TransformListener tf_listener; 65 | if (!tf_listener.waitForTransform(refFrame, childFrame, ros::Time(0), ros::Duration(0.5), 66 | ros::Duration(0.01), &errMsg)) { 67 | ROS_ERROR_STREAM("Pointcloud transform | Unable to get pose from TF: " << errMsg); 68 | return false; 69 | } else { 70 | try { 71 | tf_listener.lookupTransform(refFrame, childFrame, ros::Time(0), transform); 72 | } 73 | catch (const tf::TransformException &e) { 74 | ROS_ERROR_STREAM( 75 | "Pointcloud transform | Error in lookupTransform of " << childFrame << " in " << refFrame); 76 | return false; 77 | } 78 | } 79 | return true; 80 | } 81 | 82 | void pointcloudCB(const sensor_msgs::PointCloud::ConstPtr &cloud_in) { 83 | if (cloud_in->points.empty()) { return; } 84 | 85 | tf::StampedTransform tf_between_frames; 86 | getTransform(REFERENCE_FRAME, cloud_in->header.frame_id, tf_between_frames); // get transformation 87 | geometry_msgs::TransformStamped tf_between_frames_geo; 88 | tf::transformStampedTFToMsg(tf_between_frames, tf_between_frames_geo); // convert it to geometry_msg type 89 | 90 | sensor_msgs::PointCloud2 cloud_in2; 91 | sensor_msgs::convertPointCloudToPointCloud2(*cloud_in, cloud_in2); // convert from pointcloud to pointcloud2 92 | sensor_msgs::PointCloud2 cloud_in_transformed; 93 | tf2::doTransform(cloud_in2, cloud_in_transformed, tf_between_frames_geo); // do transformation 94 | if (pc2_pub.getNumSubscribers() > 0) { // publish pointcloud2 95 | pc2_pub.publish(cloud_in_transformed); 96 | } 97 | if (pc_pub.getNumSubscribers() > 0) { 98 | sensor_msgs::PointCloud cloud_out; 99 | sensor_msgs::convertPointCloud2ToPointCloud(cloud_in_transformed, cloud_out); //convert from pointcloud to pointcloud2 100 | pc_pub.publish(cloud_out); // publish pointcloud 101 | } 102 | } 103 | 104 | void pointcloud2CB(const sensor_msgs::PointCloud2::ConstPtr &cloud_in) { 105 | if (cloud_in->data.empty()) { return; } 106 | 107 | tf::StampedTransform tf_between_frames; 108 | getTransform(REFERENCE_FRAME, cloud_in->header.frame_id, tf_between_frames); 109 | geometry_msgs::TransformStamped tf_between_frames_geo; 110 | tf::transformStampedTFToMsg(tf_between_frames, tf_between_frames_geo); 111 | 112 | sensor_msgs::PointCloud2 cloud_in_transformed; 113 | tf2::doTransform(*cloud_in, cloud_in_transformed, tf_between_frames_geo); // do transformation 114 | if (pc2_pub.getNumSubscribers() > 0) { 115 | pc2_pub.publish(cloud_in_transformed); 116 | } 117 | if (pc_pub.getNumSubscribers() > 0) { 118 | sensor_msgs::PointCloud cloud_out; 119 | sensor_msgs::convertPointCloud2ToPointCloud(cloud_in_transformed, cloud_out); //convert from pointcloud to pointcloud2 120 | pc_pub.publish(cloud_out); 121 | } 122 | } 123 | 124 | 125 | }; 126 | } 127 | 128 | PLUGINLIB_DECLARE_CLASS(transform_pointcloud, transformPointcloud, transform_pointcloud::transformPointcloud, nodelet::Nodelet); 129 | 130 | --------------------------------------------------------------------------------