6 from os.path
import join, exists, isfile, realpath, dirname
11 from cslam_common_interfaces.msg
import KeyframeRGB, KeyframePointCloud
12 from cslam_common_interfaces.msg
import (
13 GlobalDescriptor, GlobalDescriptors, InterRobotLoopClosure,
14 LocalDescriptorsRequest, LocalKeyframeMatch, InterRobotMatch,
16 from diagnostic_msgs.msg
import KeyValue
18 from sortedcontainers
import SortedDict
21 from rclpy.node
import Node
22 from rclpy.clock
import Clock
25 from cslam.utils.misc
import dict_to_list_chunks
28 """ Global descriptor matching """
34 params (dict): parameters
35 node (ROS 2 node handle): node handle
42 if self.
paramsparams[
'frontend.global_descriptor_technique'].lower(
44 from cslam.vpr.netvlad
import NetVLAD
45 self.
nodenode.get_logger().info(
'Using NetVLAD.')
48 elif self.
paramsparams[
'frontend.global_descriptor_technique'].lower(
50 from cslam.lidar_pr.scancontext
import ScanContext
52 import cslam.lidar_pr.icp_utils
as icp_utils
53 self.
nodenode.get_logger().info(
'Using ScanContext.')
57 from cslam.vpr.cosplace
import CosPlace
58 self.
nodenode.get_logger().info(
'Using CosPlace. (default)')
64 'frontend.global_descriptors_topic'] =
'/cslam/' + self.
nodenode.get_parameter(
65 'frontend.global_descriptors_topic').value
68 self.
paramsparams[
'frontend.global_descriptors_topic'], 100)
71 self.
paramsparams[
'frontend.global_descriptors_topic'],
75 'frontend.inter_robot_matches_topic'] =
'/cslam/' + self.
nodenode.get_parameter(
76 'frontend.inter_robot_matches_topic').value
79 self.
paramsparams[
'frontend.inter_robot_matches_topic'], 100)
82 self.
paramsparams[
'frontend.inter_robot_matches_topic'],
87 KeyframeRGB,
'cslam/keyframe_data', self.
receive_keyframereceive_keyframe, 100)
90 KeyframePointCloud,
'cslam/keyframe_data', self.
receive_keyframereceive_keyframe,
93 self.
nodenode.get_logger().error(
"Unknown keyframe type")
96 LocalKeyframeMatch,
'cslam/local_keyframe_match', 100)
99 InterRobotLoopClosure,
'/cslam/inter_robot_loop_closure',
103 for i
in range(self.
paramsparams[
'max_nb_robots']):
105 i] = self.
nodenode.create_publisher(
106 LocalDescriptorsRequest,
107 '/r' + str(i) +
'/cslam/local_descriptors_request', 100)
115 self.
paramsparams[
'frontend.detection_publication_period_sec'],
123 self.
paramsparams[
'frontend.detection_publication_period_sec'],
128 if self.
paramsparams[
"evaluation.enable_logs"]:
130 KeyValue,
'cslam/log_info', 100)
132 InterRobotMatches,
'cslam/log_matches', 100)
144 from cv_bridge
import CvBridge
149 """ Add global descriptor to matching list
152 embedding (np.array): descriptor
153 kf_id (int): keyframe ID
156 matches = self.
lcmlcm.add_local_global_descriptor(embedding, kf_id)
162 msg.keyframe_id = kf_id
163 msg.robot_id = self.
paramsparams[
'robot_id']
164 msg.descriptor = embedding.tolist()
168 for match
in matches:
174 """Deletes global descriptors
175 because all other robots have already received them.
185 """Deletes inter_robot_matches
186 because all other robots have already received them.
192 if k < from_match_id:
196 """Publish global descriptors message periodically
197 Doesn't publish if the descriptors are already known by neighboring robots
200 from_kf_id = self.
neighbor_managerneighbor_manager.select_from_which_kf_to_send(
206 self.
paramsparams[
'frontend.detection_publication_max_elems_per_msg']
210 global_descriptors = GlobalDescriptors()
211 global_descriptors.descriptors = m
213 if self.
paramsparams[
"evaluation.enable_logs"]:
215 global_descriptors.descriptors) * len(
216 global_descriptors.descriptors[0].descriptor
220 if self.
paramsparams[
"evaluation.enable_logs"]:
222 KeyValue(key=
"detection_cumulative_communication",
227 """Converts an InterRobotEdge to a InterRobotMatch message
228 Args: edge (InterRobotEdge)
230 msg = InterRobotMatch()
231 msg.robot0_id = edge.robot0_id
232 msg.robot0_keyframe_id = edge.robot0_keyframe_id
233 msg.robot1_id = edge.robot1_id
234 msg.robot1_keyframe_id = edge.robot1_keyframe_id
235 msg.weight = edge.weight
239 """Publish inter-robot matches message periodically
240 Doesn't publish if the inter-robot matches are already known by neighboring robots
243 from_match_idx = self.
neighbor_managerneighbor_manager.select_from_which_match_to_send(
249 params[
'frontend.detection_publication_max_elems_per_msg'])
252 _, neighbors_in_range_list = self.
neighbor_managerneighbor_manager.check_neighbors_in_range()
253 if len(neighbors_in_range_list) == 2:
254 self.
nodenode.get_logger().info(
"Transmitting matches {}".format(neighbors_in_range_list))
257 if match.robot0_id
in neighbors_in_range_list
and match.robot1_id
in neighbors_in_range_list:
273 inter_robot_matches = InterRobotMatches()
274 inter_robot_matches.robot_id = self.
paramsparams[
'robot_id']
275 inter_robot_matches.matches = m
277 if self.
paramsparams[
"evaluation.enable_logs"]:
279 inter_robot_matches.matches) * 20
282 if self.
paramsparams[
"evaluation.enable_logs"]:
284 KeyValue(key=
"detection_cumulative_communication",
289 """ Detect intra-robot loop closures
292 embedding (np.array): descriptor
293 kf_id (int): keyframe ID
296 list(int): matched keyframes
298 if self.
paramsparams[
'frontend.enable_intra_robot_loop_closures']:
299 kf_match, _ = self.
lcmlcm.match_local_loop_closures(embedding, kf_id)
300 if kf_match
is not None:
301 msg = LocalKeyframeMatch()
302 msg.keyframe0_id = kf_id
303 msg.keyframe1_id = kf_match
307 """ Detect inter-robot loop closures
310 list(int): selected keyframes from other robots to match
312 neighbors_is_in_range, neighbors_in_range_list = self.
neighbor_managerneighbor_manager.check_neighbors_in_range(
316 if len(neighbors_in_range_list
318 if self.
paramsparams[
"evaluation.enable_logs"]: start_time = time.time()
320 selection = self.
lcmlcm.select_candidates(
321 self.
paramsparams[
"frontend.inter_robot_loop_closure_budget"],
322 neighbors_is_in_range)
326 broker =
Broker(selection, neighbors_in_range_list)
327 for selected_vertices_set
in broker.brokerage(
328 self.
paramsparams[
"frontend.use_vertex_cover_selection"]):
329 for v
in selected_vertices_set:
331 msg = LocalDescriptorsRequest()
332 msg.keyframe_id = v[1]
333 msg.matches_robot_id = vertices_info[v][0]
334 msg.matches_keyframe_id = vertices_info[v][1]
337 if self.
paramsparams[
"evaluation.enable_logs"]:
339 selected_vertices_set)
340 if self.
paramsparams[
"evaluation.enable_logs"]:
341 stop_time = time.time()
346 key=
"sparsification_cumulative_computation_time",
350 KeyValue(key=
"nb_vertices_transmitted",
353 KeyValue(key=
"nb_matches_selected",
355 if self.
paramsparams[
"evaluation.enable_sparsification_comparison"]:
356 matches = InterRobotMatches()
357 matches.robot_id = self.
paramsparams[
"robot_id"]
358 for e
in self.
lcmlcm.candidate_selector.log_mac_edges:
363 """Extracts the vertices in a list of edges
365 selection list(EdgeInterRobot): selection of edges
367 dict((int, int), list(int), list(int)): Vertices indices with their related vertices
371 key0 = (s.robot0_id, s.robot0_keyframe_id)
372 key1 = (s.robot1_id, s.robot1_keyframe_id)
374 vertices[key0][0].append(s.robot1_id)
375 vertices[key0][1].append(s.robot1_keyframe_id)
377 vertices[key0] = [[s.robot1_id], [s.robot1_keyframe_id]]
379 vertices[key1][0].append(s.robot0_id)
380 vertices[key1][1].append(s.robot0_keyframe_id)
382 vertices[key1] = [[s.robot0_id], [s.robot0_keyframe_id]]
386 """Callback to add a keyframe
389 msg (cslam_common_interfaces::msg::KeyframeRGB or KeyframePointCloud): Keyframe data
395 cv_image = bridge.imgmsg_to_cv2(msg.image,
396 desired_encoding=
'passthrough')
405 """Callback for descriptors received from other robots.
408 msg (cslam_common_interfaces::msg::GlobalDescriptors): descriptors
410 if msg.descriptors[0].robot_id != self.
paramsparams[
'robot_id']:
413 for i
in unknown_range:
414 match = self.
lcmlcm.add_other_robot_global_descriptor(
416 if match
is not None:
422 """Callback for inter-robot matches received from other robots.
425 msg (cslam_common_interfaces::msg::InterRobotMatches): matches
427 if msg.robot_id != self.
paramsparams[
'robot_id']:
428 for match
in msg.matches:
429 edge =
EdgeInterRobot(match.robot0_id, match.robot0_keyframe_id, match.robot1_id, match.robot1_keyframe_id, match.weight)
430 self.
lcmlcm.candidate_selector.add_match(edge)
433 """ Convert a inter-robot loop closure to an edge
434 for algebraic connectivity maximization
437 msg (cslam_common_interfaces::msg::InterRobotLoopClosure): Inter-robot loop closure
440 EdgeInterRobot: inter-robot edge
443 msg.robot1_id, msg.robot1_keyframe_id,
444 self.
lcmlcm.candidate_selector.fixed_weight)
447 """Receive computed inter-robot loop closure
450 msg (cslam_common_interfaces::msg::InterRobotLoopClosure): Inter-robot loop closure
453 self.
nodenode.get_logger().info(
454 'New inter-robot loop closure measurement: (' +
455 str(msg.robot0_id) +
',' + str(msg.robot0_keyframe_id) +
456 ') -> (' + str(msg.robot1_id) +
',' +
457 str(msg.robot1_keyframe_id) +
')')
459 self.
lcmlcm.candidate_selector.candidate_edges_to_fixed(
462 if self.
paramsparams[
"evaluation.enable_logs"]:
465 KeyValue(key=
"nb_matches",
469 self.
nodenode.get_logger().info(
470 'Failed inter-robot loop closure measurement: (' +
471 str(msg.robot0_id) +
',' + str(msg.robot0_keyframe_id) +
472 ') -> (' + str(msg.robot1_id) +
',' +
473 str(msg.robot1_keyframe_id) +
')')
474 self.
lcmlcm.candidate_selector.remove_candidate_edges(
477 if self.
paramsparams[
"evaluation.enable_logs"]:
480 KeyValue(key=
"nb_failed_matches",
log_total_successful_matches
def edge_list_to_vertices(self, selection)
def __init__(self, params, node)
log_total_vertices_transmitted
receive_keyframe_subscriber
def delete_useless_inter_robot_matches(self)
global_descriptor_subscriber
global_descriptors_buffer
global_descriptor_publisher
def receive_inter_robot_loop_closure(self, msg)
inter_robot_matches_publisher
def inter_robot_loop_closure_msg_to_edge(self, msg)
def receive_keyframe(self, msg)
local_descriptors_request_publishers
def add_global_descriptor_to_map(self, embedding, kf_id)
def global_descriptors_timer_callback(self)
inter_robot_matches_timer
def global_descriptor_callback(self, msg)
inter_robot_matches_buffer
def inter_robot_matches_callback(self, msg)
inter_robot_matches_subscriber
receive_inter_robot_loop_closure_subscriber
log_detection_cumulative_communication
def delete_useless_descriptors(self)
def detect_intra(self, embedding, kf_id)
def inter_robot_matches_timer_callback(self)
def edge_to_match(self, edge)
log_total_matches_selected
log_total_sparsification_computation_time
def ros_pointcloud_to_points(pc_msg)
def dict_to_list_chunks(d, start, chunk_size)