Swarm-SLAM  1.0.0
C-SLAM Framework
decentralized_pgo.cpp
Go to the documentation of this file.
2 
3 #define MAP_FRAME_ID(id) "robot" + std::to_string(id) + "_map"
4 #define CURRENT_FRAME_ID(id) "robot" + std::to_string(id) + "_current_pose"
5 #define LATEST_OPTIMIZED_FRAME_ID(id) "robot" + std::to_string(id) + "_latest_optimized_pose"
6 
7 using namespace cslam;
8 
9 DecentralizedPGO::DecentralizedPGO(std::shared_ptr<rclcpp::Node> &node)
10  : node_(node), max_waiting_time_sec_(60, 0)
11 {
12  node_->get_parameter("max_nb_robots", max_nb_robots_);
13  node_->get_parameter("robot_id", robot_id_);
14  node_->get_parameter("backend.pose_graph_optimization_start_period_ms",
15  pose_graph_optimization_start_period_ms_);
16  node_->get_parameter("backend.pose_graph_optimization_loop_period_ms",
17  pose_graph_optimization_loop_period_ms_);
18  node_->get_parameter("backend.enable_broadcast_tf_frames",
19  enable_broadcast_tf_frames_);
20  node_->get_parameter("neighbor_management.heartbeat_period_sec", heartbeat_period_sec_);
21  node->get_parameter("evaluation.enable_logs",
22  enable_logs_);
23  node->get_parameter("evaluation.log_folder",
24  log_folder_);
25  node->get_parameter("evaluation.enable_gps_recording",
26  enable_gps_recording_);
27  node->get_parameter("evaluation.enable_simulated_rendezvous", enable_simulated_rendezvous_);
28  std::string rendezvous_schedule_file;
29  node->get_parameter("evaluation.rendezvous_schedule_file", rendezvous_schedule_file);
30  node->get_parameter("evaluation.enable_pose_timestamps_recording", enable_pose_timestamps_recording_);
31  node_->get_parameter("visualization.enable",
32  enable_visualization_);
33  node_->get_parameter("visualization.publishing_period_ms",
34  visualization_period_ms_);
35 
36  int max_waiting_param;
37  node_->get_parameter("backend.max_waiting_time_sec", max_waiting_param);
38  max_waiting_time_sec_ = rclcpp::Duration(max_waiting_param, 0);
39 
40  odometry_subscriber_ =
41  node->create_subscription<cslam_common_interfaces::msg::KeyframeOdom>(
42  "cslam/keyframe_odom", 1000,
43  std::bind(&DecentralizedPGO::odometry_callback, this,
44  std::placeholders::_1));
45 
46  intra_robot_loop_closure_subscriber_ = node->create_subscription<
47  cslam_common_interfaces::msg::IntraRobotLoopClosure>(
48  "cslam/intra_robot_loop_closure", 1000,
50  std::placeholders::_1));
51 
52  inter_robot_loop_closure_subscriber_ = node->create_subscription<
53  cslam_common_interfaces::msg::InterRobotLoopClosure>(
54  "/cslam/inter_robot_loop_closure", 1000,
56  std::placeholders::_1));
57 
58  write_current_estimates_subscriber_ =
59  node->create_subscription<std_msgs::msg::String>(
60  "cslam/print_current_estimates", 100,
62  std::placeholders::_1));
63 
64  rotation_default_noise_std_ = 0.01;
65  translation_default_noise_std_ = 0.1;
66  Eigen::VectorXd sigmas(6);
67  sigmas << rotation_default_noise_std_, rotation_default_noise_std_,
68  rotation_default_noise_std_, translation_default_noise_std_,
69  translation_default_noise_std_, translation_default_noise_std_;
70  default_noise_model_ = gtsam::noiseModel::Diagonal::Sigmas(sigmas);
71  pose_graph_ = boost::make_shared<gtsam::NonlinearFactorGraph>();
72  current_pose_estimates_ = boost::make_shared<gtsam::Values>();
73  odometry_pose_estimates_ = boost::make_shared<gtsam::Values>();
74 
75  // Optimization timers
76  optimization_timer_ = node_->create_wall_timer(
77  std::chrono::milliseconds(pose_graph_optimization_start_period_ms_),
78  std::bind(&DecentralizedPGO::optimization_callback, this));
79 
80  optimization_loop_timer_ = node_->create_wall_timer(
81  std::chrono::milliseconds(pose_graph_optimization_loop_period_ms_),
83 
84  if (enable_visualization_)
85  {
86  RCLCPP_INFO(node_->get_logger(), "Visualization enabled.");
87  visualization_timer_ = node_->create_wall_timer(
88  std::chrono::milliseconds(visualization_period_ms_),
90  }
91  else
92  {
93  RCLCPP_INFO(node_->get_logger(), "Visualization disabled.");
94  }
95 
96  // Publishers for optimization result
97  debug_optimization_result_publisher_ =
98  node_->create_publisher<cslam_common_interfaces::msg::OptimizationResult>(
99  "cslam/debug_optimization_result", 100);
100 
101  for (unsigned int i = 0; i < max_nb_robots_; i++)
102  {
103  optimized_estimates_publishers_.insert(
104  {i, node->create_publisher<
105  cslam_common_interfaces::msg::OptimizationResult>(
106  "/r" + std::to_string(i) + "/cslam/optimized_estimates", 100)});
107  }
108 
109  optimized_estimates_subscriber_ = node->create_subscription<
110  cslam_common_interfaces::msg::OptimizationResult>(
111  "cslam/optimized_estimates", 100,
113  std::placeholders::_1));
114 
115  optimized_pose_estimate_publisher_ = node->create_publisher<
116  geometry_msgs::msg::PoseStamped>(
117  "/r" + std::to_string(robot_id_) + "/cslam/current_pose_estimate", 100);
118 
119  optimizer_state_publisher_ =
120  node_->create_publisher<cslam_common_interfaces::msg::OptimizerState>(
121  "cslam/optimizer_state", 100);
122 
123  // Initialize inter-robot loop closures measurements
124  for (unsigned int i = 0; i < max_nb_robots_; i++)
125  {
126  for (unsigned int j = i + 1; j < max_nb_robots_; j++)
127  {
128  inter_robot_loop_closures_.insert(
129  {{i, j}, std::vector<gtsam::BetweenFactor<gtsam::Pose3>>()});
130  }
131  }
132 
133  // Get neighbors ROS 2 objects
134  get_current_neighbors_publisher_ =
135  node->create_publisher<std_msgs::msg::String>("cslam/get_current_neighbors",
136  100);
137 
138  current_neighbors_subscriber_ = node->create_subscription<
139  cslam_common_interfaces::msg::RobotIdsAndOrigin>(
140  "cslam/current_neighbors", 100,
142  std::placeholders::_1));
143 
144  // PoseGraph ROS 2 objects
145  for (unsigned int i = 0; i < max_nb_robots_; i++)
146  {
147  get_pose_graph_publishers_.insert(
148  {i, node->create_publisher<cslam_common_interfaces::msg::RobotIds>(
149  "/r" + std::to_string(i) + "/cslam/get_pose_graph", 100)});
150  received_pose_graphs_.insert({i, false});
151  }
152 
153  get_pose_graph_subscriber_ =
154  node->create_subscription<cslam_common_interfaces::msg::RobotIds>(
155  "cslam/get_pose_graph", 100,
157  std::placeholders::_1));
158 
159  pose_graph_publisher_ =
160  node->create_publisher<cslam_common_interfaces::msg::PoseGraph>(
161  "/cslam/pose_graph", 100);
162 
163  pose_graph_subscriber_ =
164  node->create_subscription<cslam_common_interfaces::msg::PoseGraph>(
165  "/cslam/pose_graph", 100,
166  std::bind(&DecentralizedPGO::pose_graph_callback, this,
167  std::placeholders::_1));
168 
169  visualization_pose_graph_publisher_ =
170  node->create_publisher<cslam_common_interfaces::msg::PoseGraph>(
171  "/cslam/viz/pose_graph", 100);
172 
173  // Optimizer
174  optimizer_state_ = OptimizerState::IDLE;
175  is_waiting_ = false;
176  optimization_count_ = 0;
177 
178  // Initialize the transform broadcaster
179  tf_broadcaster_ = std::make_unique<tf2_ros::TransformBroadcaster>(*node_);
180 
181  if (enable_broadcast_tf_frames_)
182  {
183  tf_broadcaster_timer_ = node_->create_wall_timer(
184  std::chrono::milliseconds(pose_graph_optimization_loop_period_ms_),
185  std::bind(&DecentralizedPGO::broadcast_tf_callback, this));
186  }
187 
188  heartbeat_publisher_ =
189  node_->create_publisher<std_msgs::msg::UInt32>("cslam/heartbeat", 10);
190  heartbeat_timer_ = node_->create_wall_timer(
191  std::chrono::milliseconds((unsigned int)heartbeat_period_sec_ * 1000),
193 
194  reference_frame_per_robot_publisher_ =
195  node_->create_publisher<cslam_common_interfaces::msg::ReferenceFrames>(
196  "cslam/reference_frames", rclcpp::QoS(1).transient_local());
197 
198  origin_robot_id_ = robot_id_;
199 
200  if (enable_logs_)
201  {
202  logger_ = std::make_shared<Logger>(node_, robot_id_, max_nb_robots_, log_folder_);
203  }
204 
205  if (enable_simulated_rendezvous_)
206  {
207  sim_rdv_ = std::make_shared<SimulatedRendezVous>(node_, rendezvous_schedule_file, robot_id_);
208  }
209 
210  RCLCPP_INFO(node_->get_logger(), "Initialization done.");
211 }
212 
214 {
215  for (unsigned int i = 0; i < max_nb_robots_; i++)
216  {
217  received_pose_graphs_[i] = false;
218  }
219  other_robots_graph_and_estimates_.clear();
220  received_pose_graphs_connectivity_.clear();
221 }
222 
224 {
225  bool received_all = true;
226  for (auto id : current_neighbors_ids_.robots.ids)
227  {
228  received_all &= received_pose_graphs_[id];
229  }
230  return received_all;
231 }
232 
234  const cslam_common_interfaces::msg::KeyframeOdom::ConstSharedPtr msg)
235 {
236  gtsam::Pose3 current_estimate = odometry_msg_to_pose3(msg->odom);
237  gtsam::LabeledSymbol symbol(GRAPH_LABEL, ROBOT_LABEL(robot_id_), msg->id);
238 
239  odometry_pose_estimates_->insert(symbol, current_estimate);
240  if (msg->id == 0)
241  {
242  current_pose_estimates_->insert(symbol, current_estimate);
243  }
244 
245  if (latest_local_symbol_ != gtsam::LabeledSymbol())
246  {
247  gtsam::Pose3 odom_diff = latest_local_pose_.inverse() * current_estimate;
248  gtsam::BetweenFactor<gtsam::Pose3> factor(latest_local_symbol_, symbol,
249  odom_diff, default_noise_model_);
250  pose_graph_->push_back(factor);
251  }
252 
253  if (enable_gps_recording_)
254  {
255  gps_data_.insert({msg->id, msg->gps});
256  }
257 
258  // Update latest pose
259  latest_local_pose_ = current_estimate;
260  latest_local_symbol_ = symbol;
261 
262  if (enable_pose_timestamps_recording_)
263  {
264  logger_->log_pose_timestamp(symbol, msg->odom.header.stamp.sec, msg->odom.header.stamp.nanosec);
265  }
266 }
267 
269  const cslam_common_interfaces::msg::IntraRobotLoopClosure::
270  ConstSharedPtr msg)
271 {
272  if (msg->success)
273  {
274  gtsam::Pose3 measurement = transform_msg_to_pose3(msg->transform);
275 
276  gtsam::LabeledSymbol symbol_from(GRAPH_LABEL, ROBOT_LABEL(robot_id_),
277  msg->keyframe0_id);
278  gtsam::LabeledSymbol symbol_to(GRAPH_LABEL, ROBOT_LABEL(robot_id_),
279  msg->keyframe1_id);
280 
281  gtsam::BetweenFactor<gtsam::Pose3> factor =
282  gtsam::BetweenFactor<gtsam::Pose3>(symbol_from, symbol_to, measurement,
283  default_noise_model_);
284 
285  pose_graph_->push_back(factor);
286  RCLCPP_INFO(node_->get_logger(), "New intra-robot loop closure (%d, %d).", msg->keyframe0_id, msg->keyframe1_id);
287  }
288 }
289 
291  const cslam_common_interfaces::msg::InterRobotLoopClosure::
292  ConstSharedPtr msg)
293 {
294  if (msg->success)
295  {
296  gtsam::Pose3 measurement = transform_msg_to_pose3(msg->transform);
297 
298  unsigned char robot0_c = ROBOT_LABEL(msg->robot0_id);
299  gtsam::LabeledSymbol symbol_from(GRAPH_LABEL, robot0_c,
300  msg->robot0_keyframe_id);
301  unsigned char robot1_c = ROBOT_LABEL(msg->robot1_id);
302  gtsam::LabeledSymbol symbol_to(GRAPH_LABEL, robot1_c, msg->robot1_keyframe_id);
303 
304  gtsam::BetweenFactor<gtsam::Pose3> factor =
305  gtsam::BetweenFactor<gtsam::Pose3>(symbol_from, symbol_to, measurement,
306  default_noise_model_);
307 
308  inter_robot_loop_closures_[{std::min(msg->robot0_id, msg->robot1_id),
309  std::max(msg->robot0_id, msg->robot1_id)}]
310  .push_back(factor);
311  if (msg->robot0_id == robot_id_)
312  {
313  connected_robots_.insert(msg->robot1_id);
314  }
315  else if (msg->robot1_id == robot_id_)
316  {
317  connected_robots_.insert(msg->robot0_id);
318  }
319  }
320 }
321 
323  const std_msgs::msg::String::ConstSharedPtr msg)
324 {
325  try{
326  gtsam::writeG2o(*pose_graph_, *current_pose_estimates_, msg->data);
327  } catch (const std::exception &e) {
328  RCLCPP_ERROR(node_->get_logger(), "Error while writing estimates: %s", e.what());
329  }
330 }
331 
333  const cslam_common_interfaces::msg::RobotIdsAndOrigin::ConstSharedPtr msg)
334 {
335  current_neighbors_ids_ = *msg;
336  end_waiting();
337  if (is_optimizer())
338  {
339  optimizer_state_ = OptimizerState::POSEGRAPH_COLLECTION;
340  }
341  else
342  {
343  optimizer_state_ = OptimizerState::IDLE;
344  }
345 }
346 
348 {
349  // Here we could implement a different priority check
350  bool is_optimizer = true;
351  for (unsigned int i = 0; i < current_neighbors_ids_.origins.ids.size(); i++)
352  {
353  if (origin_robot_id_ > current_neighbors_ids_.origins.ids[i])
354  {
355  is_optimizer = false;
356  }
357  else if (origin_robot_id_ == current_neighbors_ids_.origins.ids[i] &&
358  robot_id_ > current_neighbors_ids_.robots.ids[i])
359  {
360  is_optimizer = false;
361  }
362  }
363  if (odometry_pose_estimates_->size() == 0)
364  {
365  is_optimizer = false;
366  }
367  return is_optimizer;
368 }
369 
370 cslam_common_interfaces::msg::PoseGraph DecentralizedPGO::fill_pose_graph_msg(){
371  auto current_robots_ids = current_neighbors_ids_;
372  current_robots_ids.robots.ids.push_back(robot_id_);
373  return fill_pose_graph_msg(current_robots_ids.robots);
374 }
375 
376 cslam_common_interfaces::msg::PoseGraph DecentralizedPGO::fill_pose_graph_msg(const cslam_common_interfaces::msg::RobotIds& msg){
377  cslam_common_interfaces::msg::PoseGraph out_msg;
378  out_msg.robot_id = robot_id_;
379  out_msg.values = gtsam_values_to_msg(odometry_pose_estimates_);
380  auto graph = boost::make_shared<gtsam::NonlinearFactorGraph>();
381  graph->push_back(pose_graph_->begin(), pose_graph_->end());
382 
383  std::set<unsigned int> connected_robots;
384 
385  for (unsigned int i = 0; i < msg.ids.size(); i++)
386  {
387  for (unsigned int j = i + 1; j < msg.ids.size(); j++)
388  {
389  unsigned int min_robot_id = std::min(msg.ids[i], msg.ids[j]);
390  unsigned int max_robot_id = std::max(msg.ids[i], msg.ids[j]);
391  if (inter_robot_loop_closures_[{min_robot_id, max_robot_id}].size() > 0 &&
392  (min_robot_id == robot_id_ || max_robot_id == robot_id_))
393  {
394  connected_robots.insert(min_robot_id);
395  connected_robots.insert(max_robot_id);
396  if (min_robot_id == robot_id_)
397  {
398  graph->push_back(
399  inter_robot_loop_closures_[{min_robot_id, max_robot_id}].begin(),
400  inter_robot_loop_closures_[{min_robot_id, max_robot_id}].end());
401  }
402  }
403  }
404  }
405 
406  out_msg.edges = gtsam_factors_to_msg(graph);
407  for (auto id : connected_robots)
408  {
409  if (id != robot_id_)
410  {
411  out_msg.connected_robots.ids.push_back(id);
412  }
413  }
414 
415  if (enable_gps_recording_) {
416  for (auto gps : gps_data_) {
417  out_msg.gps_values_idx.push_back(gps.first);
418  out_msg.gps_values.push_back(gps.second);
419  }
420  }
421 
422  // If logging, add extra data
423  if (enable_logs_) {
424  logger_->fill_msg(out_msg);
425  }
426 
427  return out_msg;
428 }
429 
431  const cslam_common_interfaces::msg::RobotIds::ConstSharedPtr msg)
432 {
433  auto out_msg = fill_pose_graph_msg(*msg);
434  pose_graph_publisher_->publish(out_msg);
435  tentative_local_pose_at_latest_optimization_ = latest_local_pose_;
436 }
437 
439  const cslam_common_interfaces::msg::PoseGraph::ConstSharedPtr msg)
440 {
441  if (optimizer_state_ == OptimizerState::WAITING_FOR_NEIGHBORS_POSEGRAPHS)
442  {
443  other_robots_graph_and_estimates_.insert(
444  {msg->robot_id,
445  {edges_msg_to_gtsam(msg->edges), values_msg_to_gtsam(msg->values)}});
446  received_pose_graphs_[msg->robot_id] = true;
447  received_pose_graphs_connectivity_.insert(
448  {msg->robot_id, msg->connected_robots.ids});
449 
450  if (enable_logs_){
451  logger_->add_pose_graph_log_info(*msg);
452  }
454  {
455  end_waiting();
456  optimizer_state_ = OptimizerState::START_OPTIMIZATION;
457  if (enable_logs_){
458  logger_->add_pose_graph_log_info(fill_pose_graph_msg());
459  }
460  }
461  }
462 }
463 
464 std::map<unsigned int, bool> DecentralizedPGO::connected_robot_pose_graph()
465 {
466  if (connected_robots_.size() > 0)
467  {
468  std::vector<unsigned int> v(connected_robots_.begin(),
469  connected_robots_.end());
470  received_pose_graphs_connectivity_.insert({robot_id_, v});
471  }
472 
473  std::map<unsigned int, bool> is_robot_connected;
474  is_robot_connected.insert({robot_id_, true});
475  for (auto id : current_neighbors_ids_.robots.ids)
476  {
477  is_robot_connected.insert({id, false});
478  }
479 
480  // Breadth First Search
481  bool *visited = new bool[current_neighbors_ids_.robots.ids.size()];
482  for (unsigned int i = 0; i < current_neighbors_ids_.robots.ids.size(); i++)
483  visited[i] = false;
484 
485  std::list<unsigned int> queue;
486 
487  unsigned int current_id = robot_id_;
488  visited[current_id] = true;
489  queue.push_back(current_id);
490 
491  while (!queue.empty())
492  {
493  current_id = queue.front();
494  queue.pop_front();
495 
496  for (auto id : received_pose_graphs_connectivity_[current_id])
497  {
498  is_robot_connected[id] = true;
499 
500  if (!visited[id])
501  {
502  visited[id] = true;
503  queue.push_back(id);
504  }
505  }
506  }
507  return is_robot_connected;
508 }
509 
511 {
512  get_current_neighbors_publisher_->publish(std_msgs::msg::String());
513 }
514 
516 {
517  if (optimizer_state_ == OptimizerState::IDLE)
518  {
520  }
521  else if (optimizer_state_ == OptimizerState::POSEGRAPH_COLLECTION)
522  {
524  }
525  is_waiting_ = true;
526  start_waiting_time_ = node_->now();
527 }
528 
529 void DecentralizedPGO::end_waiting() { is_waiting_ = false; }
530 
531 bool DecentralizedPGO::is_waiting() { return is_waiting_; }
532 
534 {
535  if ((node_->now() - start_waiting_time_) > max_waiting_time_sec_)
536  {
537  end_waiting();
538  optimizer_state_ = OptimizerState::IDLE;
539  RCLCPP_INFO(node_->get_logger(), "Timeout: (%d)", robot_id_);
540  }
541  return is_waiting();
542 }
543 
545 {
546  if (optimizer_state_ == OptimizerState::IDLE &&
547  odometry_pose_estimates_->size() > 0)
548  {
551  start_waiting();
552  }
553 }
554 
555 std::pair<gtsam::NonlinearFactorGraph::shared_ptr, gtsam::Values::shared_ptr>
557 {
558  // Check connectivity
559  auto is_pose_graph_connected = connected_robot_pose_graph();
560  // Aggregate graphs
561  auto graph = boost::make_shared<gtsam::NonlinearFactorGraph>();
562  auto estimates = boost::make_shared<gtsam::Values>();
563  // Local graph
564  graph->push_back(pose_graph_->begin(), pose_graph_->end());
565  estimates->insert(*odometry_pose_estimates_);
566  tentative_local_pose_at_latest_optimization_ = latest_local_pose_;
567 
568  // Add other robots graphs
569  for (auto id : current_neighbors_ids_.robots.ids)
570  {
571  if (is_pose_graph_connected[id])
572  {
573  estimates->insert(*other_robots_graph_and_estimates_[id].second);
574  }
575  }
576 
577  std::set<std::pair<gtsam::Key, gtsam::Key>> added_loop_closures;
578  // Add local inter-robot loop closures
579  auto included_robots_ids = current_neighbors_ids_;
580  included_robots_ids.robots.ids.push_back(robot_id_);
581  for (unsigned int i = 0; i < included_robots_ids.robots.ids.size(); i++)
582  {
583  for (unsigned int j = i + 1; j < included_robots_ids.robots.ids.size();
584  j++)
585  {
586  if (is_pose_graph_connected[included_robots_ids.robots.ids[i]] &&
587  is_pose_graph_connected[included_robots_ids.robots.ids[j]])
588  {
589  unsigned int min_id = std::min(included_robots_ids.robots.ids[i],
590  included_robots_ids.robots.ids[j]);
591  unsigned int max_id = std::max(included_robots_ids.robots.ids[i],
592  included_robots_ids.robots.ids[j]);
593  for (const auto &factor :
594  inter_robot_loop_closures_[{min_id, max_id}])
595  {
596  if (estimates->exists(factor.key1()) &&
597  estimates->exists(factor.key2()) &&
598  added_loop_closures.count({factor.key1(), factor.key2()}) == 0)
599  {
600  graph->push_back(factor);
601  added_loop_closures.insert({factor.key1(), factor.key2()});
602  }
603  }
604  }
605  }
606  }
607  // Add other robots factors
608  for (auto id : current_neighbors_ids_.robots.ids)
609  {
610 
611  for (const auto &factor_ : *other_robots_graph_and_estimates_[id].first)
612  {
613  auto factor =
614  boost::dynamic_pointer_cast<gtsam::BetweenFactor<gtsam::Pose3>>(
615  factor_);
616  unsigned int robot0_id =
617  ROBOT_ID(gtsam::LabeledSymbol(factor->key1()).label());
618  unsigned int robot1_id =
619  ROBOT_ID(gtsam::LabeledSymbol(factor->key2()).label());
620  if (is_pose_graph_connected[robot0_id] &&
621  is_pose_graph_connected[robot1_id])
622  {
623  if (estimates->exists(factor->key1()) &&
624  estimates->exists(factor->key2()) &&
625  added_loop_closures.count({factor->key1(), factor->key2()}) == 0)
626  {
627  graph->push_back(factor);
628  added_loop_closures.insert({factor->key1(), factor->key2()});
629  }
630  }
631  }
632  }
633  return {graph, estimates};
634 }
635 
637  const cslam_common_interfaces::msg::OptimizationResult::ConstSharedPtr
638  msg)
639 {
640  if (odometry_pose_estimates_->size() > 0 && msg->estimates.size() > 0)
641  {
642  current_pose_estimates_ = values_msg_to_gtsam(msg->estimates);
643  origin_robot_id_ = msg->origin_robot_id;
644  gtsam::LabeledSymbol first_symbol(GRAPH_LABEL, ROBOT_LABEL(robot_id_), 0);
645 
646  gtsam::Pose3 first_pose;
647  if (current_pose_estimates_->exists(first_symbol))
648  {
649  first_pose = current_pose_estimates_->at<gtsam::Pose3>(first_symbol);
650  }
651  update_transform_to_origin(first_pose);
652 
653  if (enable_logs_) {
654  try{
655  logger_->write_logs();
656  }
657  catch (const std::exception &e)
658  {
659  RCLCPP_ERROR(node_->get_logger(), "Writing logs failed: %s", e.what());
660  }
661  }
662  }
663 }
664 
666  const gtsam::Values &estimates)
667 {
668  auto included_robots_ids = current_neighbors_ids_;
669  included_robots_ids.robots.ids.push_back(robot_id_);
670  for (unsigned int i = 0; i < included_robots_ids.robots.ids.size(); i++)
671  {
672  cslam_common_interfaces::msg::OptimizationResult msg;
673  msg.success = true;
674  msg.origin_robot_id = origin_robot_id_;
675  msg.estimates =
676  gtsam_values_to_msg(estimates.filter(gtsam::LabeledSymbol::LabelTest(
677  ROBOT_LABEL(included_robots_ids.robots.ids[i]))));
678  optimized_estimates_publishers_[included_robots_ids.robots.ids[i]]->publish(
679  msg);
680  }
681 }
682 
684 {
685  if (enable_simulated_rendezvous_)
686  {
687  if (!sim_rdv_->is_alive()) {
688  return;
689  }
690  }
691  std_msgs::msg::UInt32 msg;
692  msg.data = origin_robot_id_;
693  heartbeat_publisher_->publish(msg);
694 }
695 
697 {
698  if (visualization_pose_graph_publisher_->get_subscription_count() > 0)
699  {
700  cslam_common_interfaces::msg::PoseGraph out_msg;
701  out_msg.robot_id = robot_id_;
702  out_msg.origin_robot_id = origin_robot_id_;
703  out_msg.values = gtsam_values_to_msg(current_pose_estimates_);
704  auto graph = boost::make_shared<gtsam::NonlinearFactorGraph>();
705  graph->push_back(pose_graph_->begin(), pose_graph_->end());
706 
707  for (unsigned int i = 0; i < max_nb_robots_; i++)
708  {
709  for (unsigned int j = i + 1; j < max_nb_robots_; j++)
710  {
711  unsigned int min_robot_id = std::min(i, j);
712  unsigned int max_robot_id = std::max(i, j);
713  if (inter_robot_loop_closures_[{min_robot_id, max_robot_id}].size() > 0 &&
714  (min_robot_id == robot_id_ || max_robot_id == robot_id_))
715  {
716  if (min_robot_id == robot_id_)
717  {
718  graph->push_back(
719  inter_robot_loop_closures_[{min_robot_id, max_robot_id}].begin(),
720  inter_robot_loop_closures_[{min_robot_id, max_robot_id}].end());
721  }
722  }
723  }
724  }
725 
726  out_msg.edges = gtsam_factors_to_msg(graph);
727  visualization_pose_graph_publisher_->publish(out_msg);
728  }
729 }
730 
731 void DecentralizedPGO::update_transform_to_origin(const gtsam::Pose3 &pose)
732 {
733  rclcpp::Time now = node_->get_clock()->now();
734  origin_to_first_pose_.header.stamp = now;
735  origin_to_first_pose_.header.frame_id = MAP_FRAME_ID(origin_robot_id_);
736  origin_to_first_pose_.child_frame_id = MAP_FRAME_ID(robot_id_);
737 
738  origin_to_first_pose_.transform = gtsam_pose_to_transform_msg(pose);
739 
740  // Update the reference frame
741  // This is the key info for many tasks since it allows conversions from
742  // one robot reference frame to another.
743  if (reference_frame_per_robot_publisher_->get_subscription_count() > 0)
744  {
745  cslam_common_interfaces::msg::ReferenceFrames msg;
746  msg.robot_id = robot_id_;
747  msg.origin_to_local = origin_to_first_pose_;
748  reference_frame_per_robot_publisher_->publish(msg);
749  }
750  // Store for TF
751  local_pose_at_latest_optimization_ = tentative_local_pose_at_latest_optimization_;
752  latest_optimized_pose_ = current_pose_estimates_->at<gtsam::Pose3>(current_pose_estimates_->keys().back());
753 }
754 
756 {
757  // Useful for visualization.
758  // For tasks purposes you might want to use reference_frame_per_robot_ instead
759  // Since it is updated only when a new optimization is performed.
760 
761  // origin to local map
762  rclcpp::Time now = node_->get_clock()->now();
763  origin_to_first_pose_.header.stamp = now;
764  if (origin_to_first_pose_.header.frame_id !=
765  origin_to_first_pose_.child_frame_id)
766  {
767  tf_broadcaster_->sendTransform(origin_to_first_pose_);
768  }
769 
770  // origin to latest optimized pose
771  geometry_msgs::msg::TransformStamped latest_optimized_pose_msg;
772  latest_optimized_pose_msg.header.stamp = now;
773  latest_optimized_pose_msg.header.frame_id = MAP_FRAME_ID(origin_robot_id_);
774  latest_optimized_pose_msg.child_frame_id = LATEST_OPTIMIZED_FRAME_ID(robot_id_);
775  latest_optimized_pose_msg.transform = gtsam_pose_to_transform_msg(
776  latest_optimized_pose_);
777  tf_broadcaster_->sendTransform(latest_optimized_pose_msg);
778 
779  // latest optimized pose to latest local pose (odometry alone)
780  geometry_msgs::msg::TransformStamped current_transform_msg;
781  current_transform_msg.header.stamp = now;
782  current_transform_msg.header.frame_id = LATEST_OPTIMIZED_FRAME_ID(robot_id_);
783  current_transform_msg.child_frame_id = CURRENT_FRAME_ID(robot_id_);
784  gtsam::Pose3 current_pose_diff = local_pose_at_latest_optimization_.inverse() * latest_local_pose_;
785  current_transform_msg.transform = gtsam_pose_to_transform_msg(current_pose_diff);
786  tf_broadcaster_->sendTransform(current_transform_msg);
787 
788  // Publish as message latest estimate (optimized pose + odometry)
789  geometry_msgs::msg::PoseStamped pose_msg;
790  pose_msg.header.stamp = now;
791  pose_msg.header.frame_id = MAP_FRAME_ID(origin_robot_id_);
792  pose_msg.pose = gtsam_pose_to_msg(latest_optimized_pose_ * current_pose_diff);
793  optimized_pose_estimate_publisher_->publish(pose_msg);
794 }
795 
796 gtsam::Values
797 DecentralizedPGO::optimize(const gtsam::NonlinearFactorGraph::shared_ptr &graph,
798  const gtsam::Values::shared_ptr &initial)
799 {
800  gtsam::Values result;
801  if (enable_logs_){
802  logger_->start_timer();
803  }
804  try{
805  gtsam::GncParams<gtsam::LevenbergMarquardtParams> params;
806  gtsam::GncOptimizer<gtsam::GncParams<gtsam::LevenbergMarquardtParams>>
807  optimizer(*graph, *initial, params);
808  result = optimizer.optimize();
809  }
810  catch (const std::exception &e)
811  {
812  RCLCPP_ERROR(node_->get_logger(), "Optimization failed: %s", e.what());
813  result = *initial;
814  }
815  if (enable_logs_){
816  logger_->stop_timer();
817  try{
818  logger_->log_optimized_global_pose_graph(graph, result, robot_id_);
819  }
820  catch (const std::exception &e)
821  {
822  RCLCPP_ERROR(node_->get_logger(), "Logging failed: %s", e.what());
823  result = *initial;
824  }
825  }
826  return result;
827 }
828 
830 {
831  // Build global pose graph
832  aggregate_pose_graph_ = aggregate_pose_graphs();
833 
834  // Add prior
835  // Use first pose of current estimate
836  gtsam::LabeledSymbol first_symbol(GRAPH_LABEL, ROBOT_LABEL(robot_id_), 0);
837 
838  if (!current_pose_estimates_->exists(first_symbol))
839  {
840  return;
841  }
842 
843  aggregate_pose_graph_.first->addPrior(
844  first_symbol, current_pose_estimates_->at<gtsam::Pose3>(first_symbol),
845  default_noise_model_);
846 
847  if (enable_logs_){
848  logger_->log_initial_global_pose_graph(aggregate_pose_graph_.first, aggregate_pose_graph_.second);
849  }
850 
851  // Optimize graph
852  optimization_result_ =
853  std::async(&DecentralizedPGO::optimize, this, aggregate_pose_graph_.first,
854  aggregate_pose_graph_.second);
855  optimizer_state_ = OptimizerState::OPTIMIZATION;
856 }
857 
859 {
860  auto status = optimization_result_.wait_for(std::chrono::milliseconds(0));
861 
862  if (status == std::future_status::ready)
863  {
864  RCLCPP_DEBUG(node_->get_logger(), "Pose Graph Optimization completed.");
865  auto result = optimization_result_.get();
866  optimization_count_++;
867 
868  // Share results
870  optimizer_state_ = OptimizerState::IDLE;
871 
872  // Publish result info for monitoring
873  if (debug_optimization_result_publisher_->get_subscription_count() > 0)
874  {
875  cslam_common_interfaces::msg::OptimizationResult msg;
876  msg.success = true;
877  msg.factors = gtsam_factors_to_msg(aggregate_pose_graph_.first);
878  msg.estimates = gtsam_values_to_msg(result);
879  debug_optimization_result_publisher_->publish(msg);
880  }
881  }
882 }
883 
885 {
886  if (!odometry_pose_estimates_->empty())
887  {
888  if (optimizer_state_ ==
890  {
891  if (current_neighbors_ids_.robots.ids.size() > 0)
892  {
893  for (auto id : current_neighbors_ids_.robots.ids)
894  {
895  auto current_robots_ids = current_neighbors_ids_;
896  current_robots_ids.robots.ids.push_back(robot_id_);
897  get_pose_graph_publishers_[id]->publish(current_robots_ids.robots);
898  }
899  start_waiting();
900  }
901  else
902  {
903  optimizer_state_ = OptimizerState::START_OPTIMIZATION;
904  }
905  }
906  else if (optimizer_state_ == OptimizerState::START_OPTIMIZATION)
907  {
908  // Call optimization
910  }
911  else if (optimizer_state_ == OptimizerState::OPTIMIZATION)
912  {
914  }
915  else if (is_waiting())
916  {
918  }
919  }
920  if (optimizer_state_publisher_->get_subscription_count() > 0)
921  {
923  state_msg.state = optimizer_state_;
924  optimizer_state_publisher_->publish(state_msg);
925  }
926 }
void write_current_estimates_callback(const std_msgs::msg::String::ConstSharedPtr msg)
Prints current estimates.
void pose_graph_callback(const cslam_common_interfaces::msg::PoseGraph::ConstSharedPtr msg)
Receives pose graph.
bool is_optimizer()
Check if the local robot is the optimizer according to the specified priority rule Default priority r...
void check_result_and_finish_optimization()
Check if the optimization is finished.
void optimized_estimates_callback(const cslam_common_interfaces::msg::OptimizationResult::ConstSharedPtr msg)
Receives estimates from the optimizer.
void end_waiting()
End waiting.
void heartbeat_timer_callback()
Publish heartbeat message periodically.
void optimization_callback()
Starts pose graph optimization process every X ms (defined in config)
void inter_robot_loop_closure_callback(const cslam_common_interfaces::msg::InterRobotLoopClosure::ConstSharedPtr msg)
Receives inter-robot loop closures.
void visualization_callback()
Publish heartbeat message periodically.
bool is_waiting()
Check if waiting.
void start_optimization()
Performs pose graph optimization.
cslam_common_interfaces::msg::PoseGraph fill_pose_graph_msg()
Fill a PoseGraph message with the local data.
void optimization_loop_callback()
Step execution of the optimization State Machine.
void get_pose_graph_callback(const cslam_common_interfaces::msg::RobotIds::ConstSharedPtr msg)
Receives request for pose graph.
void odometry_callback(const cslam_common_interfaces::msg::KeyframeOdom::ConstSharedPtr msg)
Receives odometry msg + keyframe id.
void resquest_current_neighbors()
Request to check the current neighbors.
bool check_waiting_timeout()
Check waiting timeout.
bool check_received_pose_graphs()
Check if received all pose graphs.
void intra_robot_loop_closure_callback(const cslam_common_interfaces::msg::IntraRobotLoopClosure::ConstSharedPtr msg)
Receives intra-robot loop closures.
void share_optimized_estimates(const gtsam::Values &estimates)
Shares optimizes estimates with the robots concerned.
std::map< unsigned int, bool > connected_robot_pose_graph()
Breadth First Seach to check connectivity.
std::pair< gtsam::NonlinearFactorGraph::shared_ptr, gtsam::Values::shared_ptr > aggregate_pose_graphs()
Put all connected pose graphs into one.
void reinitialize_received_pose_graphs()
Sets received pose graphs to false.
DecentralizedPGO(std::shared_ptr< rclcpp::Node > &node)
Initialization of parameters and ROS 2 objects.
void update_transform_to_origin(const gtsam::Pose3 &pose)
Update transform to origin.
void start_waiting()
Start waiting.
gtsam::Values optimize(const gtsam::NonlinearFactorGraph::shared_ptr &graph, const gtsam::Values::shared_ptr &initial)
Pose graph optimization function.
void current_neighbors_callback(const cslam_common_interfaces::msg::RobotIdsAndOrigin::ConstSharedPtr msg)
Receives current neighbors.
void broadcast_tf_callback()
Broadcast tf to current origin point.
#define LATEST_OPTIMIZED_FRAME_ID(id)
#define MAP_FRAME_ID(id)
#define CURRENT_FRAME_ID(id)
#define GRAPH_LABEL
Definition: gtsam_utils.h:20
#define ROBOT_ID(ch)
Definition: gtsam_utils.h:22
#define ROBOT_LABEL(id)
Definition: gtsam_utils.h:21
Definition: __init__.py:1
gtsam::NonlinearFactorGraph::shared_ptr edges_msg_to_gtsam(const std::vector< cslam_common_interfaces::msg::PoseGraphEdge > &edges)
Converts from ROS 2 message to GTSAM.
gtsam::Pose3 odometry_msg_to_pose3(const nav_msgs::msg::Odometry &odom_msg)
Converts odometry message to gtsam::Pose3.
std::vector< cslam_common_interfaces::msg::PoseGraphValue > gtsam_values_to_msg(const gtsam::Values::shared_ptr values)
Converts from GTSAM to ROS 2 message.
Definition: gtsam_utils.cpp:54
gtsam::Values::shared_ptr values_msg_to_gtsam(const std::vector< cslam_common_interfaces::msg::PoseGraphValue > &values)
Converts from ROS 2 message to GTSAM.
gtsam::Pose3 transform_msg_to_pose3(const geometry_msgs::msg::Transform &msg)
Converts a transform msg into a gtsam::Pose3.
Definition: gtsam_utils.cpp:96
geometry_msgs::msg::Pose gtsam_pose_to_msg(const gtsam::Pose3 &pose)
Converts from GTSAM to ROS 2 message.
Definition: gtsam_utils.cpp:5
std::vector< cslam_common_interfaces::msg::PoseGraphEdge > gtsam_factors_to_msg(const gtsam::NonlinearFactorGraph::shared_ptr factors)
Converts from GTSAM to ROS 2 message.
Definition: gtsam_utils.cpp:92
geometry_msgs::msg::Transform gtsam_pose_to_transform_msg(const gtsam::Pose3 &pose)
Converts from GTSAM to ROS 2 message.
Definition: gtsam_utils.cpp:19
OptimizerState
State of the Pose Graph Optimization node.
@ START_OPTIMIZATION
@ POSEGRAPH_COLLECTION
@ WAITING_FOR_NEIGHBORS_INFO
@ WAITING_FOR_NEIGHBORS_POSEGRAPHS