rosbag_validate_main.cc 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425
  1. /*
  2. * Copyright 2017 The Cartographer Authors
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #include <fstream>
  17. #include <iostream>
  18. #include <map>
  19. #include <set>
  20. #include <string>
  21. #include "absl/memory/memory.h"
  22. #include "cartographer/common/histogram.h"
  23. #include "cartographer_ros/msg_conversion.h"
  24. #include "gflags/gflags.h"
  25. #include "glog/logging.h"
  26. #include "nav_msgs/Odometry.h"
  27. #include "ros/ros.h"
  28. #include "ros/time.h"
  29. #include "rosbag/bag.h"
  30. #include "rosbag/view.h"
  31. #include "sensor_msgs/Imu.h"
  32. #include "sensor_msgs/LaserScan.h"
  33. #include "sensor_msgs/MultiEchoLaserScan.h"
  34. #include "sensor_msgs/PointCloud2.h"
  35. #include "tf2_eigen/tf2_eigen.h"
  36. #include "tf2_msgs/TFMessage.h"
  37. #include "tf2_ros/buffer.h"
  38. #include "urdf/model.h"
  39. DEFINE_string(bag_filename, "", "Bag to process.");
  40. DEFINE_bool(dump_timing, false,
  41. "Dump per-sensor timing information in files called "
  42. "timing_<frame_id>.csv in the current directory.");
  43. namespace cartographer_ros {
  44. namespace {
  45. struct FrameProperties {
  46. ros::Time last_timestamp;
  47. std::string topic;
  48. std::vector<float> time_deltas;
  49. std::unique_ptr<std::ofstream> timing_file;
  50. std::string data_type;
  51. };
  52. const double kMinLinearAcceleration = 3.;
  53. const double kMaxLinearAcceleration = 30.;
  54. const double kTimeDeltaSerializationSensorWarning = 0.1;
  55. const double kTimeDeltaSerializationSensorError = 0.5;
  56. const double kMinAverageAcceleration = 9.5;
  57. const double kMaxAverageAcceleration = 10.5;
  58. const double kMaxGapPointsData = 0.1;
  59. const double kMaxGapImuData = 0.05;
  60. const std::set<std::string> kPointDataTypes = {
  61. std::string(
  62. ros::message_traits::DataType<sensor_msgs::PointCloud2>::value()),
  63. std::string(ros::message_traits::DataType<
  64. sensor_msgs::MultiEchoLaserScan>::value()),
  65. std::string(
  66. ros::message_traits::DataType<sensor_msgs::LaserScan>::value())};
  67. std::unique_ptr<std::ofstream> CreateTimingFile(const std::string& frame_id) {
  68. auto timing_file = absl::make_unique<std::ofstream>(
  69. std::string("timing_") + frame_id + ".csv", std::ios_base::out);
  70. (*timing_file)
  71. << "# Timing information for sensor with frame id: " << frame_id
  72. << std::endl
  73. << "# Columns are in order" << std::endl
  74. << "# - packet index of the packet in the bag, first packet is 1"
  75. << std::endl
  76. << "# - timestamp when rosbag wrote the packet, i.e. "
  77. "rosbag::MessageInstance::getTime().toNSec()"
  78. << std::endl
  79. << "# - timestamp when data was acquired, i.e. "
  80. "message.header.stamp.toNSec()"
  81. << std::endl
  82. << "#" << std::endl
  83. << "# The data can be read in python using" << std::endl
  84. << "# import numpy" << std::endl
  85. << "# np.loadtxt(<filename>, dtype='uint64')" << std::endl;
  86. return timing_file;
  87. }
  88. void CheckImuMessage(const sensor_msgs::Imu& imu_message) {
  89. auto linear_acceleration = ToEigen(imu_message.linear_acceleration);
  90. if (std::isnan(linear_acceleration.norm()) ||
  91. linear_acceleration.norm() < kMinLinearAcceleration ||
  92. linear_acceleration.norm() > kMaxLinearAcceleration) {
  93. LOG_FIRST_N(WARNING, 3)
  94. << "frame_id " << imu_message.header.frame_id << " time "
  95. << imu_message.header.stamp.toNSec() << ": IMU linear acceleration is "
  96. << linear_acceleration.norm() << " m/s^2,"
  97. << " expected is [" << kMinLinearAcceleration << ", "
  98. << kMaxLinearAcceleration << "] m/s^2."
  99. << " (It should include gravity and be given in m/s^2.)"
  100. << " linear_acceleration " << linear_acceleration.transpose();
  101. }
  102. }
  103. bool IsValidPose(const geometry_msgs::Pose& pose) {
  104. return ToRigid3d(pose).IsValid();
  105. }
  106. void CheckOdometryMessage(const nav_msgs::Odometry& message) {
  107. const auto& pose = message.pose.pose;
  108. if (!IsValidPose(pose)) {
  109. LOG_FIRST_N(ERROR, 3) << "frame_id " << message.header.frame_id << " time "
  110. << message.header.stamp.toNSec()
  111. << ": Odometry pose is invalid."
  112. << " pose " << pose;
  113. }
  114. }
  115. void CheckTfMessage(const tf2_msgs::TFMessage& message) {
  116. for (const auto& transform : message.transforms) {
  117. if (transform.header.frame_id == "map") {
  118. LOG_FIRST_N(ERROR, 1)
  119. << "Input contains transform message from frame_id \""
  120. << transform.header.frame_id << "\" to child_frame_id \""
  121. << transform.child_frame_id
  122. << "\". This is almost always output published by cartographer and "
  123. "should not appear as input. (Unless you have some complex "
  124. "remove_frames configuration, this is will not work. Simplest "
  125. "solution is to record input without cartographer running.)";
  126. }
  127. }
  128. }
  129. bool IsPointDataType(const std::string& data_type) {
  130. return (kPointDataTypes.count(data_type) != 0);
  131. }
  132. class RangeDataChecker {
  133. public:
  134. template <typename MessageType>
  135. void CheckMessage(const MessageType& message) {
  136. const std::string& frame_id = message.header.frame_id;
  137. ros::Time current_time_stamp = message.header.stamp;
  138. RangeChecksum current_checksum;
  139. cartographer::common::Time time_from, time_to;
  140. ReadRangeMessage(message, &current_checksum, &time_from, &time_to);
  141. auto previous_time_to_it = frame_id_to_previous_time_to_.find(frame_id);
  142. if (previous_time_to_it != frame_id_to_previous_time_to_.end() &&
  143. previous_time_to_it->second >= time_from) {
  144. if (previous_time_to_it->second >= time_to) {
  145. LOG_FIRST_N(ERROR, 3) << "Sensor with frame_id \"" << frame_id
  146. << "\" is not sequential in time."
  147. << "Previous range message ends at time "
  148. << previous_time_to_it->second
  149. << ", current one at time " << time_to;
  150. } else {
  151. LOG_FIRST_N(WARNING, 3)
  152. << "Sensor with frame_id \"" << frame_id
  153. << "\" measurements overlap in time. "
  154. << "Previous range message, ending at time stamp "
  155. << previous_time_to_it->second
  156. << ", must finish before current range message, "
  157. << "which ranges from " << time_from << " to " << time_to;
  158. }
  159. double overlap = cartographer::common::ToSeconds(
  160. previous_time_to_it->second - time_from);
  161. auto it = frame_id_to_max_overlap_duration_.find(frame_id);
  162. if (it == frame_id_to_max_overlap_duration_.end() ||
  163. overlap > frame_id_to_max_overlap_duration_.at(frame_id)) {
  164. frame_id_to_max_overlap_duration_[frame_id] = overlap;
  165. }
  166. }
  167. frame_id_to_previous_time_to_[frame_id] = time_to;
  168. if (current_checksum.first == 0) {
  169. return;
  170. }
  171. auto it = frame_id_to_range_checksum_.find(frame_id);
  172. if (it != frame_id_to_range_checksum_.end()) {
  173. RangeChecksum previous_checksum = it->second;
  174. if (previous_checksum == current_checksum) {
  175. LOG_FIRST_N(ERROR, 3)
  176. << "Sensor with frame_id \"" << frame_id
  177. << "\" sends exactly the same range measurements multiple times. "
  178. << "Range data at time " << current_time_stamp
  179. << " equals preceding data with " << current_checksum.first
  180. << " points.";
  181. }
  182. }
  183. frame_id_to_range_checksum_[frame_id] = current_checksum;
  184. }
  185. void PrintReport() {
  186. for (auto& it : frame_id_to_max_overlap_duration_) {
  187. LOG(WARNING) << "Sensor with frame_id \"" << it.first
  188. << "\" range measurements have longest overlap of "
  189. << it.second << " s";
  190. }
  191. }
  192. private:
  193. typedef std::pair<size_t /* num_points */, Eigen::Vector4f /* points_sum */>
  194. RangeChecksum;
  195. template <typename MessageType>
  196. static void ReadRangeMessage(const MessageType& message,
  197. RangeChecksum* range_checksum,
  198. cartographer::common::Time* from,
  199. cartographer::common::Time* to) {
  200. auto point_cloud_time = ToPointCloudWithIntensities(message);
  201. const cartographer::sensor::TimedPointCloud& point_cloud =
  202. std::get<0>(point_cloud_time).points;
  203. *to = std::get<1>(point_cloud_time);
  204. *from = *to + cartographer::common::FromSeconds(point_cloud[0].time);
  205. Eigen::Vector4f points_sum = Eigen::Vector4f::Zero();
  206. for (const auto& point : point_cloud) {
  207. points_sum.head<3>() += point.position;
  208. points_sum[3] += point.time;
  209. }
  210. if (point_cloud.size() > 0) {
  211. double first_point_relative_time = point_cloud[0].time;
  212. double last_point_relative_time = (*point_cloud.rbegin()).time;
  213. if (first_point_relative_time > 0) {
  214. LOG_FIRST_N(ERROR, 1)
  215. << "Sensor with frame_id \"" << message.header.frame_id
  216. << "\" has range data that has positive relative time "
  217. << first_point_relative_time << " s, must negative or zero.";
  218. }
  219. if (last_point_relative_time != 0) {
  220. LOG_FIRST_N(INFO, 1)
  221. << "Sensor with frame_id \"" << message.header.frame_id
  222. << "\" has range data whose last point has relative time "
  223. << last_point_relative_time << " s, should be zero.";
  224. }
  225. }
  226. *range_checksum = {point_cloud.size(), points_sum};
  227. }
  228. std::map<std::string, RangeChecksum> frame_id_to_range_checksum_;
  229. std::map<std::string, cartographer::common::Time>
  230. frame_id_to_previous_time_to_;
  231. std::map<std::string, double> frame_id_to_max_overlap_duration_;
  232. };
  233. void Run(const std::string& bag_filename, const bool dump_timing) {
  234. rosbag::Bag bag;
  235. bag.open(bag_filename, rosbag::bagmode::Read);
  236. rosbag::View view(bag);
  237. std::map<std::string, FrameProperties> frame_id_to_properties;
  238. size_t message_index = 0;
  239. int num_imu_messages = 0;
  240. double sum_imu_acceleration = 0.;
  241. RangeDataChecker range_data_checker;
  242. for (const rosbag::MessageInstance& message : view) {
  243. ++message_index;
  244. std::string frame_id;
  245. ros::Time time;
  246. if (message.isType<sensor_msgs::PointCloud2>()) {
  247. auto msg = message.instantiate<sensor_msgs::PointCloud2>();
  248. time = msg->header.stamp;
  249. frame_id = msg->header.frame_id;
  250. range_data_checker.CheckMessage(*msg);
  251. } else if (message.isType<sensor_msgs::MultiEchoLaserScan>()) {
  252. auto msg = message.instantiate<sensor_msgs::MultiEchoLaserScan>();
  253. time = msg->header.stamp;
  254. frame_id = msg->header.frame_id;
  255. range_data_checker.CheckMessage(*msg);
  256. } else if (message.isType<sensor_msgs::LaserScan>()) {
  257. auto msg = message.instantiate<sensor_msgs::LaserScan>();
  258. time = msg->header.stamp;
  259. frame_id = msg->header.frame_id;
  260. range_data_checker.CheckMessage(*msg);
  261. } else if (message.isType<sensor_msgs::Imu>()) {
  262. auto msg = message.instantiate<sensor_msgs::Imu>();
  263. time = msg->header.stamp;
  264. frame_id = msg->header.frame_id;
  265. CheckImuMessage(*msg);
  266. num_imu_messages++;
  267. sum_imu_acceleration += ToEigen(msg->linear_acceleration).norm();
  268. } else if (message.isType<nav_msgs::Odometry>()) {
  269. auto msg = message.instantiate<nav_msgs::Odometry>();
  270. time = msg->header.stamp;
  271. frame_id = msg->header.frame_id;
  272. CheckOdometryMessage(*msg);
  273. } else if (message.isType<tf2_msgs::TFMessage>()) {
  274. auto msg = message.instantiate<tf2_msgs::TFMessage>();
  275. CheckTfMessage(*msg);
  276. continue;
  277. } else {
  278. continue;
  279. }
  280. bool first_packet = false;
  281. if (!frame_id_to_properties.count(frame_id)) {
  282. frame_id_to_properties.emplace(
  283. frame_id,
  284. FrameProperties{time, message.getTopic(), std::vector<float>(),
  285. dump_timing ? CreateTimingFile(frame_id) : nullptr,
  286. message.getDataType()});
  287. first_packet = true;
  288. }
  289. auto& entry = frame_id_to_properties.at(frame_id);
  290. if (!first_packet) {
  291. const double delta_t_sec = (time - entry.last_timestamp).toSec();
  292. if (delta_t_sec <= 0) {
  293. LOG_FIRST_N(ERROR, 3)
  294. << "Sensor with frame_id \"" << frame_id
  295. << "\" jumps backwards in time, i.e. timestamps are not strictly "
  296. "increasing. Make sure that the bag contains the data for each "
  297. "frame_id sorted by header.stamp, i.e. the order in which they "
  298. "were acquired from the sensor.";
  299. }
  300. entry.time_deltas.push_back(delta_t_sec);
  301. }
  302. if (entry.topic != message.getTopic()) {
  303. LOG_FIRST_N(ERROR, 3)
  304. << "frame_id \"" << frame_id
  305. << "\" is send on multiple topics. It was seen at least on "
  306. << entry.topic << " and " << message.getTopic();
  307. }
  308. entry.last_timestamp = time;
  309. if (dump_timing) {
  310. CHECK(entry.timing_file != nullptr);
  311. (*entry.timing_file) << message_index << "\t"
  312. << message.getTime().toNSec() << "\t"
  313. << time.toNSec() << std::endl;
  314. }
  315. double duration_serialization_sensor = (time - message.getTime()).toSec();
  316. if (std::abs(duration_serialization_sensor) >
  317. kTimeDeltaSerializationSensorWarning) {
  318. std::stringstream stream;
  319. stream << "frame_id \"" << frame_id << "\" on topic "
  320. << message.getTopic() << " has serialization time "
  321. << message.getTime() << " but sensor time " << time
  322. << " differing by " << duration_serialization_sensor << " s.";
  323. if (std::abs(duration_serialization_sensor) >
  324. kTimeDeltaSerializationSensorError) {
  325. LOG_FIRST_N(ERROR, 3) << stream.str();
  326. } else {
  327. LOG_FIRST_N(WARNING, 1) << stream.str();
  328. }
  329. }
  330. }
  331. bag.close();
  332. range_data_checker.PrintReport();
  333. if (num_imu_messages > 0) {
  334. double average_imu_acceleration = sum_imu_acceleration / num_imu_messages;
  335. if (std::isnan(average_imu_acceleration) ||
  336. average_imu_acceleration < kMinAverageAcceleration ||
  337. average_imu_acceleration > kMaxAverageAcceleration) {
  338. LOG(ERROR) << "Average IMU linear acceleration is "
  339. << average_imu_acceleration << " m/s^2,"
  340. << " expected is [" << kMinAverageAcceleration << ", "
  341. << kMaxAverageAcceleration
  342. << "] m/s^2. Linear acceleration data "
  343. "should include gravity and be given in m/s^2.";
  344. }
  345. }
  346. constexpr int kNumBucketsForHistogram = 10;
  347. for (const auto& entry_pair : frame_id_to_properties) {
  348. const FrameProperties& frame_properties = entry_pair.second;
  349. float max_time_delta =
  350. *std::max_element(frame_properties.time_deltas.begin(),
  351. frame_properties.time_deltas.end());
  352. if (IsPointDataType(frame_properties.data_type) &&
  353. max_time_delta > kMaxGapPointsData) {
  354. LOG(ERROR) << "Point data (frame_id: \"" << entry_pair.first
  355. << "\") has a large gap, largest is " << max_time_delta
  356. << " s, recommended is [0.0005, 0.05] s with no jitter.";
  357. }
  358. if (frame_properties.data_type ==
  359. ros::message_traits::DataType<sensor_msgs::Imu>::value() &&
  360. max_time_delta > kMaxGapImuData) {
  361. LOG(ERROR) << "IMU data (frame_id: \"" << entry_pair.first
  362. << "\") has a large gap, largest is " << max_time_delta
  363. << " s, recommended is [0.0005, 0.005] s with no jitter.";
  364. }
  365. cartographer::common::Histogram histogram;
  366. for (float time_delta : frame_properties.time_deltas) {
  367. histogram.Add(time_delta);
  368. }
  369. LOG(INFO) << "Time delta histogram for consecutive messages on topic \""
  370. << frame_properties.topic << "\" (frame_id: \""
  371. << entry_pair.first << "\"):\n"
  372. << histogram.ToString(kNumBucketsForHistogram);
  373. }
  374. if (dump_timing) {
  375. for (const auto& entry_pair : frame_id_to_properties) {
  376. entry_pair.second.timing_file->close();
  377. CHECK(*entry_pair.second.timing_file)
  378. << "Could not write timing information for \"" << entry_pair.first
  379. << "\"";
  380. }
  381. }
  382. }
  383. } // namespace
  384. } // namespace cartographer_ros
  385. int main(int argc, char** argv) {
  386. FLAGS_alsologtostderr = true;
  387. google::InitGoogleLogging(argv[0]);
  388. google::ParseCommandLineFlags(&argc, &argv, true);
  389. CHECK(!FLAGS_bag_filename.empty()) << "-bag_filename is missing.";
  390. ::cartographer_ros::Run(FLAGS_bag_filename, FLAGS_dump_timing);
  391. }