Swarm-SLAM  1.0.0
C-SLAM Framework
lidar_handler_node.py
Go to the documentation of this file.
1 #!/usr/bin/env python3
2 import numpy as np
3 from message_filters import ApproximateTimeSynchronizer, Subscriber
4 from sensor_msgs.msg import PointCloud2, PointField, NavSatFix
5 from nav_msgs.msg import Odometry
6 from cslam_common_interfaces.msg import KeyframeOdom, KeyframePointCloud
7 from cslam_common_interfaces.msg import LocalDescriptorsRequest, LocalPointCloudDescriptors, InterRobotLoopClosure, IntraRobotLoopClosure, LocalKeyframeMatch
8 from cslam_common_interfaces.msg import VizPointCloud
9 import cslam.lidar_pr.icp_utils as icp_utils
10 import rclpy
11 from rclpy.node import Node
12 from rclpy.clock import Clock
13 from rclpy.qos import QoSProfile, ReliabilityPolicy, HistoryPolicy, DurabilityPolicy
14 from diagnostic_msgs.msg import KeyValue
15 
16 class LidarHandler:
17  """Front-End data handler for lidar data
18  """
19  def __init__(self, node, params):
20  self.nodenode = node
21  self.paramsparams = params
22 
23  qos_profile = QoSProfile(
24  reliability=ReliabilityPolicy.BEST_EFFORT,
25  history=HistoryPolicy.KEEP_LAST,
26  depth=100
27  )
28 
29  tss = ApproximateTimeSynchronizer( [ Subscriber(self.nodenode, PointCloud2, self.paramsparams["frontend.pointcloud_topic"], qos_profile=qos_profile),
30  Subscriber(self.nodenode, Odometry, self.paramsparams["frontend.odom_topic"])], 100, self.paramsparams["frontend.pointcloud_odom_approx_time_sync_s"] )
31  tss.registerCallback(self.lidar_callbacklidar_callback)
32 
33  self.keyframe_odom_publisherkeyframe_odom_publisher = self.nodenode.create_publisher(KeyframeOdom, "cslam/keyframe_odom", 100)
34 
35  self.keyframe_pointcloud_publisherkeyframe_pointcloud_publisher = self.nodenode.create_publisher(KeyframePointCloud, "cslam/keyframe_data", 100)
36 
37  self.send_local_descriptors_subscribersend_local_descriptors_subscriber = self.nodenode.create_subscription(LocalDescriptorsRequest,
38  "cslam/local_descriptors_request", self.send_local_descriptors_requestsend_local_descriptors_request, 100)
39 
40  self.local_keyframe_match_subscriberlocal_keyframe_match_subscriber = self.nodenode.create_subscription(LocalKeyframeMatch,
41  "cslam/local_keyframe_match", self.receive_local_keyframe_matchreceive_local_keyframe_match, 100)
42 
43  self.pointcloud_descriptors_publisherpointcloud_descriptors_publisher = self.nodenode.create_publisher(LocalPointCloudDescriptors, "/cslam/local_descriptors", 100)
44 
45  self.pointcloud_descriptors_subscriberpointcloud_descriptors_subscriber = self.nodenode.create_subscription(LocalPointCloudDescriptors, "/cslam/local_descriptors", self.receive_local_descriptorsreceive_local_descriptors, 100)
46 
47  self.intra_robot_loop_closure_publisherintra_robot_loop_closure_publisher = self.nodenode.create_publisher(IntraRobotLoopClosure, "cslam/intra_robot_loop_closure", 100)
48 
49  self.inter_robot_loop_closure_publisherinter_robot_loop_closure_publisher = self.nodenode.create_publisher(InterRobotLoopClosure, "/cslam/inter_robot_loop_closure", 100)
50 
51  self.viz_pointcloud_publisherviz_pointcloud_publisher = self.nodenode.create_publisher(VizPointCloud, "/cslam/viz/keyframe_pointcloud", 100)
52 
53  period_ms = self.paramsparams["frontend.map_manager_process_period_ms"]
54  self.processing_timerprocessing_timer = self.nodenode.create_timer(float(period_ms)/1000, self.process_new_sensor_dataprocess_new_sensor_data, clock=Clock())
55 
56  self.received_datareceived_data = []
57  self.local_descriptors_maplocal_descriptors_map = {}
58  self.nb_local_keyframesnb_local_keyframes = 0
59  self.previous_keyframeprevious_keyframe = None
60  self.previous_odomprevious_odom = None
61 
62  if self.paramsparams["evaluation.enable_logs"]:
63  self.log_publisherlog_publisher = self.nodenode.create_publisher(
64  KeyValue, 'log_info', 100)
65  self.log_local_descriptors_cumulative_communicationlog_local_descriptors_cumulative_communication = 0
66 
67  if self.paramsparams["evaluation.enable_gps_recording"]:
68  self.gps_subscribergps_subscriber = self.nodenode.create_subscription(NavSatFix, self.paramsparams["evaluation.gps_topic"], self.gps_callbackgps_callback, 100)
69  self.gps_datagps_data = []
70  self.latest_gpslatest_gps = NavSatFix()
71 
72  def lidar_callback(self, pc_msg, odom_msg):
73  """Sensor data callback
74 
75  Args:
76  pc_msg (sensor_msgs/Pointcloud2): point cloud
77  odom_msg (nav_msgs/Odometry): odometry estimate
78  """
79  if (odom_msg.pose.covariance[0] > 1000):
80  self.nodenode.get_logger().warn("Odom tracking failed, skipping frame")
81  return
82  self.received_datareceived_data.append((pc_msg, odom_msg))
83  if self.paramsparams["evaluation.enable_gps_recording"]:
84  self.gps_datagps_data.append(self.latest_gpslatest_gps)
85 
86  def gps_callback(self, gps_msg):
87  """GPS data callback
88  """
89  self.latest_gpslatest_gps = gps_msg
90 
91  def send_local_descriptors_request(self, request):
92  """Callback for local descriptors request
93  """
94  out_msg = LocalPointCloudDescriptors()
95  out_msg.data = icp_utils.open3d_to_ros(self.local_descriptors_maplocal_descriptors_map[request.keyframe_id])
96  out_msg.keyframe_id = request.keyframe_id
97  out_msg.robot_id = self.paramsparams["robot_id"]
98  out_msg.matches_robot_id = request.matches_robot_id
99  out_msg.matches_keyframe_id = request.matches_keyframe_id
100 
101  self.pointcloud_descriptors_publisherpointcloud_descriptors_publisher.publish(out_msg)
102  if self.paramsparams["evaluation.enable_logs"]:
103  self.log_local_descriptors_cumulative_communicationlog_local_descriptors_cumulative_communication += len(out_msg.data.data)
104  self.log_publisherlog_publisher.publish(KeyValue(key="local_descriptors_cumulative_communication", value=str(self.log_local_descriptors_cumulative_communicationlog_local_descriptors_cumulative_communication)))
105 
107  """Callback for local descriptors from other robots
108  """
109  frame_ids = []
110  for i in range(len(msg.matches_robot_id)):
111  if msg.matches_robot_id[i] == self.paramsparams["robot_id"]:
112  frame_ids.append(msg.matches_keyframe_id[i])
113  for frame_id in frame_ids:
114  pc = self.local_descriptors_maplocal_descriptors_map[frame_id]
115  transform, success = icp_utils.compute_transform(pc, icp_utils.ros_to_open3d(msg.data), self.paramsparams["frontend.voxel_size"], self.paramsparams["frontend.registration_min_inliers"])
116  out_msg = InterRobotLoopClosure()
117  out_msg.robot0_id = self.paramsparams["robot_id"]
118  out_msg.robot0_keyframe_id = frame_id
119  out_msg.robot1_id = msg.robot_id
120  out_msg.robot1_keyframe_id = msg.keyframe_id
121  if success:
122  out_msg.success = True
123  out_msg.transform = transform
124  else:
125  out_msg.success = False
126  self.inter_robot_loop_closure_publisherinter_robot_loop_closure_publisher.publish(out_msg)
127 
129  """Callback for local keyframe match for intra-robot loop closures
130  """
131  pc0 = self.local_descriptors_maplocal_descriptors_map[msg.keyframe0_id]
132  pc1 = self.local_descriptors_maplocal_descriptors_map[msg.keyframe1_id]
133  transform, success = icp_utils.compute_transform(pc0, pc1, self.paramsparams["frontend.voxel_size"], self.paramsparams["frontend.registration_min_inliers"])
134  out_msg = IntraRobotLoopClosure()
135  out_msg.keyframe0_id = msg.keyframe0_id
136  out_msg.keyframe1_id = msg.keyframe1_id
137  if success:
138  out_msg.success = True
139  out_msg.transform = transform
140  else:
141  out_msg.success = False
142  self.intra_robot_loop_closure_publisherintra_robot_loop_closure_publisher.publish(out_msg)
143 
144  def odom_distance_squared(self, odom0, odom1):
145  """Compute the squared distance between two odometry messages
146  """
147  return (odom0.pose.pose.position.x - odom1.pose.pose.position.x)**2 + (odom0.pose.pose.position.y - odom1.pose.pose.position.y)**2 + (odom0.pose.pose.position.z - odom1.pose.pose.position.z)**2
148 
149  def generate_new_keyframe(self, msg):
150  """Check if we should generate a new keyframe
151 
152  Args:
153  msg (nav_msgs/Odometry): odometry msg
154 
155  Returns:
156  bool: flag indicating if a new keyframe should be created.
157  """
158  if self.previous_odomprevious_odom is None:
159  self.previous_odomprevious_odom = msg[1]
160  return True
161  dist = self.odom_distance_squaredodom_distance_squared(self.previous_odomprevious_odom, msg[1])
162  if dist > self.paramsparams["frontend.keyframe_generation_ratio_distance"]**2:
163  self.previous_odomprevious_odom = msg[1]
164  return True
165  else:
166  return False
167 
169  """ Process new sensor data
170  """
171  if len(self.received_datareceived_data) > 0:
172  data = self.received_datareceived_data[0]
173  self.received_datareceived_data.pop(0)
174  gps_data = None
175  if self.paramsparams["evaluation.enable_gps_recording"]:
176  gps = self.gps_datagps_data[0]
177  self.gps_datagps_data.pop(0)
178  if self.generate_new_keyframegenerate_new_keyframe(data):
179  try:
180  self.local_descriptors_maplocal_descriptors_map[self.nb_local_keyframesnb_local_keyframes] = icp_utils.downsample_ros_pointcloud(data[0], self.paramsparams["frontend.voxel_size"])
181  except Exception as e:
182  self.local_descriptors_maplocal_descriptors_map[self.nb_local_keyframesnb_local_keyframes] = []
183  self.nodenode.get_logger().info("Failure to downsample point cloud to voxel size {}. {}".format(self.paramsparams["frontend.voxel_size"], e))
184  return
185  # Publish pointcloud
186  msg_pointcloud = KeyframePointCloud()
187  msg_pointcloud.id = self.nb_local_keyframesnb_local_keyframes
188  msg_pointcloud.pointcloud = icp_utils.open3d_to_ros(self.local_descriptors_maplocal_descriptors_map[self.nb_local_keyframesnb_local_keyframes])
189  self.keyframe_pointcloud_publisherkeyframe_pointcloud_publisher.publish(msg_pointcloud)
190  # Publish odom
191  msg_odom = KeyframeOdom()
192  msg_odom.id = self.nb_local_keyframesnb_local_keyframes
193  msg_odom.odom = data[1]
194  if self.paramsparams["evaluation.enable_gps_recording"]:
195  msg_odom.gps = gps
196  self.keyframe_odom_publisherkeyframe_odom_publisher.publish(msg_odom)
197  if self.paramsparams["visualization.enable"]:
198  viz_msg = VizPointCloud()
199  viz_msg.robot_id = self.paramsparams["robot_id"]
200  viz_msg.keyframe_id = self.nb_local_keyframesnb_local_keyframes
201  viz_msg.pointcloud = msg_pointcloud.pointcloud
202  self.viz_pointcloud_publisherviz_pointcloud_publisher.publish(viz_msg)
203  self.nb_local_keyframesnb_local_keyframes = self.nb_local_keyframesnb_local_keyframes + 1
204 
205 if __name__ == '__main__':
206 
207  rclpy.init(args=None)
208  node = Node("map_manager")
209  node.declare_parameters(
210  namespace='',
211  parameters=[('frontend.pointcloud_topic', 'pointcloud'),
212  ('frontend.odom_topic', 'odom'),
213  ('frontend.map_manager_process_period_ms', 100),
214  ('frontend.voxel_size', 0.5),
215  ('frontend.registration_min_inliers', 60),
216  ('frontend.keyframe_generation_ratio_distance', 0.5),
217  ('frontend.pointcloud_odom_approx_time_sync_s', 0.1),
218  ('robot_id', 0),
219  ('evaluation.enable_logs', False),
220  ('evaluation.enable_gps_recording', False),
221  ('evaluation.gps_topic', ""),
222  ('evaluation.gps_topic', ""),
223  ('visualization.enable', False),
224  ])
225  params = {}
226  params['frontend.pointcloud_topic'] = node.get_parameter(
227  'frontend.pointcloud_topic').value
228  params['frontend.odom_topic'] = node.get_parameter(
229  'frontend.odom_topic').value
230  params['frontend.map_manager_process_period_ms'] = node.get_parameter(
231  'frontend.map_manager_process_period_ms').value
232  params['frontend.voxel_size'] = node.get_parameter(
233  'frontend.voxel_size').value
234  params['frontend.registration_min_inliers'] = node.get_parameter(
235  'frontend.registration_min_inliers').value
236  params['frontend.keyframe_generation_ratio_distance'] = node.get_parameter(
237  'frontend.keyframe_generation_ratio_distance').value
238  params['frontend.pointcloud_odom_approx_time_sync_s'] = node.get_parameter(
239  'frontend.pointcloud_odom_approx_time_sync_s').value
240  params['robot_id'] = node.get_parameter(
241  'robot_id').value
242  params["evaluation.enable_logs"] = node.get_parameter(
243  'evaluation.enable_logs').value
244  params["evaluation.enable_gps_recording"] = node.get_parameter(
245  'evaluation.enable_gps_recording').value
246  params["evaluation.gps_topic"] = node.get_parameter(
247  'evaluation.gps_topic').value
248  params["visualization.enable"] = node.get_parameter(
249  'visualization.enable').value
250  lidar_handler = LidarHandler(node, params)
251  node.get_logger().info('Initialization done.')
252  rclpy.spin(node)
253  rclpy.shutdown()
def odom_distance_squared(self, odom0, odom1)
def lidar_callback(self, pc_msg, odom_msg)
def ros_to_open3d(msg)
Definition: icp_utils.py:159
def downsample_ros_pointcloud(pc_msg, voxel_size)
Definition: icp_utils.py:170
def open3d_to_ros(open3d_cloud)
Definition: icp_utils.py:152
def compute_transform(src, dst, voxel_size, min_inliers)
Definition: icp_utils.py:174