offline_node_main.cc
Go to the documentation of this file.
1 /*
2  * Copyright 2016 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 
17 #include <csignal>
18 #include <sstream>
19 #include <string>
20 #include <vector>
21 
26 #include "cartographer_ros/node.h"
30 #include "gflags/gflags.h"
31 #include "ros/callback_queue.h"
32 #include "rosbag/bag.h"
33 #include "rosbag/view.h"
34 #include "rosgraph_msgs/Clock.h"
35 #include "tf2_msgs/TFMessage.h"
37 #include "urdf/model.h"
38 
39 DEFINE_string(configuration_directory, "",
40  "First directory in which configuration files are searched, "
41  "second is always the Cartographer installation to allow "
42  "including files from there.");
43 DEFINE_string(configuration_basename, "",
44  "Basename, i.e. not containing any directory prefix, of the "
45  "configuration file.");
46 DEFINE_string(bag_filenames, "", "Comma-separated list of bags to process.");
48  urdf_filename, "",
49  "URDF file that contains static links for your sensor configuration.");
50 DEFINE_bool(use_bag_transforms, true,
51  "Whether to read, use and republish the transforms from the bag.");
52 
53 namespace cartographer_ros {
54 namespace {
55 
56 constexpr char kClockTopic[] = "clock";
57 constexpr char kTfStaticTopic[] = "/tf_static";
58 constexpr char kTfTopic[] = "tf";
59 constexpr int kLatestOnlyPublisherQueueSize = 1;
60 
61 volatile std::sig_atomic_t sigint_triggered = 0;
62 
63 void SigintHandler(int) { sigint_triggered = 1; }
64 
65 std::vector<string> SplitString(const string& input, const char delimiter) {
66  std::stringstream stream(input);
67  string token;
68  std::vector<string> tokens;
69  while (std::getline(stream, token, delimiter)) {
70  tokens.push_back(token);
71  }
72  return tokens;
73 }
74 
75 // TODO(hrapp): This is duplicated in node_main.cc. Pull out into a config
76 // unit.
77 std::tuple<NodeOptions, TrajectoryOptions> LoadOptions() {
78  auto file_resolver = cartographer::common::make_unique<
80  std::vector<string>{FLAGS_configuration_directory});
81  const string code =
82  file_resolver->GetFileContentOrDie(FLAGS_configuration_basename);
83  cartographer::common::LuaParameterDictionary lua_parameter_dictionary(
84  code, std::move(file_resolver));
85 
86  return std::make_tuple(CreateNodeOptions(&lua_parameter_dictionary),
87  CreateTrajectoryOptions(&lua_parameter_dictionary));
88 }
89 
90 void Run(const std::vector<string>& bag_filenames) {
91  NodeOptions node_options;
92  TrajectoryOptions trajectory_options;
93  std::tie(node_options, trajectory_options) = LoadOptions();
94 
96 
97  std::vector<geometry_msgs::TransformStamped> urdf_transforms;
98  if (!FLAGS_urdf_filename.empty()) {
99  urdf_transforms =
100  ReadStaticTransformsFromUrdf(FLAGS_urdf_filename, &tf_buffer);
101  }
102 
103  tf_buffer.setUsingDedicatedThread(true);
104 
105  // Since we preload the transform buffer, we should never have to wait for a
106  // transform. When we finish processing the bag, we will simply drop any
107  // remaining sensor data that cannot be transformed due to missing transforms.
108  node_options.lookup_transform_timeout_sec = 0.;
109  Node node(node_options, &tf_buffer);
110 
111  std::unordered_set<string> expected_sensor_ids;
112  const auto check_insert = [&expected_sensor_ids, &node](const string& topic) {
113  CHECK(expected_sensor_ids.insert(node.node_handle()->resolveName(topic))
114  .second);
115  };
116 
117  // For 2D SLAM, subscribe to exactly one horizontal laser.
118  if (trajectory_options.use_laser_scan) {
119  check_insert(kLaserScanTopic);
120  }
121  if (trajectory_options.use_multi_echo_laser_scan) {
122  check_insert(kMultiEchoLaserScanTopic);
123  }
124 
125  // For 3D SLAM, subscribe to all point clouds topics.
126  if (trajectory_options.num_point_clouds > 0) {
127  for (int i = 0; i < trajectory_options.num_point_clouds; ++i) {
128  // TODO(hrapp): This code is duplicated in places. Pull out a method.
129  string topic = kPointCloud2Topic;
130  if (trajectory_options.num_point_clouds > 1) {
131  topic += "_" + std::to_string(i + 1);
132  }
133  check_insert(topic);
134  }
135  }
136 
137  // For 2D SLAM, subscribe to the IMU if we expect it. For 3D SLAM, the IMU is
138  // required.
139  if (node_options.map_builder_options.use_trajectory_builder_3d() ||
140  (node_options.map_builder_options.use_trajectory_builder_2d() &&
141  trajectory_options.trajectory_builder_options
142  .trajectory_builder_2d_options()
143  .use_imu_data())) {
144  check_insert(kImuTopic);
145  }
146 
147  // For both 2D and 3D SLAM, odometry is optional.
148  if (trajectory_options.use_odometry) {
149  check_insert(kOdometryTopic);
150  }
151 
152  ::ros::Publisher tf_publisher =
153  node.node_handle()->advertise<tf2_msgs::TFMessage>(
154  kTfTopic, kLatestOnlyPublisherQueueSize);
155 
156  ::tf2_ros::StaticTransformBroadcaster static_tf_broadcaster;
157 
158  ::ros::Publisher clock_publisher =
159  node.node_handle()->advertise<rosgraph_msgs::Clock>(
160  kClockTopic, kLatestOnlyPublisherQueueSize);
161 
162  if (urdf_transforms.size() > 0) {
163  static_tf_broadcaster.sendTransform(urdf_transforms);
164  }
165 
166  for (const string& bag_filename : bag_filenames) {
167  if (sigint_triggered) {
168  break;
169  }
170 
171  const int trajectory_id = node.map_builder_bridge()->AddTrajectory(
172  expected_sensor_ids, trajectory_options);
173 
174  rosbag::Bag bag;
175  bag.open(bag_filename, rosbag::bagmode::Read);
176  rosbag::View view(bag);
177  const ::ros::Time begin_time = view.getBeginTime();
178  const double duration_in_seconds = (view.getEndTime() - begin_time).toSec();
179 
180  // We make sure that tf_messages are published before any data messages, so
181  // that tf lookups always work and that tf_buffer has a small cache size -
182  // because it gets very inefficient with a large one.
183  std::deque<rosbag::MessageInstance> delayed_messages;
184  for (const rosbag::MessageInstance& msg : view) {
185  if (sigint_triggered) {
186  break;
187  }
188 
189  if (FLAGS_use_bag_transforms && msg.isType<tf2_msgs::TFMessage>()) {
190  auto tf_message = msg.instantiate<tf2_msgs::TFMessage>();
191  tf_publisher.publish(tf_message);
192 
193  for (const auto& transform : tf_message->transforms) {
194  try {
195  tf_buffer.setTransform(transform, "unused_authority",
196  msg.getTopic() == kTfStaticTopic);
197  } catch (const tf2::TransformException& ex) {
198  LOG(WARNING) << ex.what();
199  }
200  }
201  }
202 
203  while (!delayed_messages.empty() &&
204  delayed_messages.front().getTime() <
205  msg.getTime() + ::ros::Duration(1.)) {
206  const rosbag::MessageInstance& delayed_msg = delayed_messages.front();
207  const string topic = node.node_handle()->resolveName(
208  delayed_msg.getTopic(), false /* resolve */);
209  if (delayed_msg.isType<sensor_msgs::LaserScan>()) {
210  node.map_builder_bridge()
211  ->sensor_bridge(trajectory_id)
212  ->HandleLaserScanMessage(
213  topic, delayed_msg.instantiate<sensor_msgs::LaserScan>());
214  }
215  if (delayed_msg.isType<sensor_msgs::MultiEchoLaserScan>()) {
216  node.map_builder_bridge()
217  ->sensor_bridge(trajectory_id)
218  ->HandleMultiEchoLaserScanMessage(
219  topic,
220  delayed_msg.instantiate<sensor_msgs::MultiEchoLaserScan>());
221  }
222  if (delayed_msg.isType<sensor_msgs::PointCloud2>()) {
223  node.map_builder_bridge()
224  ->sensor_bridge(trajectory_id)
225  ->HandlePointCloud2Message(
226  topic, delayed_msg.instantiate<sensor_msgs::PointCloud2>());
227  }
228  if (delayed_msg.isType<sensor_msgs::Imu>()) {
229  node.map_builder_bridge()
230  ->sensor_bridge(trajectory_id)
231  ->HandleImuMessage(topic,
232  delayed_msg.instantiate<sensor_msgs::Imu>());
233  }
234  if (delayed_msg.isType<nav_msgs::Odometry>()) {
235  node.map_builder_bridge()
236  ->sensor_bridge(trajectory_id)
237  ->HandleOdometryMessage(
238  topic, delayed_msg.instantiate<nav_msgs::Odometry>());
239  }
240  rosgraph_msgs::Clock clock;
241  clock.clock = delayed_msg.getTime();
242  clock_publisher.publish(clock);
243 
244  ::ros::spinOnce();
245 
246  LOG_EVERY_N(INFO, 100000)
247  << "Processed " << (delayed_msg.getTime() - begin_time).toSec() << " of "
248  << duration_in_seconds << " bag time seconds...";
249 
250  delayed_messages.pop_front();
251  }
252 
253  const string topic =
254  node.node_handle()->resolveName(msg.getTopic(), false /* resolve */);
255  if (expected_sensor_ids.count(topic) == 0) {
256  continue;
257  }
258  delayed_messages.push_back(msg);
259 
260  }
261 
262  bag.close();
263  node.map_builder_bridge()->FinishTrajectory(trajectory_id);
264  }
265 
266  node.map_builder_bridge()->SerializeState(bag_filenames.front());
267  node.map_builder_bridge()->WriteAssets(bag_filenames.front());
268 }
269 
270 } // namespace
271 } // namespace cartographer_ros
272 
273 int main(int argc, char** argv) {
274  google::InitGoogleLogging(argv[0]);
275  google::ParseCommandLineFlags(&argc, &argv, true);
276 
277  CHECK(!FLAGS_configuration_directory.empty())
278  << "-configuration_directory is missing.";
279  CHECK(!FLAGS_configuration_basename.empty())
280  << "-configuration_basename is missing.";
281  CHECK(!FLAGS_bag_filenames.empty()) << "-bag_filenames is missing.";
282 
283  std::signal(SIGINT, &::cartographer_ros::SigintHandler);
284  ::ros::init(argc, argv, "cartographer_offline_node",
286  ::ros::start();
287 
289  cartographer_ros::Run(
290  cartographer_ros::SplitString(FLAGS_bag_filenames, ','));
291 
292  ::ros::shutdown();
293 }
DEFINE_bool(use_bag_transforms, true,"Whether to read, use and republish the transforms from the bag.")
void publish(const boost::shared_ptr< M > &message) const
void setUsingDedicatedThread(bool value)
void open(std::string const &filename, uint32_t mode=bagmode::Read)
bool setTransform(const geometry_msgs::TransformStamped &transform, const std::string &authority, bool is_static=false)
constexpr char kImuTopic[]
Definition: node.h:45
constexpr char kLaserScanTopic[]
Definition: node.h:42
void close()
ros::Time const & getTime() const
int main(int argc, char **argv)
boost::shared_ptr< T > instantiate() const
NodeOptions CreateNodeOptions(::cartographer::common::LuaParameterDictionary *const lua_parameter_dictionary)
Definition: node_options.cc:23
constexpr char kOdometryTopic[]
Definition: node.h:46
DEFINE_string(configuration_directory,"","First directory in which configuration files are searched, ""second is always the Cartographer installation to allow ""including files from there.")
TrajectoryOptions CreateTrajectoryOptions(::cartographer::common::LuaParameterDictionary *const lua_parameter_dictionary)
constexpr char kMultiEchoLaserScanTopic[]
Definition: node.h:43
std::string const & getTopic() const
constexpr char kPointCloud2Topic[]
Definition: node.h:44
string GetFileContentOrDie(const string &basename) override
tf2_ros::Buffer * tf_buffer
_Unique_if< T >::_Single_object make_unique(Args &&...args)
std::vector< geometry_msgs::TransformStamped > ReadStaticTransformsFromUrdf(const string &urdf_filename, tf2_ros::Buffer *const tf_buffer)
Definition: urdf_reader.cc:27


cartographer_ros
Author(s):
autogenerated on Wed Jun 5 2019 22:35:56