ROS1Visualizer.cpp
Go to the documentation of this file.
1 /*
2  * OpenVINS: An Open Platform for Visual-Inertial Research
3  * Copyright (C) 2018-2023 Patrick Geneva
4  * Copyright (C) 2018-2023 Guoquan Huang
5  * Copyright (C) 2018-2023 OpenVINS Contributors
6  * Copyright (C) 2018-2019 Kevin Eckenhoff
7  *
8  * This program is free software: you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation, either version 3 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program. If not, see <https://www.gnu.org/licenses/>.
20  */
21 
22 #include "ROS1Visualizer.h"
23 
24 #include "core/VioManager.h"
26 #include "sim/Simulator.h"
27 #include "state/Propagator.h"
28 #include "state/State.h"
29 #include "state/StateHelper.h"
30 #include "utils/dataset_reader.h"
31 #include "utils/print.h"
32 #include "utils/sensor_data.h"
33 
34 using namespace ov_core;
35 using namespace ov_type;
36 using namespace ov_msckf;
37 
38 ROS1Visualizer::ROS1Visualizer(std::shared_ptr<ros::NodeHandle> nh, std::shared_ptr<VioManager> app, std::shared_ptr<Simulator> sim)
39  : _nh(nh), _app(app), _sim(sim), thread_update_running(false) {
40 
41  // Setup our transform broadcaster
42  mTfBr = std::make_shared<tf::TransformBroadcaster>();
43 
44  // Create image transport
46 
47  // Setup pose and path publisher
48  pub_poseimu = nh->advertise<geometry_msgs::PoseWithCovarianceStamped>("poseimu", 2);
49  PRINT_DEBUG("Publishing: %s\n", pub_poseimu.getTopic().c_str());
50  pub_odomimu = nh->advertise<nav_msgs::Odometry>("odomimu", 2);
51  PRINT_DEBUG("Publishing: %s\n", pub_odomimu.getTopic().c_str());
52  pub_pathimu = nh->advertise<nav_msgs::Path>("pathimu", 2);
53  PRINT_DEBUG("Publishing: %s\n", pub_pathimu.getTopic().c_str());
54 
55  // 3D points publishing
56  pub_points_msckf = nh->advertise<sensor_msgs::PointCloud2>("points_msckf", 2);
57  PRINT_DEBUG("Publishing: %s\n", pub_points_msckf.getTopic().c_str());
58  pub_points_slam = nh->advertise<sensor_msgs::PointCloud2>("points_slam", 2);
59  PRINT_DEBUG("Publishing: %s\n", pub_points_msckf.getTopic().c_str());
60  pub_points_aruco = nh->advertise<sensor_msgs::PointCloud2>("points_aruco", 2);
61  PRINT_DEBUG("Publishing: %s\n", pub_points_aruco.getTopic().c_str());
62  pub_points_sim = nh->advertise<sensor_msgs::PointCloud2>("points_sim", 2);
63  PRINT_DEBUG("Publishing: %s\n", pub_points_sim.getTopic().c_str());
64 
65  // Our tracking image
66  it_pub_tracks = it.advertise("trackhist", 2);
67  PRINT_DEBUG("Publishing: %s\n", it_pub_tracks.getTopic().c_str());
68 
69  // Groundtruth publishers
70  pub_posegt = nh->advertise<geometry_msgs::PoseStamped>("posegt", 2);
71  PRINT_DEBUG("Publishing: %s\n", pub_posegt.getTopic().c_str());
72  pub_pathgt = nh->advertise<nav_msgs::Path>("pathgt", 2);
73  PRINT_DEBUG("Publishing: %s\n", pub_pathgt.getTopic().c_str());
74 
75  // Loop closure publishers
76  pub_loop_pose = nh->advertise<nav_msgs::Odometry>("loop_pose", 2);
77  pub_loop_point = nh->advertise<sensor_msgs::PointCloud>("loop_feats", 2);
78  pub_loop_extrinsic = nh->advertise<nav_msgs::Odometry>("loop_extrinsic", 2);
79  pub_loop_intrinsics = nh->advertise<sensor_msgs::CameraInfo>("loop_intrinsics", 2);
80  it_pub_loop_img_depth = it.advertise("loop_depth", 2);
81  it_pub_loop_img_depth_color = it.advertise("loop_depth_colored", 2);
82 
83  // option to enable publishing of global to IMU transformation
84  nh->param<bool>("publish_global_to_imu_tf", publish_global2imu_tf, true);
85  nh->param<bool>("publish_calibration_tf", publish_calibration_tf, true);
86 
87  // Load groundtruth if we have it and are not doing simulation
88  // NOTE: needs to be a csv ASL format file
89  if (nh->hasParam("path_gt") && _sim == nullptr) {
90  std::string path_to_gt;
91  nh->param<std::string>("path_gt", path_to_gt, "");
92  if (!path_to_gt.empty()) {
93  DatasetReader::load_gt_file(path_to_gt, gt_states);
94  PRINT_DEBUG("gt file path is: %s\n", path_to_gt.c_str());
95  }
96  }
97 
98  // Load if we should save the total state to file
99  // If so, then open the file and create folders as needed
100  nh->param<bool>("save_total_state", save_total_state, false);
101  if (save_total_state) {
102 
103  // files we will open
104  std::string filepath_est, filepath_std, filepath_gt;
105  nh->param<std::string>("filepath_est", filepath_est, "state_estimate.txt");
106  nh->param<std::string>("filepath_std", filepath_std, "state_deviation.txt");
107  nh->param<std::string>("filepath_gt", filepath_gt, "state_groundtruth.txt");
108 
109  // If it exists, then delete it
110  if (boost::filesystem::exists(filepath_est))
111  boost::filesystem::remove(filepath_est);
112  if (boost::filesystem::exists(filepath_std))
113  boost::filesystem::remove(filepath_std);
114 
115  // Create folder path to this location if not exists
116  boost::filesystem::create_directories(boost::filesystem::path(filepath_est.c_str()).parent_path());
117  boost::filesystem::create_directories(boost::filesystem::path(filepath_std.c_str()).parent_path());
118 
119  // Open the files
120  of_state_est.open(filepath_est.c_str());
121  of_state_std.open(filepath_std.c_str());
122  of_state_est << "# timestamp(s) q p v bg ba cam_imu_dt num_cam cam0_k cam0_d cam0_rot cam0_trans ... imu_model dw da tg wtoI atoI etc"
123  << std::endl;
124  of_state_std << "# timestamp(s) q p v bg ba cam_imu_dt num_cam cam0_k cam0_d cam0_rot cam0_trans ... imu_model dw da tg wtoI atoI etc"
125  << std::endl;
126 
127  // Groundtruth if we are simulating
128  if (_sim != nullptr) {
129  if (boost::filesystem::exists(filepath_gt))
130  boost::filesystem::remove(filepath_gt);
131  boost::filesystem::create_directories(boost::filesystem::path(filepath_gt.c_str()).parent_path());
132  of_state_gt.open(filepath_gt.c_str());
133  of_state_gt << "# timestamp(s) q p v bg ba cam_imu_dt num_cam cam0_k cam0_d cam0_rot cam0_trans ... imu_model dw da tg wtoI atoI etc"
134  << std::endl;
135  }
136  }
137 
138  // Start thread for the image publishing
139  if (_app->get_params().use_multi_threading_pubs) {
140  std::thread thread([&] {
141  ros::Rate loop_rate(20);
142  while (ros::ok()) {
143  publish_images();
144  loop_rate.sleep();
145  }
146  });
147  thread.detach();
148  }
149 }
150 
151 void ROS1Visualizer::setup_subscribers(std::shared_ptr<ov_core::YamlParser> parser) {
152 
153  // We need a valid parser
154  assert(parser != nullptr);
155 
156  // Create imu subscriber (handle legacy ros param info)
157  std::string topic_imu;
158  _nh->param<std::string>("topic_imu", topic_imu, "/imu0");
159  parser->parse_external("relative_config_imu", "imu0", "rostopic", topic_imu);
160  sub_imu = _nh->subscribe(topic_imu, 1000, &ROS1Visualizer::callback_inertial, this);
161  PRINT_INFO("subscribing to IMU: %s\n", topic_imu.c_str());
162 
163  // Logic for sync stereo subscriber
164  // https://answers.ros.org/question/96346/subscribe-to-two-image_raws-with-one-function/?answer=96491#post-id-96491
165  if (_app->get_params().state_options.num_cameras == 2) {
166  // Read in the topics
167  std::string cam_topic0, cam_topic1;
168  _nh->param<std::string>("topic_camera" + std::to_string(0), cam_topic0, "/cam" + std::to_string(0) + "/image_raw");
169  _nh->param<std::string>("topic_camera" + std::to_string(1), cam_topic1, "/cam" + std::to_string(1) + "/image_raw");
170  parser->parse_external("relative_config_imucam", "cam" + std::to_string(0), "rostopic", cam_topic0);
171  parser->parse_external("relative_config_imucam", "cam" + std::to_string(1), "rostopic", cam_topic1);
172  // Create sync filter (they have unique pointers internally, so we have to use move logic here...)
173  auto image_sub0 = std::make_shared<message_filters::Subscriber<sensor_msgs::Image>>(*_nh, cam_topic0, 1);
174  auto image_sub1 = std::make_shared<message_filters::Subscriber<sensor_msgs::Image>>(*_nh, cam_topic1, 1);
175  auto sync = std::make_shared<message_filters::Synchronizer<sync_pol>>(sync_pol(10), *image_sub0, *image_sub1);
176  sync->registerCallback(boost::bind(&ROS1Visualizer::callback_stereo, this, _1, _2, 0, 1));
177  // Append to our vector of subscribers
178  sync_cam.push_back(sync);
179  sync_subs_cam.push_back(image_sub0);
180  sync_subs_cam.push_back(image_sub1);
181  PRINT_INFO("subscribing to cam (stereo): %s\n", cam_topic0.c_str());
182  PRINT_INFO("subscribing to cam (stereo): %s\n", cam_topic1.c_str());
183  } else {
184  // Now we should add any non-stereo callbacks here
185  for (int i = 0; i < _app->get_params().state_options.num_cameras; i++) {
186  // read in the topic
187  std::string cam_topic;
188  _nh->param<std::string>("topic_camera" + std::to_string(i), cam_topic, "/cam" + std::to_string(i) + "/image_raw");
189  parser->parse_external("relative_config_imucam", "cam" + std::to_string(i), "rostopic", cam_topic);
190  // create subscriber
191  subs_cam.push_back(_nh->subscribe<sensor_msgs::Image>(cam_topic, 10, boost::bind(&ROS1Visualizer::callback_monocular, this, _1, i)));
192  PRINT_INFO("subscribing to cam (mono): %s\n", cam_topic.c_str());
193  }
194  }
195 }
196 
198 
199  // Return if we have already visualized
200  if (last_visualization_timestamp == _app->get_state()->_timestamp && _app->initialized())
201  return;
202  last_visualization_timestamp = _app->get_state()->_timestamp;
203 
204  // Start timing
205  // boost::posix_time::ptime rT0_1, rT0_2;
206  // rT0_1 = boost::posix_time::microsec_clock::local_time();
207 
208  // publish current image (only if not multi-threaded)
209  if (!_app->get_params().use_multi_threading_pubs)
210  publish_images();
211 
212  // Return if we have not inited
213  if (!_app->initialized())
214  return;
215 
216  // Save the start time of this dataset
217  if (!start_time_set) {
218  rT1 = boost::posix_time::microsec_clock::local_time();
219  start_time_set = true;
220  }
221 
222  // publish state
223  publish_state();
224 
225  // publish points
227 
228  // Publish gt if we have it
230 
231  // Publish keyframe information
233 
234  // Save total state
235  if (save_total_state) {
237  }
238 
239  // Print how much time it took to publish / displaying things
240  // rT0_2 = boost::posix_time::microsec_clock::local_time();
241  // double time_total = (rT0_2 - rT0_1).total_microseconds() * 1e-6;
242  // PRINT_DEBUG(BLUE "[TIME]: %.4f seconds for visualization\n" RESET, time_total);
243 }
244 
245 void ROS1Visualizer::visualize_odometry(double timestamp) {
246 
247  // Return if we have not inited
248  if (!_app->initialized())
249  return;
250 
251  // Get fast propagate state at the desired timestamp
252  std::shared_ptr<State> state = _app->get_state();
253  Eigen::Matrix<double, 13, 1> state_plus = Eigen::Matrix<double, 13, 1>::Zero();
254  Eigen::Matrix<double, 12, 12> cov_plus = Eigen::Matrix<double, 12, 12>::Zero();
255  if (!_app->get_propagator()->fast_state_propagate(state, timestamp, state_plus, cov_plus))
256  return;
257 
258  // // Get the simulated groundtruth so we can evaulate the error in respect to it
259  // // NOTE: we get the true time in the IMU clock frame
260  // if (_sim != nullptr) {
261  // Eigen::Matrix<double, 17, 1> state_gt;
262  // if (_sim->get_state(timestamp, state_gt)) {
263  // // Difference between positions
264  // double dx = state_plus(4, 0) - state_gt(5, 0);
265  // double dy = state_plus(5, 0) - state_gt(6, 0);
266  // double dz = state_plus(6, 0) - state_gt(7, 0);
267  // double err_pos = std::sqrt(dx * dx + dy * dy + dz * dz);
268  // // Quaternion error
269  // Eigen::Matrix<double, 4, 1> quat_gt, quat_st, quat_diff;
270  // quat_gt << state_gt(1, 0), state_gt(2, 0), state_gt(3, 0), state_gt(4, 0);
271  // quat_st << state_plus(0, 0), state_plus(1, 0), state_plus(2, 0), state_plus(3, 0);
272  // quat_diff = quat_multiply(quat_st, Inv(quat_gt));
273  // double err_ori = (180 / M_PI) * 2 * quat_diff.block(0, 0, 3, 1).norm();
274  // // Calculate NEES values
275  // Eigen::Vector3d quat_diff_vec = quat_diff.block(0, 0, 3, 1);
276  // Eigen::Vector3d cov_vec = cov_plus.block(0, 0, 3, 3).inverse() * 2 * quat_diff.block(0, 0, 3, 1);
277  // double ori_nees = 2 * quat_diff_vec.dot(cov_vec);
278  // Eigen::Vector3d errpos = state_plus.block(4, 0, 3, 1) - state_gt.block(5, 0, 3, 1);
279  // double pos_nees = errpos.transpose() * cov_plus.block(3, 3, 3, 3).inverse() * errpos;
280  // PRINT_INFO(REDPURPLE "error to gt => %.3f, %.3f (deg,m) | nees => %.1f, %.1f (ori,pos) \n" RESET, err_ori, err_pos, ori_nees,
281  // pos_nees);
282  // }
283  // }
284 
285  // Publish our odometry message if requested
286  if (pub_odomimu.getNumSubscribers() != 0) {
287 
288  nav_msgs::Odometry odomIinM;
289  odomIinM.header.stamp = ros::Time(timestamp);
290  odomIinM.header.frame_id = "global";
291 
292  // The POSE component (orientation and position)
293  odomIinM.pose.pose.orientation.x = state_plus(0);
294  odomIinM.pose.pose.orientation.y = state_plus(1);
295  odomIinM.pose.pose.orientation.z = state_plus(2);
296  odomIinM.pose.pose.orientation.w = state_plus(3);
297  odomIinM.pose.pose.position.x = state_plus(4);
298  odomIinM.pose.pose.position.y = state_plus(5);
299  odomIinM.pose.pose.position.z = state_plus(6);
300 
301  // The TWIST component (angular and linear velocities)
302  odomIinM.child_frame_id = "imu";
303  odomIinM.twist.twist.linear.x = state_plus(7); // vel in local frame
304  odomIinM.twist.twist.linear.y = state_plus(8); // vel in local frame
305  odomIinM.twist.twist.linear.z = state_plus(9); // vel in local frame
306  odomIinM.twist.twist.angular.x = state_plus(10); // we do not estimate this...
307  odomIinM.twist.twist.angular.y = state_plus(11); // we do not estimate this...
308  odomIinM.twist.twist.angular.z = state_plus(12); // we do not estimate this...
309 
310  // Finally set the covariance in the message (in the order position then orientation as per ros convention)
311  Eigen::Matrix<double, 12, 12> Phi = Eigen::Matrix<double, 12, 12>::Zero();
312  Phi.block(0, 3, 3, 3).setIdentity();
313  Phi.block(3, 0, 3, 3).setIdentity();
314  Phi.block(6, 6, 6, 6).setIdentity();
315  cov_plus = Phi * cov_plus * Phi.transpose();
316  for (int r = 0; r < 6; r++) {
317  for (int c = 0; c < 6; c++) {
318  odomIinM.pose.covariance[6 * r + c] = cov_plus(r, c);
319  }
320  }
321  for (int r = 0; r < 6; r++) {
322  for (int c = 0; c < 6; c++) {
323  odomIinM.twist.covariance[6 * r + c] = cov_plus(r + 6, c + 6);
324  }
325  }
326  pub_odomimu.publish(odomIinM);
327  }
328 
329  // Publish our transform on TF
330  // NOTE: since we use JPL we have an implicit conversion to Hamilton when we publish
331  // NOTE: a rotation from GtoI in JPL has the same xyzw as a ItoG Hamilton rotation
332  auto odom_pose = std::make_shared<ov_type::PoseJPL>();
333  odom_pose->set_value(state_plus.block(0, 0, 7, 1));
334  tf::StampedTransform trans = ROSVisualizerHelper::get_stamped_transform_from_pose(odom_pose, false);
335  trans.frame_id_ = "global";
336  trans.child_frame_id_ = "imu";
337  if (publish_global2imu_tf) {
338  mTfBr->sendTransform(trans);
339  }
340 
341  // Loop through each camera calibration and publish it
342  for (const auto &calib : state->_calib_IMUtoCAM) {
343  tf::StampedTransform trans_calib = ROSVisualizerHelper::get_stamped_transform_from_pose(calib.second, true);
344  trans_calib.frame_id_ = "imu";
345  trans_calib.child_frame_id_ = "cam" + std::to_string(calib.first);
347  mTfBr->sendTransform(trans_calib);
348  }
349  }
350 }
351 
353 
354  // Final time offset value
355  if (_app->get_state()->_options.do_calib_camera_timeoffset) {
356  PRINT_INFO(REDPURPLE "camera-imu timeoffset = %.5f\n\n" RESET, _app->get_state()->_calib_dt_CAMtoIMU->value()(0));
357  }
358 
359  // Final camera intrinsics
360  if (_app->get_state()->_options.do_calib_camera_intrinsics) {
361  for (int i = 0; i < _app->get_state()->_options.num_cameras; i++) {
362  std::shared_ptr<Vec> calib = _app->get_state()->_cam_intrinsics.at(i);
363  PRINT_INFO(REDPURPLE "cam%d intrinsics:\n" RESET, (int)i);
364  PRINT_INFO(REDPURPLE "%.3f,%.3f,%.3f,%.3f\n" RESET, calib->value()(0), calib->value()(1), calib->value()(2), calib->value()(3));
365  PRINT_INFO(REDPURPLE "%.5f,%.5f,%.5f,%.5f\n\n" RESET, calib->value()(4), calib->value()(5), calib->value()(6), calib->value()(7));
366  }
367  }
368 
369  // Final camera extrinsics
370  if (_app->get_state()->_options.do_calib_camera_pose) {
371  for (int i = 0; i < _app->get_state()->_options.num_cameras; i++) {
372  std::shared_ptr<PoseJPL> calib = _app->get_state()->_calib_IMUtoCAM.at(i);
373  Eigen::Matrix4d T_CtoI = Eigen::Matrix4d::Identity();
374  T_CtoI.block(0, 0, 3, 3) = quat_2_Rot(calib->quat()).transpose();
375  T_CtoI.block(0, 3, 3, 1) = -T_CtoI.block(0, 0, 3, 3) * calib->pos();
376  PRINT_INFO(REDPURPLE "T_C%dtoI:\n" RESET, i);
377  PRINT_INFO(REDPURPLE "%.3f,%.3f,%.3f,%.3f,\n" RESET, T_CtoI(0, 0), T_CtoI(0, 1), T_CtoI(0, 2), T_CtoI(0, 3));
378  PRINT_INFO(REDPURPLE "%.3f,%.3f,%.3f,%.3f,\n" RESET, T_CtoI(1, 0), T_CtoI(1, 1), T_CtoI(1, 2), T_CtoI(1, 3));
379  PRINT_INFO(REDPURPLE "%.3f,%.3f,%.3f,%.3f,\n" RESET, T_CtoI(2, 0), T_CtoI(2, 1), T_CtoI(2, 2), T_CtoI(2, 3));
380  PRINT_INFO(REDPURPLE "%.3f,%.3f,%.3f,%.3f\n\n" RESET, T_CtoI(3, 0), T_CtoI(3, 1), T_CtoI(3, 2), T_CtoI(3, 3));
381  }
382  }
383 
384  // IMU intrinsics
385  if (_app->get_state()->_options.do_calib_imu_intrinsics) {
386  Eigen::Matrix3d Dw = State::Dm(_app->get_state()->_options.imu_model, _app->get_state()->_calib_imu_dw->value());
387  Eigen::Matrix3d Da = State::Dm(_app->get_state()->_options.imu_model, _app->get_state()->_calib_imu_da->value());
388  Eigen::Matrix3d Tw = Dw.colPivHouseholderQr().solve(Eigen::Matrix3d::Identity());
389  Eigen::Matrix3d Ta = Da.colPivHouseholderQr().solve(Eigen::Matrix3d::Identity());
390  Eigen::Matrix3d R_IMUtoACC = _app->get_state()->_calib_imu_ACCtoIMU->Rot().transpose();
391  Eigen::Matrix3d R_IMUtoGYRO = _app->get_state()->_calib_imu_GYROtoIMU->Rot().transpose();
392  PRINT_INFO(REDPURPLE "Tw:\n" RESET);
393  PRINT_INFO(REDPURPLE "%.5f,%.5f,%.5f,\n" RESET, Tw(0, 0), Tw(0, 1), Tw(0, 2));
394  PRINT_INFO(REDPURPLE "%.5f,%.5f,%.5f,\n" RESET, Tw(1, 0), Tw(1, 1), Tw(1, 2));
395  PRINT_INFO(REDPURPLE "%.5f,%.5f,%.5f\n\n" RESET, Tw(2, 0), Tw(2, 1), Tw(2, 2));
396  PRINT_INFO(REDPURPLE "Ta:\n" RESET);
397  PRINT_INFO(REDPURPLE "%.5f,%.5f,%.5f,\n" RESET, Ta(0, 0), Ta(0, 1), Ta(0, 2));
398  PRINT_INFO(REDPURPLE "%.5f,%.5f,%.5f,\n" RESET, Ta(1, 0), Ta(1, 1), Ta(1, 2));
399  PRINT_INFO(REDPURPLE "%.5f,%.5f,%.5f\n\n" RESET, Ta(2, 0), Ta(2, 1), Ta(2, 2));
400  PRINT_INFO(REDPURPLE "R_IMUtoACC:\n" RESET);
401  PRINT_INFO(REDPURPLE "%.5f,%.5f,%.5f,\n" RESET, R_IMUtoACC(0, 0), R_IMUtoACC(0, 1), R_IMUtoACC(0, 2));
402  PRINT_INFO(REDPURPLE "%.5f,%.5f,%.5f,\n" RESET, R_IMUtoACC(1, 0), R_IMUtoACC(1, 1), R_IMUtoACC(1, 2));
403  PRINT_INFO(REDPURPLE "%.5f,%.5f,%.5f\n\n" RESET, R_IMUtoACC(2, 0), R_IMUtoACC(2, 1), R_IMUtoACC(2, 2));
404  PRINT_INFO(REDPURPLE "R_IMUtoGYRO:\n" RESET);
405  PRINT_INFO(REDPURPLE "%.5f,%.5f,%.5f,\n" RESET, R_IMUtoGYRO(0, 0), R_IMUtoGYRO(0, 1), R_IMUtoGYRO(0, 2));
406  PRINT_INFO(REDPURPLE "%.5f,%.5f,%.5f,\n" RESET, R_IMUtoGYRO(1, 0), R_IMUtoGYRO(1, 1), R_IMUtoGYRO(1, 2));
407  PRINT_INFO(REDPURPLE "%.5f,%.5f,%.5f\n\n" RESET, R_IMUtoGYRO(2, 0), R_IMUtoGYRO(2, 1), R_IMUtoGYRO(2, 2));
408  }
409 
410  // IMU intrinsics gravity sensitivity
411  if (_app->get_state()->_options.do_calib_imu_g_sensitivity) {
412  Eigen::Matrix3d Tg = State::Tg(_app->get_state()->_calib_imu_tg->value());
413  PRINT_INFO(REDPURPLE "Tg:\n" RESET);
414  PRINT_INFO(REDPURPLE "%.6f,%.6f,%.6f,\n" RESET, Tg(0, 0), Tg(0, 1), Tg(0, 2));
415  PRINT_INFO(REDPURPLE "%.6f,%.6f,%.6f,\n" RESET, Tg(1, 0), Tg(1, 1), Tg(1, 2));
416  PRINT_INFO(REDPURPLE "%.6f,%.6f,%.6f\n\n" RESET, Tg(2, 0), Tg(2, 1), Tg(2, 2));
417  }
418 
419  // Publish RMSE if we have it
420  if (!gt_states.empty()) {
421  PRINT_INFO(REDPURPLE "RMSE: %.3f (deg) orientation\n" RESET, std::sqrt(summed_mse_ori / summed_number));
422  PRINT_INFO(REDPURPLE "RMSE: %.3f (m) position\n\n" RESET, std::sqrt(summed_mse_pos / summed_number));
423  }
424 
425  // Publish RMSE and NEES if doing simulation
426  if (_sim != nullptr) {
427  PRINT_INFO(REDPURPLE "RMSE: %.3f (deg) orientation\n" RESET, std::sqrt(summed_mse_ori / summed_number));
428  PRINT_INFO(REDPURPLE "RMSE: %.3f (m) position\n\n" RESET, std::sqrt(summed_mse_pos / summed_number));
429  PRINT_INFO(REDPURPLE "NEES: %.3f (deg) orientation\n" RESET, summed_nees_ori / summed_number);
430  PRINT_INFO(REDPURPLE "NEES: %.3f (m) position\n\n" RESET, summed_nees_pos / summed_number);
431  }
432 
433  // Print the total time
434  rT2 = boost::posix_time::microsec_clock::local_time();
435  PRINT_INFO(REDPURPLE "TIME: %.3f seconds\n\n" RESET, (rT2 - rT1).total_microseconds() * 1e-6);
436 }
437 
438 void ROS1Visualizer::callback_inertial(const sensor_msgs::Imu::ConstPtr &msg) {
439 
440  // convert into correct format
441  ov_core::ImuData message;
442  message.timestamp = msg->header.stamp.toSec();
443  message.wm << msg->angular_velocity.x, msg->angular_velocity.y, msg->angular_velocity.z;
444  message.am << msg->linear_acceleration.x, msg->linear_acceleration.y, msg->linear_acceleration.z;
445 
446  // send it to our VIO system
447  _app->feed_measurement_imu(message);
448  visualize_odometry(message.timestamp);
449 
450  // If the processing queue is currently active / running just return so we can keep getting measurements
451  // Otherwise create a second thread to do our update in an async manor
452  // The visualization of the state, images, and features will be synchronous with the update!
454  return;
455  thread_update_running = true;
456  std::thread thread([&] {
457  // Lock on the queue (prevents new images from appending)
458  std::lock_guard<std::mutex> lck(camera_queue_mtx);
459 
460  // Count how many unique image streams
461  std::map<int, bool> unique_cam_ids;
462  for (const auto &cam_msg : camera_queue) {
463  unique_cam_ids[cam_msg.sensor_ids.at(0)] = true;
464  }
465 
466  // If we do not have enough unique cameras then we need to wait
467  // We should wait till we have one of each camera to ensure we propagate in the correct order
468  auto params = _app->get_params();
469  size_t num_unique_cameras = (params.state_options.num_cameras == 2) ? 1 : params.state_options.num_cameras;
470  if (unique_cam_ids.size() == num_unique_cameras) {
471 
472  // Loop through our queue and see if we are able to process any of our camera measurements
473  // We are able to process if we have at least one IMU measurement greater than the camera time
474  double timestamp_imu_inC = message.timestamp - _app->get_state()->_calib_dt_CAMtoIMU->value()(0);
475  while (!camera_queue.empty() && camera_queue.at(0).timestamp < timestamp_imu_inC) {
476  auto rT0_1 = boost::posix_time::microsec_clock::local_time();
477  double update_dt = 100.0 * (timestamp_imu_inC - camera_queue.at(0).timestamp);
478  _app->feed_measurement_camera(camera_queue.at(0));
479  visualize();
480  camera_queue.pop_front();
481  auto rT0_2 = boost::posix_time::microsec_clock::local_time();
482  double time_total = (rT0_2 - rT0_1).total_microseconds() * 1e-6;
483  PRINT_INFO(BLUE "[TIME]: %.4f seconds total (%.1f hz, %.2f ms behind)\n" RESET, time_total, 1.0 / time_total, update_dt);
484  }
485  }
486  thread_update_running = false;
487  });
488 
489  // If we are single threaded, then run single threaded
490  // Otherwise detach this thread so it runs in the background!
491  if (!_app->get_params().use_multi_threading_subs) {
492  thread.join();
493  } else {
494  thread.detach();
495  }
496 }
497 
498 void ROS1Visualizer::callback_monocular(const sensor_msgs::ImageConstPtr &msg0, int cam_id0) {
499 
500  // Check if we should drop this image
501  double timestamp = msg0->header.stamp.toSec();
502  double time_delta = 1.0 / _app->get_params().track_frequency;
503  if (camera_last_timestamp.find(cam_id0) != camera_last_timestamp.end() && timestamp < camera_last_timestamp.at(cam_id0) + time_delta) {
504  return;
505  }
506  camera_last_timestamp[cam_id0] = timestamp;
507 
508  // Get the image
510  try {
512  } catch (cv_bridge::Exception &e) {
513  PRINT_ERROR("cv_bridge exception: %s", e.what());
514  return;
515  }
516 
517  // Create the measurement
518  ov_core::CameraData message;
519  message.timestamp = cv_ptr->header.stamp.toSec();
520  message.sensor_ids.push_back(cam_id0);
521  message.images.push_back(cv_ptr->image.clone());
522 
523  // Load the mask if we are using it, else it is empty
524  // TODO: in the future we should get this from external pixel segmentation
525  if (_app->get_params().use_mask) {
526  message.masks.push_back(_app->get_params().masks.at(cam_id0));
527  } else {
528  message.masks.push_back(cv::Mat::zeros(cv_ptr->image.rows, cv_ptr->image.cols, CV_8UC1));
529  }
530 
531  // append it to our queue of images
532  std::lock_guard<std::mutex> lck(camera_queue_mtx);
533  camera_queue.push_back(message);
534  std::sort(camera_queue.begin(), camera_queue.end());
535 }
536 
537 void ROS1Visualizer::callback_stereo(const sensor_msgs::ImageConstPtr &msg0, const sensor_msgs::ImageConstPtr &msg1, int cam_id0,
538  int cam_id1) {
539 
540  // Check if we should drop this image
541  double timestamp = msg0->header.stamp.toSec();
542  double time_delta = 1.0 / _app->get_params().track_frequency;
543  if (camera_last_timestamp.find(cam_id0) != camera_last_timestamp.end() && timestamp < camera_last_timestamp.at(cam_id0) + time_delta) {
544  return;
545  }
546  camera_last_timestamp[cam_id0] = timestamp;
547 
548  // Get the image
550  try {
552  } catch (cv_bridge::Exception &e) {
553  PRINT_ERROR("cv_bridge exception: %s\n", e.what());
554  return;
555  }
556 
557  // Get the image
559  try {
561  } catch (cv_bridge::Exception &e) {
562  PRINT_ERROR("cv_bridge exception: %s\n", e.what());
563  return;
564  }
565 
566  // Create the measurement
567  ov_core::CameraData message;
568  message.timestamp = cv_ptr0->header.stamp.toSec();
569  message.sensor_ids.push_back(cam_id0);
570  message.sensor_ids.push_back(cam_id1);
571  message.images.push_back(cv_ptr0->image.clone());
572  message.images.push_back(cv_ptr1->image.clone());
573 
574  // Load the mask if we are using it, else it is empty
575  // TODO: in the future we should get this from external pixel segmentation
576  if (_app->get_params().use_mask) {
577  message.masks.push_back(_app->get_params().masks.at(cam_id0));
578  message.masks.push_back(_app->get_params().masks.at(cam_id1));
579  } else {
580  // message.masks.push_back(cv::Mat(cv_ptr0->image.rows, cv_ptr0->image.cols, CV_8UC1, cv::Scalar(255)));
581  message.masks.push_back(cv::Mat::zeros(cv_ptr0->image.rows, cv_ptr0->image.cols, CV_8UC1));
582  message.masks.push_back(cv::Mat::zeros(cv_ptr1->image.rows, cv_ptr1->image.cols, CV_8UC1));
583  }
584 
585  // append it to our queue of images
586  std::lock_guard<std::mutex> lck(camera_queue_mtx);
587  camera_queue.push_back(message);
588  std::sort(camera_queue.begin(), camera_queue.end());
589 }
590 
592 
593  // Get the current state
594  std::shared_ptr<State> state = _app->get_state();
595 
596  // We want to publish in the IMU clock frame
597  // The timestamp in the state will be the last camera time
598  double t_ItoC = state->_calib_dt_CAMtoIMU->value()(0);
599  double timestamp_inI = state->_timestamp + t_ItoC;
600 
601  // Create pose of IMU (note we use the bag time)
602  geometry_msgs::PoseWithCovarianceStamped poseIinM;
603  poseIinM.header.stamp = ros::Time(timestamp_inI);
604  poseIinM.header.seq = poses_seq_imu;
605  poseIinM.header.frame_id = "global";
606  poseIinM.pose.pose.orientation.x = state->_imu->quat()(0);
607  poseIinM.pose.pose.orientation.y = state->_imu->quat()(1);
608  poseIinM.pose.pose.orientation.z = state->_imu->quat()(2);
609  poseIinM.pose.pose.orientation.w = state->_imu->quat()(3);
610  poseIinM.pose.pose.position.x = state->_imu->pos()(0);
611  poseIinM.pose.pose.position.y = state->_imu->pos()(1);
612  poseIinM.pose.pose.position.z = state->_imu->pos()(2);
613 
614  // Finally set the covariance in the message (in the order position then orientation as per ros convention)
615  std::vector<std::shared_ptr<Type>> statevars;
616  statevars.push_back(state->_imu->pose()->p());
617  statevars.push_back(state->_imu->pose()->q());
618  Eigen::Matrix<double, 6, 6> covariance_posori = StateHelper::get_marginal_covariance(_app->get_state(), statevars);
619  for (int r = 0; r < 6; r++) {
620  for (int c = 0; c < 6; c++) {
621  poseIinM.pose.covariance[6 * r + c] = covariance_posori(r, c);
622  }
623  }
624  pub_poseimu.publish(poseIinM);
625 
626  //=========================================================
627  //=========================================================
628 
629  // Append to our pose vector
630  geometry_msgs::PoseStamped posetemp;
631  posetemp.header = poseIinM.header;
632  posetemp.pose = poseIinM.pose.pose;
633  poses_imu.push_back(posetemp);
634 
635  // Create our path (imu)
636  // NOTE: We downsample the number of poses as needed to prevent rviz crashes
637  // NOTE: https://github.com/ros-visualization/rviz/issues/1107
638  nav_msgs::Path arrIMU;
639  arrIMU.header.stamp = ros::Time::now();
640  arrIMU.header.seq = poses_seq_imu;
641  arrIMU.header.frame_id = "global";
642  for (size_t i = 0; i < poses_imu.size(); i += std::floor((double)poses_imu.size() / 16384.0) + 1) {
643  arrIMU.poses.push_back(poses_imu.at(i));
644  }
645  pub_pathimu.publish(arrIMU);
646 
647  // Move them forward in time
648  poses_seq_imu++;
649 }
650 
652 
653  // Return if we have already visualized
654  if (_app->get_state() == nullptr)
655  return;
656  if (last_visualization_timestamp_image == _app->get_state()->_timestamp && _app->initialized())
657  return;
658  last_visualization_timestamp_image = _app->get_state()->_timestamp;
659 
660  // Check if we have subscribers
661  if (it_pub_tracks.getNumSubscribers() == 0)
662  return;
663 
664  // Get our image of history tracks
665  cv::Mat img_history = _app->get_historical_viz_image();
666  if (img_history.empty())
667  return;
668 
669  // Create our message
670  std_msgs::Header header;
671  header.stamp = ros::Time::now();
672  header.frame_id = "cam0";
673  sensor_msgs::ImagePtr exl_msg = cv_bridge::CvImage(header, "bgr8", img_history).toImageMsg();
674 
675  // Publish
676  it_pub_tracks.publish(exl_msg);
677 }
678 
680 
681  // Check if we have subscribers
684  return;
685 
686  // Get our good MSCKF features
687  std::vector<Eigen::Vector3d> feats_msckf = _app->get_good_features_MSCKF();
688  sensor_msgs::PointCloud2 cloud = ROSVisualizerHelper::get_ros_pointcloud(feats_msckf);
689  pub_points_msckf.publish(cloud);
690 
691  // Get our good SLAM features
692  std::vector<Eigen::Vector3d> feats_slam = _app->get_features_SLAM();
693  sensor_msgs::PointCloud2 cloud_SLAM = ROSVisualizerHelper::get_ros_pointcloud(feats_slam);
694  pub_points_slam.publish(cloud_SLAM);
695 
696  // Get our good ARUCO features
697  std::vector<Eigen::Vector3d> feats_aruco = _app->get_features_ARUCO();
698  sensor_msgs::PointCloud2 cloud_ARUCO = ROSVisualizerHelper::get_ros_pointcloud(feats_aruco);
699  pub_points_aruco.publish(cloud_ARUCO);
700 
701  // Skip the rest of we are not doing simulation
702  if (_sim == nullptr)
703  return;
704 
705  // Get our good SIMULATION features
706  std::vector<Eigen::Vector3d> feats_sim = _sim->get_map_vec();
707  sensor_msgs::PointCloud2 cloud_SIM = ROSVisualizerHelper::get_ros_pointcloud(feats_sim);
708  pub_points_sim.publish(cloud_SIM);
709 }
710 
712 
713  // Our groundtruth state
714  Eigen::Matrix<double, 17, 1> state_gt;
715 
716  // We want to publish in the IMU clock frame
717  // The timestamp in the state will be the last camera time
718  double t_ItoC = _app->get_state()->_calib_dt_CAMtoIMU->value()(0);
719  double timestamp_inI = _app->get_state()->_timestamp + t_ItoC;
720 
721  // Check that we have the timestamp in our GT file [time(sec),q_GtoI,p_IinG,v_IinG,b_gyro,b_accel]
722  if (_sim == nullptr && (gt_states.empty() || !DatasetReader::get_gt_state(timestamp_inI, state_gt, gt_states))) {
723  return;
724  }
725 
726  // Get the simulated groundtruth
727  // NOTE: we get the true time in the IMU clock frame
728  if (_sim != nullptr) {
729  timestamp_inI = _app->get_state()->_timestamp + _sim->get_true_parameters().calib_camimu_dt;
730  if (!_sim->get_state(timestamp_inI, state_gt))
731  return;
732  }
733 
734  // Get the GT and system state state
735  Eigen::Matrix<double, 16, 1> state_ekf = _app->get_state()->_imu->value();
736 
737  // Create pose of IMU
738  geometry_msgs::PoseStamped poseIinM;
739  poseIinM.header.stamp = ros::Time(timestamp_inI);
740  poseIinM.header.seq = poses_seq_gt;
741  poseIinM.header.frame_id = "global";
742  poseIinM.pose.orientation.x = state_gt(1, 0);
743  poseIinM.pose.orientation.y = state_gt(2, 0);
744  poseIinM.pose.orientation.z = state_gt(3, 0);
745  poseIinM.pose.orientation.w = state_gt(4, 0);
746  poseIinM.pose.position.x = state_gt(5, 0);
747  poseIinM.pose.position.y = state_gt(6, 0);
748  poseIinM.pose.position.z = state_gt(7, 0);
749  pub_posegt.publish(poseIinM);
750 
751  // Append to our pose vector
752  poses_gt.push_back(poseIinM);
753 
754  // Create our path (imu)
755  // NOTE: We downsample the number of poses as needed to prevent rviz crashes
756  // NOTE: https://github.com/ros-visualization/rviz/issues/1107
757  nav_msgs::Path arrIMU;
758  arrIMU.header.stamp = ros::Time::now();
759  arrIMU.header.seq = poses_seq_gt;
760  arrIMU.header.frame_id = "global";
761  for (size_t i = 0; i < poses_gt.size(); i += std::floor((double)poses_gt.size() / 16384.0) + 1) {
762  arrIMU.poses.push_back(poses_gt.at(i));
763  }
764  pub_pathgt.publish(arrIMU);
765 
766  // Move them forward in time
767  poses_seq_gt++;
768 
769  // Publish our transform on TF
770  tf::StampedTransform trans;
771  trans.stamp_ = ros::Time::now();
772  trans.frame_id_ = "global";
773  trans.child_frame_id_ = "truth";
774  tf::Quaternion quat(state_gt(1, 0), state_gt(2, 0), state_gt(3, 0), state_gt(4, 0));
775  trans.setRotation(quat);
776  tf::Vector3 orig(state_gt(5, 0), state_gt(6, 0), state_gt(7, 0));
777  trans.setOrigin(orig);
778  if (publish_global2imu_tf) {
779  mTfBr->sendTransform(trans);
780  }
781 
782  //==========================================================================
783  //==========================================================================
784 
785  // Difference between positions
786  double dx = state_ekf(4, 0) - state_gt(5, 0);
787  double dy = state_ekf(5, 0) - state_gt(6, 0);
788  double dz = state_ekf(6, 0) - state_gt(7, 0);
789  double err_pos = std::sqrt(dx * dx + dy * dy + dz * dz);
790 
791  // Quaternion error
792  Eigen::Matrix<double, 4, 1> quat_gt, quat_st, quat_diff;
793  quat_gt << state_gt(1, 0), state_gt(2, 0), state_gt(3, 0), state_gt(4, 0);
794  quat_st << state_ekf(0, 0), state_ekf(1, 0), state_ekf(2, 0), state_ekf(3, 0);
795  quat_diff = quat_multiply(quat_st, Inv(quat_gt));
796  double err_ori = (180 / M_PI) * 2 * quat_diff.block(0, 0, 3, 1).norm();
797 
798  //==========================================================================
799  //==========================================================================
800 
801  // Get covariance of pose
802  std::vector<std::shared_ptr<Type>> statevars;
803  statevars.push_back(_app->get_state()->_imu->q());
804  statevars.push_back(_app->get_state()->_imu->p());
805  Eigen::Matrix<double, 6, 6> covariance = StateHelper::get_marginal_covariance(_app->get_state(), statevars);
806 
807  // Calculate NEES values
808  // NOTE: need to manually multiply things out to make static asserts work
809  // NOTE: https://github.com/rpng/open_vins/pull/226
810  // NOTE: https://github.com/rpng/open_vins/issues/236
811  // NOTE: https://gitlab.com/libeigen/eigen/-/issues/1664
812  Eigen::Vector3d quat_diff_vec = quat_diff.block(0, 0, 3, 1);
813  Eigen::Vector3d cov_vec = covariance.block(0, 0, 3, 3).inverse() * 2 * quat_diff.block(0, 0, 3, 1);
814  double ori_nees = 2 * quat_diff_vec.dot(cov_vec);
815  Eigen::Vector3d errpos = state_ekf.block(4, 0, 3, 1) - state_gt.block(5, 0, 3, 1);
816  double pos_nees = errpos.transpose() * covariance.block(3, 3, 3, 3).inverse() * errpos;
817 
818  //==========================================================================
819  //==========================================================================
820 
821  // Update our average variables
822  if (!std::isnan(ori_nees) && !std::isnan(pos_nees)) {
823  summed_mse_ori += err_ori * err_ori;
824  summed_mse_pos += err_pos * err_pos;
825  summed_nees_ori += ori_nees;
826  summed_nees_pos += pos_nees;
827  summed_number++;
828  }
829 
830  // Nice display for the user
831  PRINT_INFO(REDPURPLE "error to gt => %.3f, %.3f (deg,m) | rmse => %.3f, %.3f (deg,m) | called %d times\n" RESET, err_ori, err_pos,
832  std::sqrt(summed_mse_ori / summed_number), std::sqrt(summed_mse_pos / summed_number), (int)summed_number);
833  PRINT_INFO(REDPURPLE "nees => %.1f, %.1f (ori,pos) | avg nees = %.1f, %.1f (ori,pos)\n" RESET, ori_nees, pos_nees,
835 
836  //==========================================================================
837  //==========================================================================
838 }
839 
841 
842  // Get the current tracks in this frame
843  double active_tracks_time1 = -1;
844  double active_tracks_time2 = -1;
845  std::unordered_map<size_t, Eigen::Vector3d> active_tracks_posinG;
846  std::unordered_map<size_t, Eigen::Vector3d> active_tracks_uvd;
847  cv::Mat active_cam0_image;
848  _app->get_active_tracks(active_tracks_time1, active_tracks_posinG, active_tracks_uvd);
849  _app->get_active_image(active_tracks_time2, active_cam0_image);
850  if (active_tracks_time1 == -1)
851  return;
852  if (_app->get_state()->_clones_IMU.find(active_tracks_time1) == _app->get_state()->_clones_IMU.end())
853  return;
854  Eigen::Vector4d quat = _app->get_state()->_clones_IMU.at(active_tracks_time1)->quat();
855  Eigen::Vector3d pos = _app->get_state()->_clones_IMU.at(active_tracks_time1)->pos();
856  if (active_tracks_time1 != active_tracks_time2)
857  return;
858 
859  // Default header
860  std_msgs::Header header;
861  header.stamp = ros::Time(active_tracks_time1);
862 
863  //======================================================
864  // Check if we have subscribers for the pose odometry, camera intrinsics, or extrinsics
867 
868  // PUBLISH HISTORICAL POSE ESTIMATE
869  nav_msgs::Odometry odometry_pose;
870  odometry_pose.header = header;
871  odometry_pose.header.frame_id = "global";
872  odometry_pose.pose.pose.position.x = pos(0);
873  odometry_pose.pose.pose.position.y = pos(1);
874  odometry_pose.pose.pose.position.z = pos(2);
875  odometry_pose.pose.pose.orientation.x = quat(0);
876  odometry_pose.pose.pose.orientation.y = quat(1);
877  odometry_pose.pose.pose.orientation.z = quat(2);
878  odometry_pose.pose.pose.orientation.w = quat(3);
879  pub_loop_pose.publish(odometry_pose);
880 
881  // PUBLISH IMU TO CAMERA0 EXTRINSIC
882  // need to flip the transform to the IMU frame
883  Eigen::Vector4d q_ItoC = _app->get_state()->_calib_IMUtoCAM.at(0)->quat();
884  Eigen::Vector3d p_CinI = -_app->get_state()->_calib_IMUtoCAM.at(0)->Rot().transpose() * _app->get_state()->_calib_IMUtoCAM.at(0)->pos();
885  nav_msgs::Odometry odometry_calib;
886  odometry_calib.header = header;
887  odometry_calib.header.frame_id = "imu";
888  odometry_calib.pose.pose.position.x = p_CinI(0);
889  odometry_calib.pose.pose.position.y = p_CinI(1);
890  odometry_calib.pose.pose.position.z = p_CinI(2);
891  odometry_calib.pose.pose.orientation.x = q_ItoC(0);
892  odometry_calib.pose.pose.orientation.y = q_ItoC(1);
893  odometry_calib.pose.pose.orientation.z = q_ItoC(2);
894  odometry_calib.pose.pose.orientation.w = q_ItoC(3);
895  pub_loop_extrinsic.publish(odometry_calib);
896 
897  // PUBLISH CAMERA0 INTRINSICS
898  bool is_fisheye = (std::dynamic_pointer_cast<ov_core::CamEqui>(_app->get_params().camera_intrinsics.at(0)) != nullptr);
899  sensor_msgs::CameraInfo cameraparams;
900  cameraparams.header = header;
901  cameraparams.header.frame_id = "cam0";
902  cameraparams.distortion_model = is_fisheye ? "equidistant" : "plumb_bob";
903  Eigen::VectorXd cparams = _app->get_state()->_cam_intrinsics.at(0)->value();
904  cameraparams.D = {cparams(4), cparams(5), cparams(6), cparams(7)};
905  cameraparams.K = {cparams(0), 0, cparams(2), 0, cparams(1), cparams(3), 0, 0, 1};
906  pub_loop_intrinsics.publish(cameraparams);
907  }
908 
909  //======================================================
910  // PUBLISH FEATURE TRACKS IN THE GLOBAL FRAME OF REFERENCE
911  if (pub_loop_point.getNumSubscribers() != 0) {
912 
913  // Construct the message
914  sensor_msgs::PointCloud point_cloud;
915  point_cloud.header = header;
916  point_cloud.header.frame_id = "global";
917  for (const auto &feattimes : active_tracks_posinG) {
918 
919  // Get this feature information
920  size_t featid = feattimes.first;
921  Eigen::Vector3d uvd = Eigen::Vector3d::Zero();
922  if (active_tracks_uvd.find(featid) != active_tracks_uvd.end()) {
923  uvd = active_tracks_uvd.at(featid);
924  }
925  Eigen::Vector3d pFinG = active_tracks_posinG.at(featid);
926 
927  // Push back 3d point
928  geometry_msgs::Point32 p;
929  p.x = pFinG(0);
930  p.y = pFinG(1);
931  p.z = pFinG(2);
932  point_cloud.points.push_back(p);
933 
934  // Push back the uv_norm, uv_raw, and feature id
935  // NOTE: we don't use the normalized coordinates to save time here
936  // NOTE: they will have to be re-normalized in the loop closure code
937  sensor_msgs::ChannelFloat32 p_2d;
938  p_2d.values.push_back(0);
939  p_2d.values.push_back(0);
940  p_2d.values.push_back(uvd(0));
941  p_2d.values.push_back(uvd(1));
942  p_2d.values.push_back(featid);
943  point_cloud.channels.push_back(p_2d);
944  }
945  pub_loop_point.publish(point_cloud);
946  }
947 
948  //======================================================
949  // Depth images of sparse points and its colorized version
951 
952  // Create the images we will populate with the depths
953  std::pair<int, int> wh_pair = {active_cam0_image.cols, active_cam0_image.rows};
954  cv::Mat depthmap = cv::Mat::zeros(wh_pair.second, wh_pair.first, CV_16UC1);
955  cv::Mat depthmap_viz = active_cam0_image;
956 
957  // Loop through all points and append
958  for (const auto &feattimes : active_tracks_uvd) {
959 
960  // Get this feature information
961  size_t featid = feattimes.first;
962  Eigen::Vector3d uvd = active_tracks_uvd.at(featid);
963 
964  // Skip invalid points
965  double dw = 4;
966  if (uvd(0) < dw || uvd(0) > wh_pair.first - dw || uvd(1) < dw || uvd(1) > wh_pair.second - dw) {
967  continue;
968  }
969 
970  // Append the depth
971  // NOTE: scaled by 1000 to fit the 16U
972  // NOTE: access order is y,x (stupid opencv convention stuff)
973  depthmap.at<uint16_t>((int)uvd(1), (int)uvd(0)) = (uint16_t)(1000 * uvd(2));
974 
975  // Taken from LSD-SLAM codebase segment into 0-4 meter segments:
976  // https://github.com/tum-vision/lsd_slam/blob/d1e6f0e1a027889985d2e6b4c0fe7a90b0c75067/lsd_slam_core/src/util/globalFuncs.cpp#L87-L96
977  float id = 1.0f / (float)uvd(2);
978  float r = (0.0f - id) * 255 / 1.0f;
979  if (r < 0)
980  r = -r;
981  float g = (1.0f - id) * 255 / 1.0f;
982  if (g < 0)
983  g = -g;
984  float b = (2.0f - id) * 255 / 1.0f;
985  if (b < 0)
986  b = -b;
987  uchar rc = r < 0 ? 0 : (r > 255 ? 255 : r);
988  uchar gc = g < 0 ? 0 : (g > 255 ? 255 : g);
989  uchar bc = b < 0 ? 0 : (b > 255 ? 255 : b);
990  cv::Scalar color(255 - rc, 255 - gc, 255 - bc);
991 
992  // Small square around the point (note the above bound check needs to take into account this width)
993  cv::Point p0(uvd(0) - dw, uvd(1) - dw);
994  cv::Point p1(uvd(0) + dw, uvd(1) + dw);
995  cv::rectangle(depthmap_viz, p0, p1, color, -1);
996  }
997 
998  // Create our messages
999  header.frame_id = "cam0";
1000  sensor_msgs::ImagePtr exl_msg1 = cv_bridge::CvImage(header, sensor_msgs::image_encodings::TYPE_16UC1, depthmap).toImageMsg();
1001  it_pub_loop_img_depth.publish(exl_msg1);
1002  header.stamp = ros::Time::now();
1003  header.frame_id = "cam0";
1004  sensor_msgs::ImagePtr exl_msg2 = cv_bridge::CvImage(header, "bgr8", depthmap_viz).toImageMsg();
1006  }
1007 }
ov_msckf::ROS1Visualizer::sync_pol
message_filters::sync_policies::ApproximateTime< sensor_msgs::Image, sensor_msgs::Image > sync_pol
Definition: ROS1Visualizer.h:152
Propagator.h
ov_msckf::ROS1Visualizer::camera_queue_mtx
std::mutex camera_queue_mtx
Definition: ROS1Visualizer.h:180
image_transport::Publisher::getTopic
std::string getTopic() const
ov_msckf::ROS1Visualizer::summed_mse_pos
double summed_mse_pos
Definition: ROS1Visualizer.h:163
tf::StampedTransform::stamp_
ros::Time stamp_
ov_msckf::ROS1Visualizer::rT2
boost::posix_time::ptime rT2
Definition: ROS1Visualizer.h:170
ov_msckf::ROS1Visualizer::_app
std::shared_ptr< VioManager > _app
Core application of the filter system.
Definition: ROS1Visualizer.h:137
ov_msckf::ROS1Visualizer::poses_gt
std::vector< geometry_msgs::PoseStamped > poses_gt
Definition: ROS1Visualizer.h:194
ov_msckf::ROS1Visualizer::pub_posegt
ros::Publisher pub_posegt
Definition: ROS1Visualizer.h:161
ov_msckf::ROS1Visualizer::pub_points_msckf
ros::Publisher pub_points_msckf
Definition: ROS1Visualizer.h:145
ROS1Visualizer.h
image_transport::ImageTransport
boost::shared_ptr
ov_msckf::State::Dm
static Eigen::Matrix3d Dm(StateOptions::ImuModel imu_model, const Eigen::MatrixXd &vec)
Gyroscope and accelerometer intrinsic matrix (scale imperfection and axis misalignment)
Definition: State.h:91
cv_bridge::CvImage::toImageMsg
sensor_msgs::ImagePtr toImageMsg() const
ov_msckf::ROS1Visualizer::publish_groundtruth
void publish_groundtruth()
Publish groundtruth (if we have it)
Definition: ROS1Visualizer.cpp:711
ov_msckf::ROS1Visualizer::rT1
boost::posix_time::ptime rT1
Definition: ROS1Visualizer.h:170
ROSVisualizerHelper.h
ov_core::CameraData::images
std::vector< cv::Mat > images
tf::StampedTransform::child_frame_id_
std::string child_frame_id_
ov_msckf::ROS1Visualizer::publish_features
void publish_features()
Publish current features.
Definition: ROS1Visualizer.cpp:679
quat_multiply
Eigen::Matrix< double, 4, 1 > quat_multiply(const Eigen::Matrix< double, 4, 1 > &q, const Eigen::Matrix< double, 4, 1 > &p)
ov_msckf::ROS1Visualizer::poses_seq_gt
unsigned int poses_seq_gt
Definition: ROS1Visualizer.h:193
PRINT_DEBUG
#define PRINT_DEBUG(x...)
image_transport::Publisher::getNumSubscribers
uint32_t getNumSubscribers() const
ov_msckf
Extended Kalman Filter estimator.
Definition: VioManager.h:46
REDPURPLE
#define REDPURPLE
ov_msckf::ROS1Visualizer::summed_number
size_t summed_number
Definition: ROS1Visualizer.h:166
sensor_data.h
ov_msckf::ROS1Visualizer::_sim
std::shared_ptr< Simulator > _sim
Simulator (is nullptr if we are not sim'ing)
Definition: ROS1Visualizer.h:140
ov_msckf::ROS1Visualizer::poses_imu
std::vector< geometry_msgs::PoseStamped > poses_imu
Definition: ROS1Visualizer.h:158
ov_core::CameraData
cv_bridge::Exception
ov_msckf::ROS1Visualizer::_nh
std::shared_ptr< ros::NodeHandle > _nh
Global node handler.
Definition: ROS1Visualizer.h:134
tf::StampedTransform
ov_msckf::ROS1Visualizer::last_visualization_timestamp_image
double last_visualization_timestamp_image
Definition: ROS1Visualizer.h:187
ov_msckf::ROS1Visualizer::pub_pathimu
ros::Publisher pub_pathimu
Definition: ROS1Visualizer.h:144
ov_msckf::ROS1Visualizer::poses_seq_imu
unsigned int poses_seq_imu
Definition: ROS1Visualizer.h:157
tf::StampedTransform::frame_id_
std::string frame_id_
PRINT_ERROR
#define PRINT_ERROR(x...)
ov_msckf::ROS1Visualizer::publish_loopclosure_information
void publish_loopclosure_information()
Publish loop-closure information of current pose and active track information.
Definition: ROS1Visualizer.cpp:840
ros::Publisher::publish
void publish(const boost::shared_ptr< M > &message) const
ov_msckf::ROS1Visualizer::pub_odomimu
ros::Publisher pub_odomimu
Definition: ROS1Visualizer.h:144
ros::ok
ROSCPP_DECL bool ok()
StateHelper.h
ov_msckf::ROS1Visualizer::it_pub_loop_img_depth
image_transport::Publisher it_pub_loop_img_depth
Definition: ROS1Visualizer.h:143
ov_msckf::ROS1Visualizer::callback_inertial
void callback_inertial(const sensor_msgs::Imu::ConstPtr &msg)
Callback for inertial information.
Definition: ROS1Visualizer.cpp:438
f
f
ov_msckf::ROS1Visualizer::it_pub_tracks
image_transport::Publisher it_pub_tracks
Definition: ROS1Visualizer.h:143
ov_msckf::ROS1Visualizer::pub_pathgt
ros::Publisher pub_pathgt
Definition: ROS1Visualizer.h:161
image_transport::ImageTransport::advertise
Publisher advertise(const std::string &base_topic, uint32_t queue_size, bool latch=false)
sensor_msgs::image_encodings::TYPE_16UC1
const std::string TYPE_16UC1
ov_msckf::ROS1Visualizer::sub_imu
ros::Subscriber sub_imu
Definition: ROS1Visualizer.h:150
ov_core::CameraData::sensor_ids
std::vector< int > sensor_ids
ov_msckf::ROS1Visualizer::pub_points_slam
ros::Publisher pub_points_slam
Definition: ROS1Visualizer.h:145
ov_msckf::ROS1Visualizer::visualize
void visualize()
Will visualize the system if we have new things.
Definition: ROS1Visualizer.cpp:197
ov_msckf::ROS1Visualizer::thread_update_running
std::atomic< bool > thread_update_running
Definition: ROS1Visualizer.h:173
ov_msckf::ROS1Visualizer::publish_calibration_tf
bool publish_calibration_tf
Definition: ROS1Visualizer.h:196
ov_msckf::ROS1Visualizer::publish_state
void publish_state()
Publish the current state.
Definition: ROS1Visualizer.cpp:591
ov_msckf::ROS1Visualizer::sync_cam
std::vector< std::shared_ptr< message_filters::Synchronizer< sync_pol > > > sync_cam
Definition: ROS1Visualizer.h:153
ov_core::CameraData::timestamp
double timestamp
ov_core::CameraData::masks
std::vector< cv::Mat > masks
tf::Transform::setRotation
TFSIMD_FORCE_INLINE void setRotation(const Quaternion &q)
ov_msckf::ROS1Visualizer::publish_images
void publish_images()
Publish the active tracking image.
Definition: ROS1Visualizer.cpp:651
VioManager.h
ov_msckf::StateHelper::get_marginal_covariance
static Eigen::MatrixXd get_marginal_covariance(std::shared_ptr< State > state, const std::vector< std::shared_ptr< ov_type::Type >> &small_variables)
For a given set of variables, this will this will calculate a smaller covariance.
Definition: StateHelper.cpp:226
ov_msckf::ROS1Visualizer::last_visualization_timestamp
double last_visualization_timestamp
Definition: ROS1Visualizer.h:186
ov_msckf::ROS1Visualizer::camera_queue
std::deque< ov_core::CameraData > camera_queue
Definition: ROS1Visualizer.h:179
ov_msckf::ROS1Visualizer::sync_subs_cam
std::vector< std::shared_ptr< message_filters::Subscriber< sensor_msgs::Image > > > sync_subs_cam
Definition: ROS1Visualizer.h:154
BLUE
BLUE
ov_msckf::ROS1Visualizer::subs_cam
std::vector< ros::Subscriber > subs_cam
Definition: ROS1Visualizer.h:151
ov_core::ImuData::wm
Eigen::Matrix< double, 3, 1 > wm
image_transport::Publisher::publish
void publish(const sensor_msgs::Image &message) const
app
app
ov_msckf::ROS1Visualizer::pub_loop_point
ros::Publisher pub_loop_point
Definition: ROS1Visualizer.h:146
ov_core::ImuData
tf::Transform::setOrigin
TFSIMD_FORCE_INLINE void setOrigin(const Vector3 &origin)
ros::Rate::sleep
bool sleep()
quat_2_Rot
Eigen::Matrix< double, 3, 3 > quat_2_Rot(const Eigen::Matrix< double, 4, 1 > &q)
ov_msckf::ROS1Visualizer::visualize_odometry
void visualize_odometry(double timestamp)
Will publish our odometry message for the current timestep. This will take the current state estimate...
Definition: ROS1Visualizer.cpp:245
ov_msckf::ROS1Visualizer::pub_loop_pose
ros::Publisher pub_loop_pose
Definition: ROS1Visualizer.h:146
ov_core::ImuData::am
Eigen::Matrix< double, 3, 1 > am
ov_msckf::ROS1Visualizer::it_pub_loop_img_depth_color
image_transport::Publisher it_pub_loop_img_depth_color
Definition: ROS1Visualizer.h:143
ros::Publisher::getTopic
std::string getTopic() const
ov_msckf::ROS1Visualizer::pub_points_sim
ros::Publisher pub_points_sim
Definition: ROS1Visualizer.h:145
print.h
ros::Time
ov_msckf::State::Tg
static Eigen::Matrix3d Tg(const Eigen::MatrixXd &vec)
Gyroscope gravity sensitivity.
Definition: State.h:110
ov_msckf::ROS1Visualizer::summed_nees_pos
double summed_nees_pos
Definition: ROS1Visualizer.h:165
sensor_msgs::image_encodings::MONO8
const std::string MONO8
ov_msckf::ROS1Visualizer::callback_monocular
void callback_monocular(const sensor_msgs::ImageConstPtr &msg0, int cam_id0)
Callback for monocular cameras information.
Definition: ROS1Visualizer.cpp:498
ov_msckf::ROS1Visualizer::pub_poseimu
ros::Publisher pub_poseimu
Definition: ROS1Visualizer.h:144
PRINT_INFO
#define PRINT_INFO(x...)
ov_type
RESET
#define RESET
cv_bridge::CvImage
dataset_reader.h
ov_msckf::ROS1Visualizer::camera_last_timestamp
std::map< int, double > camera_last_timestamp
Definition: ROS1Visualizer.h:183
ov_msckf::ROS1Visualizer::summed_nees_ori
double summed_nees_ori
Definition: ROS1Visualizer.h:164
sim
std::shared_ptr< Simulator > sim
Definition: run_simulation.cpp:42
ov_msckf::ROS1Visualizer::publish_global2imu_tf
bool publish_global2imu_tf
Definition: ROS1Visualizer.h:195
ov_msckf::ROS1Visualizer::mTfBr
std::shared_ptr< tf::TransformBroadcaster > mTfBr
Definition: ROS1Visualizer.h:147
ov_msckf::ROS1Visualizer::pub_points_aruco
ros::Publisher pub_points_aruco
Definition: ROS1Visualizer.h:145
ov_msckf::ROS1Visualizer::callback_stereo
void callback_stereo(const sensor_msgs::ImageConstPtr &msg0, const sensor_msgs::ImageConstPtr &msg1, int cam_id0, int cam_id1)
Callback for synchronized stereo camera information.
Definition: ROS1Visualizer.cpp:537
ros::Rate
ov_msckf::ROS1Visualizer::visualize_final
void visualize_final()
After the run has ended, print results.
Definition: ROS1Visualizer.cpp:352
ov_msckf::ROS1Visualizer::of_state_est
std::ofstream of_state_est
Definition: ROS1Visualizer.h:200
Inv
Eigen::Matrix< double, 4, 1 > Inv(Eigen::Matrix< double, 4, 1 > q)
ov_msckf::ROS1Visualizer::setup_subscribers
void setup_subscribers(std::shared_ptr< ov_core::YamlParser > parser)
Will setup ROS subscribers and callbacks.
Definition: ROS1Visualizer.cpp:151
ros::Publisher::getNumSubscribers
uint32_t getNumSubscribers() const
State.h
cv_bridge::toCvShare
CvImageConstPtr toCvShare(const sensor_msgs::Image &source, const boost::shared_ptr< void const > &tracked_object, const std::string &encoding=std::string())
header
const std::string header
ov_core::ImuData::timestamp
double timestamp
Simulator.h
ov_msckf::ROSVisualizerHelper::sim_save_total_state_to_file
static void sim_save_total_state_to_file(std::shared_ptr< State > state, std::shared_ptr< Simulator > sim, std::ofstream &of_state_est, std::ofstream &of_state_std, std::ofstream &of_state_gt)
Save current estimate state and groundtruth including calibration.
Definition: ROSVisualizerHelper.cpp:154
ov_msckf::ROS1Visualizer::pub_loop_intrinsics
ros::Publisher pub_loop_intrinsics
Definition: ROS1Visualizer.h:146
ov_msckf::ROS1Visualizer::gt_states
std::map< double, Eigen::Matrix< double, 17, 1 > > gt_states
Definition: ROS1Visualizer.h:190
ov_msckf::ROS1Visualizer::pub_loop_extrinsic
ros::Publisher pub_loop_extrinsic
Definition: ROS1Visualizer.h:146
ov_msckf::ROS1Visualizer::start_time_set
bool start_time_set
Definition: ROS1Visualizer.h:169
tf::Quaternion
ov_core
ov_msckf::ROS1Visualizer::save_total_state
bool save_total_state
Definition: ROS1Visualizer.h:199
ov_msckf::ROS1Visualizer::of_state_std
std::ofstream of_state_std
Definition: ROS1Visualizer.h:200
ov_msckf::ROS1Visualizer::of_state_gt
std::ofstream of_state_gt
Definition: ROS1Visualizer.h:200
ov_msckf::ROS1Visualizer::summed_mse_ori
double summed_mse_ori
Definition: ROS1Visualizer.h:162
ros::Time::now
static Time now()


ov_msckf
Author(s): Patrick Geneva , Kevin Eckenhoff , Guoquan Huang
autogenerated on Mon Dec 16 2024 03:06:54