00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031 #include <laser_slam/util.h>
00032 #include <laser_slam/global_map.h>
00033 #include <laser_slam/point_cloud_snapshotter.h>
00034 #include <laser_slam/exception.h>
00035 #include <laser_geometry/laser_geometry.h>
00036 #include <tf/exceptions.h>
00037 #include <pcl_ros/transforms.h>
00038 #include <pose_graph/graph_search.h>
00039 #include <graph_mapping_utils/utils.h>
00040 #include <ros/ros.h>
00041 #include <boost/foreach.hpp>
00042
00043
00044
00045 namespace laser_slam
00046 {
00047
00048 namespace gm=geometry_msgs;
00049 namespace sm=sensor_msgs;
00050 namespace util=graph_mapping_utils;
00051 namespace pg=pose_graph;
00052 namespace msg=graph_mapping_msgs;
00053 namespace nm=nav_msgs;
00054
00055 using std::string;
00056
00057 typedef boost::mutex::scoped_lock Lock;
00058 typedef boost::optional<ros::Time> MaybeTime;
00059 typedef boost::circular_buffer<sm::LaserScan::ConstPtr> ScanBuf;
00060
00061 const double SCAN_TIMEOUT=30.0;
00062 const double SCAN_UPDATE_DISTANCE_THRESHOLD=1.0;
00063 const double SCAN_UPDATE_ANGLE_THRESHOLD=0.5;
00064
00065
00066 PointCloudSnapshotter::PointCloudSnapshotter
00067 (boost::shared_ptr<tf::TransformListener> tf, ros::NodeHandle param_nh) :
00068 param_nh_(param_nh),
00069 fixed_frame_(util::searchParam<string>(param_nh_, "fixed_frame")),
00070 sensor_frame_(util::searchParam<string>(param_nh_, "laser_frame")),
00071 base_frame_(util::searchParam<string>(param_nh_, "base_frame")),
00072 global_frame_(util::searchParam<string>(param_nh_, "optimization_frame")),
00073 transform_wait_duration_(0.2),
00074 grid_resolution_(util::getParam<double>(param_nh_, "grid_resolution")),
00075 scan_save_min_gap_(util::getParam<double>(param_nh_,
00076 "scan_save_min_gap", 0.1)),
00077 scan_time_tol_(util::getParam<double>(param_nh_, "scan_time_tolerance")),
00078 cleanup_grid_(util::getParam<bool>(param_nh_, "cleanup_grid", true)),
00079 robot_radius_(util::getParam<double>(param_nh_, "robot_radius", -1)),
00080 recent_scans_(SCAN_BUFFER_SIZE),
00081 optimize_flag_(true), nh_(), tf_(tf), db_("graph_mapping"),
00082 scans_(&db_, "scans"), ff_poses_(&db_, "fixed_frame_poses"),
00083 laser_sub_(nh_.subscribe("scan", 1,
00084 &PointCloudSnapshotter::scanCallback, this)),
00085 loc_sub_(nh_.subscribe("graph_localization", 5,
00086 &PointCloudSnapshotter::locCallback, this)),
00087 diff_sub_(boost::bind(&PointCloudSnapshotter::diffCallback, this, _1, _2)),
00088 map_pub_(nh_.advertise<nm::OccupancyGrid>("global_map", 5)),
00089 get_poses_client_(nh_.serviceClient<msg::GetPoses>("get_node_poses")),
00090 map_pub_timer_(util::timerFromRate(nh_, util::getParam<double>
00091 (param_nh_, "grid_pub_rate", 0.2),
00092 &PointCloudSnapshotter::publishMap, this)),
00093 recent_scan_time_server_(nh_.advertiseService
00094 ("recent_scan_time",
00095 &PointCloudSnapshotter::recentScanTimeCallback,
00096 this))
00097 {
00098 }
00099
00100
00101
00102 struct ScanOrdering
00103 {
00104 bool operator() (const sm::LaserScan::ConstPtr& scan, const ros::Time& t)
00105 {
00106 return (scan->header.stamp<t);
00107 }
00108 };
00109
00110
00111
00112 MaybeTime PointCloudSnapshotter::recentScanTime (const ros::Time& t1,
00113 const ros::Time& t2) const
00114 {
00115 ROS_ASSERT (t2>=t1);
00116 Lock lock(mutex_);
00117 const ScanBuf::const_iterator pos =
00118 lower_bound(recent_scans_.begin(), recent_scans_.end(), t2, ScanOrdering());
00119 MaybeTime t;
00120 if (pos!=recent_scans_.begin()) {
00121 sm::LaserScan::ConstPtr scan = *(pos-1);
00122 if (scan->header.stamp >= t1)
00123 t = scan->header.stamp;
00124 }
00125 ROS_ASSERT (!t || (*t >= t1 && *t <= t2));
00126 return t;
00127 }
00128
00129
00130 bool PointCloudSnapshotter::recentScanTimeCallback
00131 (RecentScanTime::Request& req, RecentScanTime::Response& resp)
00132 {
00133 MaybeTime mt = recentScanTime(req.t1, req.t2);
00134 if (mt)
00135 {
00136 resp.t = *mt;
00137 resp.found = true;
00138 }
00139 else
00140 {
00141 resp.found = false;
00142 }
00143
00144 return true;
00145 }
00146
00149 void PointCloudSnapshotter::scanCallback (const sm::LaserScan::ConstPtr& scan)
00150 {
00151 Lock lock(mutex_);
00152 if (recent_scans_.empty() || (*(--recent_scans_.end()))->header.stamp +
00153 scan_save_min_gap_ < scan->header.stamp)
00154 {
00155 recent_scans_.push_back(scan);
00156
00157
00158
00159 if (ref_node_)
00160 {
00161 try
00162 {
00163 LocalizedScan::ConstPtr old_scan = scans_.get(*ref_node_);
00164 const double disp = util::length(last_loc_->pose.position);
00165 const double angle = tf::getYaw(last_loc_->pose.orientation);
00166 if (scan->header.stamp >
00167 old_scan->scan.header.stamp + ros::Duration(SCAN_TIMEOUT) &&
00168 disp<SCAN_UPDATE_DISTANCE_THRESHOLD &&
00169 fabs(angle)<SCAN_UPDATE_ANGLE_THRESHOLD)
00170 {
00171
00172
00173
00174 }
00175 }
00176 catch (pg::DataNotFoundException& e)
00177 {
00178 ROS_WARN ("Skipping scan age check as scan not found for %u", *ref_node_);
00179 }
00180 }
00181 }
00182 }
00183
00184
00186 void PointCloudSnapshotter::diffCallback
00187 (boost::optional<const msg::ConstraintGraphDiff&> diff,
00188 const pg::ConstraintGraph& graph)
00189 {
00190 ROS_DEBUG_COND_NAMED (diff, "snapshot",
00191 "Received diff with %zu nodes, %zu edges",
00192 diff->new_nodes.size(), diff->new_edges.size());
00193 ROS_DEBUG_COND_NAMED (!diff, "snapshot",
00194 "Received full graph with %zu nodes",
00195 graph.allNodes().size());
00196
00197 if (diff && diff->new_nodes.size() == 1) {
00198 ROS_ASSERT (diff->new_node_timestamps.size() == 1 &&
00199 diff->new_edges.size() == 0);
00200 const ros::Time& t = diff->new_node_timestamps[0];
00201 const unsigned n(diff->new_nodes[0].id);
00202 Lock lock(mutex_);
00203
00204
00205 ScanBuf::const_iterator pos =
00206 lower_bound(recent_scans_.begin(), recent_scans_.end(),
00207 t, ScanOrdering());
00208 if (pos==recent_scans_.end() && !recent_scans_.empty()) pos--;
00209 if (pos==recent_scans_.end() ||
00210 fabs((*pos)->header.stamp.toSec()-t.toSec()) > scan_time_tol_) {
00211 ROS_WARN_STREAM_NAMED ("snapshot",
00212 "Couldn't find buffered data with stamp close to "
00213 << t << "; not saving a scan. Currently have " <<
00214 recent_scans_.size() << " scans.");
00215 ROS_WARN_STREAM_COND_NAMED (pos!=recent_scans_.end(), "snapshot",
00216 " Closest scan was at " <<
00217 (*pos)->header.stamp <<
00218 " and scan time tolerance parameter was "
00219 << scan_time_tol_);
00220 return;
00221 }
00222
00223
00224 processAndSaveScan(*pos, t, n);
00225 }
00226 else if (!diff || diff->new_edges.size() > 0) {
00227 optimize_flag_ = true;
00228 }
00229 }
00230
00231 tf::Pose PointCloudSnapshotter::ffPoseAt (const ros::Time& t)
00232 {
00233 tf::StampedTransform tr;
00234 tf_->lookupTransform(fixed_frame_, base_frame_, t, tr);
00235 return tr;
00236 }
00237
00238
00239
00240 LocalizedScan::Ptr PointCloudSnapshotter::processScan
00241 (sm::LaserScan::ConstPtr scan, const gm::Pose& sensor_pose) const
00242 {
00243 LocalizedScan::Ptr sensor_frame_cloud = boost::make_shared<LocalizedScan>();
00244
00245
00246 sm::PointCloud fixed_frame_cloud;
00247 laser_geometry::LaserProjection projector_;
00248 projector_.transformLaserScanToPointCloud(fixed_frame_, *scan, fixed_frame_cloud, *tf_);
00249
00250
00251 tf_->transformPointCloud(sensor_frame_, scan->header.stamp, fixed_frame_cloud,
00252 fixed_frame_, sensor_frame_cloud->cloud);
00253 sensor_frame_cloud->sensor_pose = sensor_pose;
00254 sensor_frame_cloud->header.frame_id = "unused";
00255 sensor_frame_cloud->header.stamp = ros::Time(42.0);
00256 sensor_frame_cloud->scan = *scan;
00257 const gm::Point b = util::barycenter(sensor_frame_cloud->cloud);
00258 sensor_frame_cloud->barycenter =
00259 util::transformPoint(util::toPose(sensor_pose), b);
00260
00261 return sensor_frame_cloud;
00262 }
00263
00264
00267 void PointCloudSnapshotter::processAndSaveScan (sm::LaserScan::ConstPtr scan,
00268 const ros::Time& t,
00269 const unsigned n)
00270 {
00271 const ros::Time start = scan->header.stamp;
00272 const ros::Duration scan_duration(scan->ranges.size()*scan->time_increment);
00273 const ros::Time end = start + scan_duration;
00274
00275
00276 if (!tf_->waitForTransform(fixed_frame_, sensor_frame_, start, transform_wait_duration_))
00277 throw SnapshotTransformException(fixed_frame_, sensor_frame_, start, transform_wait_duration_);
00278 if (!tf_->waitForTransform(fixed_frame_, base_frame_, start, transform_wait_duration_))
00279 throw SnapshotTransformException(fixed_frame_, base_frame_, start, transform_wait_duration_);
00280 if (!tf_->waitForTransform(fixed_frame_, sensor_frame_, end, transform_wait_duration_))
00281 throw SnapshotTransformException(fixed_frame_, sensor_frame_, end, transform_wait_duration_);
00282 if (!tf_->waitForTransform(fixed_frame_, base_frame_, end, transform_wait_duration_))
00283 throw SnapshotTransformException(fixed_frame_, base_frame_, end, transform_wait_duration_);
00284 if (!tf_->waitForTransform(fixed_frame_, base_frame_, t, transform_wait_duration_))
00285 throw SnapshotTransformException(fixed_frame_, base_frame_, t, transform_wait_duration_);
00286
00287
00288 gm::PoseStamped id, scan_sensor_pose;
00289 id.pose.orientation.w = 1.0;
00290 id.header.stamp = scan->header.stamp;
00291 id.header.frame_id = sensor_frame_;
00292 tf_->transformPose(base_frame_, t, id, fixed_frame_, scan_sensor_pose);
00293
00294 LocalizedScan::Ptr loc_scan = processScan(scan, scan_sensor_pose.pose);
00295 scans_.set(n, loc_scan);
00296 ROS_DEBUG_STREAM_NAMED ("snapshot", "Scan saved");
00297 }
00298
00299
00300 void PointCloudSnapshotter::processAndSaveScan (sm::LaserScan::ConstPtr scan,
00301 const gm::PoseStamped& pos)
00302 {
00303 const unsigned n = util::refNode(pos);
00304 const ros::Time start = scan->header.stamp;
00305 const ros::Duration scan_duration(scan->ranges.size()*scan->time_increment);
00306 const ros::Time end = start + scan_duration;
00307
00308
00309 if (!tf_->waitForTransform(fixed_frame_, sensor_frame_, start, transform_wait_duration_))
00310 throw SnapshotTransformException(fixed_frame_, sensor_frame_, start, transform_wait_duration_);
00311 if (!tf_->waitForTransform(fixed_frame_, base_frame_, start, transform_wait_duration_))
00312 throw SnapshotTransformException(fixed_frame_, base_frame_, start, transform_wait_duration_);
00313 if (!tf_->waitForTransform(fixed_frame_, sensor_frame_, end, transform_wait_duration_))
00314 throw SnapshotTransformException(fixed_frame_, sensor_frame_, end, transform_wait_duration_);
00315 if (!tf_->waitForTransform(fixed_frame_, base_frame_, end, transform_wait_duration_))
00316 throw SnapshotTransformException(fixed_frame_, base_frame_, end, transform_wait_duration_);
00317 if (!tf_->waitForTransform(fixed_frame_, base_frame_, pos.header.stamp, transform_wait_duration_))
00318 throw SnapshotTransformException(fixed_frame_, base_frame_, pos.header.stamp, transform_wait_duration_);
00319
00320
00321 gm::PoseStamped id, scan_sensor_pose;
00322 id.pose.orientation.w = 1.0;
00323 id.header.stamp = scan->header.stamp;
00324 id.header.frame_id = sensor_frame_;
00325 tf_->transformPose(base_frame_, pos.header.stamp, id, fixed_frame_, scan_sensor_pose);
00326 const gm::Pose pose_in_node_frame =
00327 util::transformPose(util::toPose(pos.pose), scan_sensor_pose.pose);
00328 LocalizedScan::Ptr loc_scan = processScan(scan, pose_in_node_frame);
00329 scans_.set(n, loc_scan);
00330
00331 using util::toString;
00332 ROS_INFO_STREAM ("Scan sensor pose in base frame was " << toString(scan_sensor_pose.pose)
00333 << ", localization was " << util::toString(pos.pose) << ", and pose in node frame was "
00334 << toString(pose_in_node_frame));
00335
00336
00337 ROS_WARN_STREAM_NAMED ("snapshot", "Scan updated");
00338
00339 }
00340
00341 void PointCloudSnapshotter::locCallback (msg::LocalizationDistribution::ConstPtr m)
00342 {
00343 Lock l(mutex_);
00344 if (m->samples.size()==1) {
00345 last_loc_ = m->samples[0];
00346 ref_node_ = util::refNode(m->samples[0]);
00347 }
00348 else
00349 ROS_WARN_NAMED ("snapshot", "Ignoring localization message with %zu samples",
00350 m->samples.size());
00351 }
00352
00353 void PointCloudSnapshotter::publishMap (const ros::TimerEvent& e)
00354 {
00355
00356 if (!map_pub_.getNumSubscribers())
00357 return;
00358
00359 pg::ConstraintGraph g = diff_sub_.getCurrentGraph();
00360 if (g.allNodes().empty())
00361 return;
00362 pg::NodeSet comp;
00363 bool need_to_optimize = false;
00364 {
00365 Lock l(mutex_);
00366 if (!ref_node_)
00367 {
00368 comp = largestComp(g);
00369 need_to_optimize = true;
00370 }
00371 else {
00372 comp = pg::componentContaining(g, *ref_node_);
00373 need_to_optimize = optimize_flag_ || !util::contains(opt_poses_, *ref_node_);
00374 }
00375 }
00376 if (need_to_optimize)
00377 {
00378 optimizeGraph(&g, &opt_poses_, comp, get_poses_client_);
00379 ROS_DEBUG_STREAM_NAMED ("snapshot_optimize", "Reoptimizing component containing "
00380 << (ref_node_ ? *ref_node_ : *comp.begin()));
00381 optimize_flag_ = false;
00382 }
00383 g.setOptimizedPoses(opt_poses_);
00384 ROS_DEBUG_STREAM_NAMED ("publish_grid", "Publishing grid based on component containing " <<
00385 (ref_node_ ? *ref_node_ : *comp.begin()) << " with " << comp.size() << " nodes");
00386 const nm::OccupancyGrid::ConstPtr map =
00387 generateGlobalMap(g, scans_, grid_resolution_, global_frame_, cleanup_grid_,
00388 opt_poses_, robot_radius_);
00389 map_pub_.publish(map);
00390 }
00391
00392
00393 }