graph_mapper.cpp
Go to the documentation of this file.
00001 /*
00002  * Copyright (c) 2008, Willow Garage, Inc.
00003  * All rights reserved.
00004  *
00005  * Redistribution and use in source and binary forms, with or without
00006  * modification, are permitted provided that the following conditions are met:
00007  *
00008  *     * Redistributions of source code must retain the above copyright
00009  *       notice, this list of conditions and the following disclaimer.
00010  *     * Redistributions in binary form must reproduce the above copyright
00011  *       notice, this list of conditions and the following disclaimer in the
00012  *       documentation and/or other materials provided with the distribution.
00013  *     * Neither the name of the Willow Garage, Inc. nor the names of its
00014  *       contributors may be used to endorse or promote products derived from
00015  *       this software without specific prior written permission.
00016  *
00017  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
00018  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
00019  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
00020  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
00021  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
00022  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
00023  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
00024  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
00025  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
00026  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00027  * POSSIBILITY OF SUCH DAMAGE.
00028  *
00029  */
00030 
00037 #include <pose_graph/pose_graph.h>
00038 #include <graph_slam/global_map.h>
00039 #include <graph_slam/constraint_generator.h>
00040 #include <pose_graph/utils.h>
00041 #include <graph_slam/scan_match_localization.h>
00042 #include <pose_graph/spa_conversion.h>
00043 #include <pose_graph/spa_2d_conversion.h>
00044 #include <pose_graph/transforms.h>
00045 #include <pose_graph/pose_graph_message.h>
00046 #include <pose_graph/Edge.h>
00047 #include <pose_graph/exception.h>
00048 #include <graph_slam/diff_publisher.h>
00049 #include <pose_graph/SaveGraph.h>
00050 #include <graph_slam/GenerateGlobalMap.h>
00051 #include <occupancy_grid_utils/ray_tracer.h>
00052 #include <ros/ros.h>
00053 #include <ros/time.h>
00054 #include <rosbag/view.h>
00055 #include <tf/transform_datatypes.h>
00056 #include <tf/transform_listener.h>
00057 #include <tf/transform_broadcaster.h>
00058 #include <sensor_msgs/LaserScan.h>
00059 #include <graph_slam/GraphLocalization.h>
00060 #include <laser_geometry/laser_geometry.h>
00061 #include <geometry_msgs/PoseStamped.h>
00062 #include <visualization_msgs/MarkerArray.h>
00063 #include <boost/thread.hpp>
00064 #include <boost/foreach.hpp>
00065 #include <boost/shared_ptr.hpp>
00066 #include <boost/optional.hpp>
00067 #include <string>
00068 #include <algorithm>
00069 #include <std_msgs/Int64.h>
00070 #include <std_msgs/UInt64.h>
00071 #include <queue>
00072 
00073 
00074 namespace graph_slam
00075 {
00076 
00077 namespace gu=occupancy_grid_utils;
00078 namespace sm=sensor_msgs;
00079 namespace gm=geometry_msgs;
00080 namespace nm=nav_msgs;
00081 namespace vm=visualization_msgs;
00082 namespace gs=graph_slam;
00083 
00084 using namespace pose_graph;
00085 
00086 
00087 using boost::format;
00088 using XmlRpc::XmlRpcValue;
00089 using std::string;
00090 using std::map;
00091 using Eigen3::Vector3d;
00092 using Eigen3::Quaterniond;
00093 using Eigen3::Affine3d;
00094 using Eigen3::Translation3d;
00095 using ros::NodeHandle;
00096 using ros::WallTimerEvent;
00097 using ros::WallDuration;
00098 using ros::Duration;
00099 using ros::Time;
00100 using std_msgs::Int64;
00101 using std_msgs::UInt64;
00102 using boost::optional;
00103 using gm::Pose;
00104 using gm::PoseStamped;
00105 using gu::LocalizedCloud;
00106 
00107 typedef boost::mutex::scoped_lock Lock;
00108 typedef set<NodeId> NodeSet;
00109 typedef boost::shared_ptr<nm::OccupancyGrid> GridPtr;
00110 typedef boost::shared_ptr<nm::OccupancyGrid const> GridConstPtr;
00111 
00112 /************************************************************
00113  * Constants
00114  ************************************************************/
00115 
00116 const double DEFAULT_POS_THRESHOLD = 2.0;
00117 const double DEFAULT_ANGLE_THRESHOLD = 1.57;
00118 const double STATUS_CHECK_INT = 5.0;
00119 
00120 /************************************************************
00121  * Class that holds node state
00122  ************************************************************/
00123 
00124 class GraphMapper
00125 {
00126 public:
00127 
00129   GraphMapper();
00130 
00131 private:
00132 
00133   /****************************************
00134    * Ops
00135    ****************************************/
00136   
00137   // Initialize state from a saved graph
00138   void initializeFromFile (const string& fname);
00139 
00140   // Set up the constraint generators from the parameter server
00141   void setupConstraintGenerators () ;
00142 
00143   // Add a new node if necessary
00144   void updateGraph (const WallTimerEvent& e);
00145 
00146   // Update the grid node data structures based on last_node_
00147   // Specifically, grid_node_to_nodes_, node_to_grid_node_, and current_grid_node_
00148   void updateGrid ();
00149 
00150   // Update last_fixed_frame_pose_ to fixed-frame pose at time t
00151   void updateFixedFramePose (const Time& t);
00152 
00153   // Actually add a node to graph and attach last scan
00154   void addNewNode ();
00155 
00156   // Check if two poses are far enough apart to be separate nodes
00157   bool sufficientlyFarApart (const Pose& p1, const Pose& p2) const;
00158 
00159   // Save the last laser scan
00160   void scanCallback (const sm::LaserScan::ConstPtr& scan);
00161 
00162   // Visualize grid containing a particular node
00163   void visualizeGridCallback (const UInt64& n);
00164 
00165   // Service callback for saving graph 
00166   bool saveGraph (SaveGraph::Request& req, SaveGraph::Response& resp);
00167 
00168   // Service for getting global grid
00169   bool generateMap (GenerateGlobalMap::Request& req, GenerateGlobalMap::Response& resp);
00170 
00171   // Publish visualization
00172   void visualize (const WallTimerEvent& e);
00173 
00174   // Repeatedly optimize poses
00175   void optimizePosesTimed (const WallTimerEvent& e);
00176 
00177   // Use graph optimizer to improve pose estimates.  Requires holding lock.
00178   void optimizePoses ();
00179 
00180   // Publish the current graph
00181   void publishLocalization (const WallTimerEvent& e);
00182 
00183   // Get the most recent fixed frame pose.
00184   PoseStamped currentFixedFramePose () const;
00185 
00186   // Periodically check that the node state looks ok
00187   void statusCheck (const WallTimerEvent& e);
00188 
00189   // Figure out which grid node corresponds to a given node
00190   optional<NodeId> getGridNode (const NodeId n) const;  
00191 
00192   // Tell the constraint generators that we switched to an existing node
00193   // Assumes lock is held
00194   void updateConstraintLocalizations ();
00195 
00196   /****************************************
00197    * Parameters
00198    ****************************************/
00199 
00200   // The frame in which scans come in
00201   const string sensor_frame_;
00202 
00203   // The base frame of the robot
00204   const string base_frame_;
00205 
00206   // A locally fixed frame (typically, odometry)
00207   const string fixed_frame_;
00208 
00209   // Min displacement (in meters) before new graph node created
00210   const double pos_threshold_;
00211 
00212   // Min angular displacement (in radians) before new graph node created
00213   const double angle_threshold_;
00214 
00215   // Whether to do optimization
00216   const bool optimize_;
00217 
00218   // Whether to use 3d optimizer
00219   const bool use_spa_3d_;
00220 
00221   // The side length in meters of the local grids 
00222   const double local_grid_size_;
00223 
00224   // The resolution of the local grids
00225   const double local_grid_resolution_;
00226 
00227   /****************************************
00228    * Local state; access locked by mutex_
00229    ****************************************/
00230 
00231   // Is graph initialized.  Only matters when reading a graph on startup.
00232   bool initialized_;
00233 
00234   // Pointer to last scan
00235   LaserPtr last_scan_;
00236 
00237   // Last known fixed frame pose
00238   optional<PoseStamped> last_fixed_frame_pose_;
00239 
00240   // Maps from node to fixed frame pose at that node
00241   NodePoseMap node_fixed_frame_poses_;
00242 
00243   // Last added node's id
00244   optional<NodeId> last_node_;
00245 
00246   // The graph that's built up over time
00247   PoseGraph graph_;
00248 
00249   // This holds the results of optimization
00250   // At any point, its keys will be a subset (since new nodes may have been added) of the nodes in graph_
00251   NodePoseMap optimized_node_poses_;
00252 
00253   // Maps each node to the associated grid node
00254   map<NodeId, NodeId> node_to_grid_node_;
00255   
00256   // Maps each grid node to the set of nodes whose clouds are overlaid to form the grid
00257   map<NodeId, NodeSet> grid_node_to_nodes_;
00258 
00259   // Current grid node (if there is one)
00260   optional<NodeId> current_grid_node_;
00261 
00262   // Localization state
00263   optional<LocalizationState> localization_;
00264 
00265   // Queue of unpublished diffs to the graph
00266   std::queue<PoseGraphDiff> diffs_;
00267 
00268   // Node whose grid we visualize next
00269   optional<NodeId> node_to_visualize_;
00270 
00271   // Unique id of graphs/diffs we publish
00272   unsigned current_graph_id_;
00273 
00274   /****************************************
00275    * Associated objects
00276    ****************************************/
00277 
00278   mutable boost::mutex mutex_; 
00279   NodeHandle nh_;
00280   tf::TransformListener tf_;
00281   ros::WallTimer update_timer_, visualization_timer_, optimization_timer_, graph_publication_timer_, 
00282     status_check_timer_;
00283   ros::Subscriber scan_sub_, visualize_grid_sub_;
00284   ros::Publisher vis_marker_pub_, marker_array_pub_, vis_scan_pub_, current_frame_pub_, local_grid_pub_;
00285   ros::ServiceServer graph_save_srv_, generate_map_srv_;
00286   tf::TransformBroadcaster tf_broadcaster_;
00287   vector<ConstraintGenPtr> constraint_generators_;
00288   boost::shared_ptr<ScanMatchLocalization> localizer_;
00289   DiffPublisher diff_pub_;
00290   
00291 
00292 };
00293 
00294 
00295 /************************************************************
00296  * Utilities
00297  ***********************************************************/
00298 
00299 unsigned getUniqueId () 
00300 {
00301   static unsigned id=0;
00302   return id++;
00303 }
00304   
00305 
00306 /************************************************************
00307  * Initialization
00308  ************************************************************/
00309 
00310 template <class T>
00311 T getPrivateParam (const string& name, const T& default_val)
00312 {
00313   T val;
00314   NodeHandle nh("~");
00315   nh.param(name, val, default_val);
00316   ROS_DEBUG_STREAM_NAMED ("init", "Set param " << name << " to value " 
00317                           << val << " (default was " << default_val << ")");
00318   return val;
00319 }
00320 
00321 
00322 void GraphMapper::setupConstraintGenerators () 
00323 {
00324   NodeHandle private_nh("~");
00325   XmlRpcValue constraint_sources;
00326   if (!private_nh.getParam("constraint_sources", constraint_sources))
00327     throw NonexistentParamException("~constraint_sources");
00328   const XmlRpcValue::Type type = constraint_sources.getType();
00329   if (type != XmlRpcValue::TypeArray)
00330     throw PoseGraphException(format("~constraint_sources parameter was not an array (type was %1%)") % type);
00331   for (int i=0; i<constraint_sources.size(); i++) {
00332     XmlRpcValue source = constraint_sources[i];
00333     const XmlRpcValue::Type type = source.getType();
00334     if (type != XmlRpcValue::TypeString)
00335       throw PoseGraphException(format("~constraint_sources[%1%] was not a string (type was %1%)") % type);
00336 
00337     const NodeHandle source_nh(private_nh, (string) source);
00338     ROS_DEBUG_STREAM_NAMED ("init", "Initializing constraint " << (string) source);
00339     constraint_generators_.push_back(createConstraintGenerator(source_nh));
00340   }
00341 }
00342 
00343   
00344 void GraphMapper::initializeFromFile (const string& fname)
00345 {
00346   Lock lock(mutex_);
00347   ROS_DEBUG_STREAM_NAMED ("init", "Reading graph from " << fname);
00348   graph_ = readFromFile(fname);
00349   const NodeSet nodes = graph_.allNodes();
00350   BOOST_FOREACH (const NodeId n, nodes) {
00351     node_fixed_frame_poses_[n] = graph_.getInitialPoseEstimate(n);
00352     ROS_ASSERT_MSG (graph_.hasScan(n), "Graph doesn't have scan for node %lu", n.getId());
00353     localizer_->addNode(n, graph_.getScan(n));
00354   }
00355   // Hack: rather than localizing, we just assume we're at node 1
00356   const NodeId n1 = *nodes.begin();
00357   last_node_ = n1;
00358   vector<NodeId> seq;
00359   seq.push_back(n1);
00360   BOOST_FOREACH (ConstraintGenPtr gen, constraint_generators_) {
00361     gen->initializeFromGraph(graph_, seq);
00362   }  
00363   ROS_DEBUG_STREAM_NAMED ("init", "Read graph with " << nodes.size() << " nodes; assuming we're at " << n1);
00364   optimizePoses();
00365   localization_ = LocalizationState(n1, poseToWorldTransform(node_fixed_frame_poses_[n1]),
00366                                     convertToPose(makePose2D(0, 0, 0)), ros::Time::now());
00367   while (!last_fixed_frame_pose_)
00368     updateFixedFramePose(ros::Time::now());
00369   updateConstraintLocalizations();
00370   diff_pub_.setGraph(graph_);
00371 }
00372 
00373 void GraphMapper::updateConstraintLocalizations ()
00374 {
00375   gs::GraphLocalization loc;
00376   loc.node = localization_->ref_node.getId();
00377   loc.offset = localization_->node_frame_pose;
00378   loc.fixed_frame_pose = last_fixed_frame_pose_->pose;
00379   
00380   BOOST_FOREACH (ConstraintGenPtr gen, constraint_generators_) 
00381     gen->updateLocalization(loc);
00382 }
00383 
00384 double getLocalizationRate ()
00385 {
00386   return getPrivateParam("localization_pub_rate", 5.0);
00387 }
00388 
00389 unsigned getLocalizationsPerScanMatch ()
00390 {
00391   unsigned l = ceil(getLocalizationRate()/getPrivateParam("localization_scan_match_rate", 5.0));
00392   ROS_DEBUG_STREAM_NAMED ("init", "We'll do scan matching every " << l << " localizations");
00393   return l;
00394 }
00395 
00396 
00397 
00398 
00399 GraphMapper::GraphMapper () :
00400   sensor_frame_(getPrivateParam<string>("sensor_frame", "base_laser_link")),
00401   base_frame_(getPrivateParam<string>("base_frame", "base_footprint")),
00402   fixed_frame_(getPrivateParam<string>("fixed_frame", "odom")),
00403   pos_threshold_(getPrivateParam<double>("position_displacement_threshold", DEFAULT_POS_THRESHOLD)),
00404   angle_threshold_(getPrivateParam<double>("angle_displacement_threshold", DEFAULT_ANGLE_THRESHOLD)),
00405   optimize_(getPrivateParam<bool>("optimize", true)),
00406   use_spa_3d_(getPrivateParam<bool>("use_spa_3d", true)),
00407   local_grid_size_(getPrivateParam("local_grid_size", 20.0)),
00408   local_grid_resolution_(getPrivateParam("local_grid_resolution", 0.05)),
00409   initialized_(false), update_timer_(nh_.createWallTimer(WallDuration(0.5), &GraphMapper::updateGraph, this)),
00410   visualization_timer_(nh_.createWallTimer(WallDuration(2.0), &GraphMapper::visualize, this)),
00411   optimization_timer_(nh_.createWallTimer(WallDuration(1.0), &GraphMapper::optimizePosesTimed, this)),
00412   graph_publication_timer_(nh_.createWallTimer(WallDuration(1/getLocalizationRate()), &GraphMapper::publishLocalization, this)),
00413   status_check_timer_(nh_.createWallTimer(WallDuration(STATUS_CHECK_INT), &GraphMapper::statusCheck, this)),
00414   scan_sub_(nh_.subscribe("scan", 1, &GraphMapper::scanCallback, this)),
00415   visualize_grid_sub_(nh_.subscribe("visualize_grid", 10, &GraphMapper::visualizeGridCallback, this)),
00416   vis_marker_pub_(nh_.advertise<vm::Marker> ("visualization_marker", 10)),
00417   marker_array_pub_(nh_.advertise<vm::MarkerArray> ("visualization_marker_array", 1000)),
00418   vis_scan_pub_(nh_.advertise<sm::PointCloud> ("pose_graph_scans", 100)),
00419   current_frame_pub_(nh_.advertise<Int64> ("current_node", 10)),
00420   local_grid_pub_(nh_.advertise<nm::OccupancyGrid> ("graph_mapper_local_grid", 10)),
00421   graph_save_srv_(nh_.advertiseService("save_graph", &GraphMapper::saveGraph, this)),
00422   generate_map_srv_(nh_.advertiseService("generate_map", &GraphMapper::generateMap, this)),
00423   diff_pub_(nh_)
00424 {
00425   // Get the laser's pose, relative to base.
00426   tf::Stamped<tf::Pose> ident;
00427   tf::Stamped<tf::Transform> laser_to_base;
00428   ident.setIdentity();
00429   ident.frame_id_ = sensor_frame_;
00430   ident.stamp_ = ros::Time();
00431 
00432   {
00433     bool received = false;
00434     while (!received) { // 
00435       if (!tf_.waitForTransform(base_frame_, ident.frame_id_, ros::Time::now(), ros::Duration(10.0))) {
00436         ROS_WARN_STREAM ("Still waiting for transformn betwee " << sensor_frame_ << " and " << base_frame_ <<
00437                          " during graph mapper initialization");
00438         ros::WallDuration(1.0).sleep();
00439       }
00440       else
00441         received = true;
00442     }
00443   }
00444   
00445   tf_.transformPose(base_frame_, ident, laser_to_base);
00446   gm::Pose2D laser_pose;
00447   laser_pose.x = laser_to_base.getOrigin().x();
00448   laser_pose.y = laser_to_base.getOrigin().y();
00449   laser_pose.theta = tf::getYaw(laser_to_base.getRotation());
00450   localizer_.reset(new ScanMatchLocalization(laser_pose, 5.0, getLocalizationsPerScanMatch()));
00451   
00452   string input_file = getPrivateParam("input_file", string());
00453   setupConstraintGenerators();
00454   if (!input_file.empty())
00455     initializeFromFile (input_file);
00456   initialized_ = true;
00457 }
00458 
00459 /************************************************************
00460  * Callbacks
00461  ************************************************************/
00462 
00463 // Store the most recent laser scan
00464 void GraphMapper::scanCallback (const LaserPtr& scan)
00465 {
00466   Lock lock(mutex_);
00467   ROS_DEBUG_COND_NAMED (!last_scan_, "pose_graph_node", "First scan received");
00468   last_scan_ = scan;
00469   updateFixedFramePose(scan->header.stamp);
00470 }
00471 
00472 // Visualize the local grid for a particular node
00473 void GraphMapper::visualizeGridCallback (const UInt64& n) 
00474 {
00475   node_to_visualize_ = NodeId(n.data);
00476 }
00477 
00478 bool GraphMapper::saveGraph (SaveGraph::Request& req, SaveGraph::Response& resp)
00479 {
00480   if (initialized_) {
00481     Lock lock(mutex_);
00482     rosbag::Bag bag(req.filename, rosbag::bagmode::Write);
00483     bag.write("graph", ros::Time::now(), poseGraphToRos(graph_));
00484     resp.succeeded = true;
00485   }
00486   else {
00487     ROS_WARN ("Can't save graph yet as it's not initialized");
00488     resp.succeeded = false;
00489   }
00490   return true;
00491 }
00492 
00493 bool GraphMapper::generateMap (GenerateGlobalMap::Request& req, GenerateGlobalMap::Response& resp)
00494 {
00495   boost::shared_ptr<PoseGraph> copy;
00496   NodePoseMap opt_poses;
00497   
00498   // While holding lock, make copies of relevant objects
00499   {
00500     Lock lock(mutex_);
00501     copy.reset(new PoseGraph(graph_));
00502     opt_poses = optimized_node_poses_;
00503   }
00504 
00505   if (initialized_ && !opt_poses.empty()) {
00506     resp.map = *generateGlobalMap(*copy, opt_poses, req.resolution);
00507     resp.succeeded = true;
00508     local_grid_pub_.publish(resp.map);
00509   }
00510   else {
00511     ROS_WARN ("Can't generate global map as not yet fully initialized");
00512     resp.succeeded = false;
00513   }
00514 
00515   return true;
00516 }
00517 
00518              
00519 /************************************************************
00520  * Thread 1: periodically add a new node to graph
00521  * if we're far enough away from the last one, and connect
00522  * it to previous nodes by calling the constraint generators
00523  ************************************************************/
00524 
00525 void GraphMapper::updateGraph (const WallTimerEvent& e)
00526 {
00527   Pose id;
00528   id.orientation.w = 1.0;
00529   Lock lock(mutex_);
00530   if (!initialized_ || !last_scan_)
00531     return; // statusCheck will periodically print a warning in this case
00532   if (!last_fixed_frame_pose_)
00533     return; // A warning will have been printed in update
00534   else if (Time::now() - last_fixed_frame_pose_->header.stamp > Duration(0.2))
00535     ROS_WARN_STREAM ("Fixed frame pose " << *last_fixed_frame_pose_ << " out of date.  Skipping graph update.");
00536 
00537   if (!localization_ || sufficientlyFarApart(id, localization_->node_frame_pose)) {
00538 
00539     optimizePoses();
00540     addNewNode();
00541 
00542     // Also create the diff object to publish
00543     PoseGraphDiff diff;
00544     diff.id = current_graph_id_++;
00545     if (graph_.hasCloud(*last_node_)) {
00546       diff.cloud_ids.push_back(last_node_->getId());
00547       diff.clouds.push_back(*graph_.getCloud(*last_node_));
00548     }
00549     Node diff_node;
00550     diff_node.id = last_node_->getId();
00551     diff_node.initial_pose_estimate = graph_.getInitialPoseEstimate(*last_node_);
00552     diff.new_nodes.push_back(diff_node);
00553     
00554     BOOST_FOREACH (ConstraintGenPtr gen, constraint_generators_) {
00555       NodeConstraintVector constraints = gen->getConstraints(graph_, *last_node_, optimized_node_poses_);
00556       BOOST_FOREACH (const NodeConstraint& c, constraints) {
00557         const EdgeId e = graph_.addEdge(c.target, *last_node_, c.constraint);
00558         
00559         // Update the diff
00560         Edge diff_edge;
00561         diff_edge.src = c.target.getId();
00562         diff_edge.dest = last_node_->getId();
00563         diff_edge.constraint = constraintToRos(c.constraint);
00564         diff_edge.id = e.getId();
00565         diff.new_edges.push_back(diff_edge);
00566       }
00567     }
00568     diff_pub_.addDiff(&diff);
00569 
00570     // Update grid for visualization
00571     updateGrid();
00572   }
00573 }
00574 
00575 // Update the variable last_fixed_frame_pose_ to hold the
00576 // fixed frame pose at time t
00577 // Caller must hold a lock on mutex_
00578 void GraphMapper::updateFixedFramePose (const Time& t)
00579 {
00580   if (tf_.waitForTransform(fixed_frame_, base_frame_, t, ros::Duration(1.0))) {
00581     PoseStamped p;
00582     p.pose.position.x = p.pose.position.y = p.pose.position.z = 0.0;
00583     p.pose.orientation = tf::createQuaternionMsgFromYaw(0.0);
00584     p.header.frame_id = base_frame_;
00585     p.header.stamp = t;
00586     tf::Stamped<tf::Pose> base_pose, fixed_pose;
00587     tf::poseStampedMsgToTF(p, base_pose);
00588 
00590     tf_.transformPose(fixed_frame_, base_pose, fixed_pose);
00591 
00592     gm::PoseStamped fixed_pose_msg;
00593     tf::poseStampedTFToMsg(fixed_pose, fixed_pose_msg);
00594     last_fixed_frame_pose_ = fixed_pose_msg;
00595     
00596     ROS_DEBUG_STREAM_NAMED ("pose_graph_update_fixed_frame_pose", "Updated fixed frame pose to "
00597                             << toString(last_fixed_frame_pose_->pose) << " with stamp "
00598                             << last_fixed_frame_pose_->header.stamp.toSec());
00599                                       
00600   }
00601   else {
00602     ROS_WARN_STREAM ("Skipping update of fixed frame pose due to lack of transform from " << 
00603                      base_frame_ << " to " << fixed_frame_);
00604   }
00605 }
00606 
00607 
00608 // Are two poses sufficiently far apart that a new node should be added to the graph?
00609 bool GraphMapper::sufficientlyFarApart (const Pose& p1, const Pose& p2) const
00610 {
00611   const double dx = p1.position.x - p2.position.x;
00612   const double dy = p1.position.y - p2.position.y;
00613   const double dtheta = abs(tf::getYaw(p1.orientation) - tf::getYaw(p2.orientation));
00614   return (dx*dx + dy*dy > pos_threshold_*pos_threshold_) || (dtheta > angle_threshold_);
00615 }
00616 
00617 // Add a new node at the current position in the graph
00618 void GraphMapper::addNewNode ()
00619 {
00620   ROS_ASSERT (last_scan_ && last_fixed_frame_pose_);
00621   ROS_ASSERT_MSG (last_scan_->header.frame_id == sensor_frame_, 
00622                   "Code currently assumes scans are in default sensor frame");
00623 
00624   /****************************************
00625    * 1. Add the node itself
00626    ****************************************/
00627 
00628   NodeId n = graph_.addNode();
00629   graph_.attachScan(n, last_scan_);
00630   graph_.setInitialPoseEstimate(n, last_fixed_frame_pose_->pose);
00631   node_fixed_frame_poses_[n] = last_fixed_frame_pose_->pose;
00632   ros::Time node_stamp = last_fixed_frame_pose_->header.stamp;
00633   ROS_DEBUG_NAMED ("pose_graph_node_update", 
00634                    "Added node %lu with stamp %.2f, at [%.2f, %.2f, %.2f].",
00635                    n.getId(), node_stamp.toSec(), last_fixed_frame_pose_->pose.position.x, 
00636                    last_fixed_frame_pose_->pose.position.y, 
00637                    tf::getYaw(last_fixed_frame_pose_->pose.orientation));
00638   last_node_ = n;
00639   
00640 
00641   /****************************************
00642    * 2. Store the projected and transformed
00643    * laser scan
00644    ****************************************/
00645 
00646   // We want to store the entire cloud with respect to the current sensor frame
00647   // Unfortunately, transformLaserScanToPointCloud assumes the target frame is fixed
00648   // So we first project to a (more or less) fixed frame, then transform to the 
00649   // sensor frame at a particular time point
00650 
00651   try {
00652     const ros::Time start = last_scan_->header.stamp;
00653     const ros::Time end = start + ros::Duration(last_scan_->ranges.size()*last_scan_->time_increment);
00654     const ros::Duration wait(2.0);
00655 
00656     if (!tf_.waitForTransform(fixed_frame_, sensor_frame_, start, wait)) {
00657       ROS_WARN_STREAM ("Timed out waiting for transform from sensor to fixed frame at start time " <<
00658                        start << " of laser scan");
00659     }
00660     else if (!tf_.waitForTransform(fixed_frame_, sensor_frame_, end, ros::Duration(2.0))) {
00661       ROS_WARN_STREAM ("Timed out waiting for transform from sensor to fixed frame at end time " <<
00662                        end << " of laser scan");
00663     }
00664 
00665     // We think we can transform the cloud to the fixed frame 
00666     else {
00667 
00668       sm::PointCloud fixed_frame_cloud;
00669       laser_geometry::LaserProjection projector_;
00670       projector_.transformLaserScanToPointCloud (fixed_frame_, *last_scan_, fixed_frame_cloud, tf_);
00671 
00672       if (!tf_.waitForTransform(sensor_frame_, fixed_frame_, node_stamp, wait)) {
00673         ROS_WARN_STREAM ("Timed out waiting for transform from " << fixed_frame_ << " to " << sensor_frame_
00674                          << " at " << node_stamp);
00675       }
00676 
00677       // We think we can transform the (now fixed) cloud from the fixed to the sensor frame
00678       else {
00679 
00680         LocalizedCloud::Ptr sensor_frame_cloud(new LocalizedCloud());
00681         tf_.transformPointCloud (sensor_frame_, node_stamp, fixed_frame_cloud, 
00682                                  fixed_frame_, sensor_frame_cloud->cloud);
00683         geometry_msgs::PoseStamped id, sensor_pose;
00684         id.header.stamp = ros::Time();
00685         id.header.frame_id = sensor_frame_;
00686         id.pose.orientation.w = 1.0; // id is now the identity pose
00687         tf_.transformPose(base_frame_, id, sensor_pose);
00688         sensor_frame_cloud->sensor_pose = sensor_pose;
00689 
00690         graph_.attachCloud(n, sensor_frame_cloud);
00691       }
00692     }
00693   }
00694 
00695   // Because the above has race conditions.  This will become unnecessary once we have tf2.
00696   catch (tf::TransformException& e) {
00697     ROS_WARN_STREAM ("Received tf exception '" << e.what() <<
00698                      "' when trying to transform scan to cloud.  Skipping");
00699   }
00700 
00701   /****************************************
00702    * 3. Set the localization
00703    ****************************************/
00704   
00705   const Eigen3::Affine3d node_to_fixed_frame = poseToWorldTransform(node_fixed_frame_poses_[n]);
00706   localizer_->addNode(n, last_scan_);
00707   Pose identity;
00708   identity.orientation.w = 1.0;
00709   localization_ = LocalizationState(n, node_to_fixed_frame, identity, last_scan_->header.stamp);
00710   // In this case we don't have to update the constraint generators since we will call getconstraints
00711   // \todo Really needs to be made simpler
00712 
00713   ROS_DEBUG_STREAM_NAMED ("localization_switch", "Switching to newly added node " << n);
00714 
00715 }
00716 
00717 
00718 bool withinDistance (const PoseGraph& g, const NodeId n1, const NodeId n2, const double d)
00719 {
00720   ShortestPathResult res=g.shortestPath(n1, n2);
00721   return res.path_found && res.cost < d;
00722 }
00723 
00724 
00725 void GraphMapper::updateGrid ()
00726 {  
00727   // Start up a new grid if we're too far from the current one
00728   if (!(current_grid_node_ && 
00729         withinDistance(graph_, *current_grid_node_, *last_node_, local_grid_size_/3))) {
00730     current_grid_node_ = *last_node_;
00731     grid_node_to_nodes_[*last_node_] = NodeSet();
00732   }
00733 
00734   // Add this node to the current grid
00735   node_to_grid_node_[*last_node_] = *current_grid_node_;
00736   grid_node_to_nodes_[*current_grid_node_].insert(*last_node_);
00737 }
00738 
00739 
00740 /************************************************************
00741  * Thread 2: periodically optimize the entire
00742  * graph using sparse pose adjustment
00743  ************************************************************/
00744 
00745 
00746 void GraphMapper::optimizePosesTimed (const WallTimerEvent& e)
00747 {
00748   if (initialized_) {
00749     Lock lock(mutex_);
00750     optimizePoses();
00751   }
00752 }
00753 
00754 void GraphMapper::optimizePoses ()
00755 {
00756   if (optimize_) {
00757     optimized_node_poses_ =
00758       use_spa_3d_ ?
00759       optimizeGraph(graph_) :
00760       optimizeGraph2D(graph_);
00761   }
00762   else
00763     optimized_node_poses_ = node_fixed_frame_poses_;
00764 }
00765 
00766 
00767 
00768 /************************************************************
00769  * Thread 3: publish visualization
00770  ************************************************************/
00771 
00772 
00773 // Say you have a node at pose p and an outgoing edge labelled with constraint c.
00774 // This returns the expected position of the node on the other side of the edge.
00775 Pose applyConstraint (const Pose& p, const PoseConstraint& c)
00776 {
00777   const Affine3d trans = poseToWorldTransform(p);
00778   const Pose rel_pose = getPose(c);
00779   return applyTransform(trans, rel_pose);
00780 }
00781 
00782 
00783 void addTransformedPoints (LocalizedCloud::ConstPtr cloud, const Pose& pose, sm::PointCloud* cloud_out)
00784 {
00785   const Affine3d base_to_world = poseToWorldTransform(pose);
00786   const Affine3d sensor_to_base = poseToWorldTransform(cloud->sensor_pose.pose);
00787   const Affine3d sensor_to_world = base_to_world*sensor_to_base;
00788 
00789   BOOST_FOREACH (const gm::Point32 pt, cloud->cloud.points) 
00790     cloud_out->points.push_back(transformPoint(sensor_to_world, pt));
00791 }
00792 
00793 
00794 // Visualize graph and scans
00795 void GraphMapper::visualize (const WallTimerEvent& e) 
00796 {
00797   if (!initialized_)
00798     return;
00799   
00800   const unsigned NODE_MARKER_ID_BASE = 1000;
00801   
00802   Lock lock(mutex_);
00803   vm::Marker edges, loc;
00804   vm::Marker node_prototype;
00805   vm::MarkerArray nodes;
00806   sm::PointCloud cloud;
00807 
00808   unsigned next_ind=0;
00809   edges.header.frame_id = node_prototype.header.frame_id = loc.header.frame_id = OPTIMIZED_FRAME;
00810   node_prototype.id = -42;
00811   edges.id = next_ind++;
00812   loc.id = next_ind++;
00813   node_prototype.header.stamp = edges.header.stamp = loc.header.stamp = Time::now();
00814   edges.ns = node_prototype.ns = loc.ns = "pose_graph";
00815   node_prototype.type = vm::Marker::SPHERE;
00816   edges.type = vm::Marker::LINE_LIST;
00817   loc.type = vm::Marker::ARROW;
00818   edges.action = node_prototype.action = loc.action = vm::Marker::ADD;
00819   edges.scale.x = 0.05;
00820   node_prototype.scale.x = 0.1;
00821   node_prototype.scale.y = 0.1;
00822   node_prototype.scale.z = 0.1;
00823   node_prototype.pose.orientation.w = 1.0;
00824   loc.scale.x = 0.1;
00825   loc.scale.y = 0.2;
00826   edges.color.b = node_prototype.color.r = 1.0;
00827   loc.color.r = .64;
00828   loc.color.g = .1;
00829   loc.color.b = .9;
00830   edges.color.a = loc.color.a = node_prototype.color.a = 1.0;
00831 
00832 
00833   if (optimized_node_poses_.size() > 0) {
00834 
00835 
00836     BOOST_FOREACH (const NodePoseMap::value_type& entry, optimized_node_poses_) {
00837 
00838       // Publish sphere at node position
00839       const NodeId n = entry.first;
00840       const Pose pose = entry.second;
00841       vm::Marker node = node_prototype;
00842       const size_t node_ind = nodes.markers.size();
00843       nodes.markers.resize(node_ind+1);
00844       nodes.markers[node_ind] = node_prototype;
00845       nodes.markers[node_ind].pose.position = pose.position;
00846       nodes.markers[node_ind].id = n.getId() + NODE_MARKER_ID_BASE;
00847 
00848       // Add scan at node to point cloud
00849       if (graph_.hasCloud(n))
00850         addTransformedPoints(graph_.getCloud(n), pose, &cloud);
00851       // This loops over the constraints, and for each one that is rooted at the current node, draws a line
00852       BOOST_FOREACH (const EdgeId e, graph_.incidentEdges(n)) {
00853         if (graph_.incidentNodes(e).first == n) {
00854           edges.points.push_back(pose.position);
00855           const Pose pose2 = applyConstraint(entry.second, graph_.getConstraint(e));
00856           edges.points.push_back(pose2.position);
00857         }
00858       }
00859     }
00860 
00861 
00862     // Localization
00863     if (localization_) {
00864       NodePoseMap::const_iterator iter=optimized_node_poses_.find(localization_->ref_node);
00865       if (iter!=optimized_node_poses_.end()) {
00866         loc.points.push_back(iter->second.position);
00867         Eigen3::Affine3d node_to_opt = poseToWorldTransform(iter->second);
00868         loc.points.push_back(applyTransform(node_to_opt, localization_->node_frame_pose).position);
00869       }
00870     }
00871     else
00872       loc.action = vm::Marker::DELETE;
00873   }
00874 
00875   marker_array_pub_.publish(nodes);
00876   vis_marker_pub_.publish(edges);
00877   vis_marker_pub_.publish(loc);
00878 
00879   cloud.header.frame_id = OPTIMIZED_FRAME;
00880   cloud.header.stamp = ros::Time::now();
00881   vis_scan_pub_.publish(cloud);
00882 
00883 }
00884 
00885 optional<NodeId> GraphMapper::getGridNode (const NodeId n) const
00886 {
00887   optional<NodeId> g;
00888   typedef map<NodeId, set<NodeId> > GridMap;
00889   BOOST_FOREACH (const GridMap::value_type& e, grid_node_to_nodes_) {
00890     if (contains(e.second, n)) {
00891       g = e.first;
00892       break;
00893     }
00894   }
00895   return g;
00896 }
00897 
00898 
00899 
00900 
00901 /************************************************************
00902  * Thread 4: publish localization and tf frames
00903  ************************************************************/
00904 
00905 void TransformEigenToTF(const Eigen3::Affine3d &k, tf::Transform &t)
00906 {
00907   t.setOrigin(tf::Vector3(k.matrix()(0,3), k.matrix()(1,3), k.matrix()(2,3)));
00908   t.setBasis(btMatrix3x3(k.matrix()(0,0), k.matrix()(0,1),k.matrix()(0,2),k.matrix()(1,0), k.matrix()(1,1),k.matrix()(1,2),k.matrix()(2,0), k.matrix()(2,1),k.matrix()(2,2)));
00909 }
00910 
00911 void GraphMapper::publishLocalization (const WallTimerEvent& e)
00912 {
00913   if (!initialized_)
00914     return;
00915   Lock lock(mutex_);
00916 
00917   if (localization_ && last_fixed_frame_pose_) {
00918     // Make sure ref node has an optimized pose.  Ok since we hold the lock here.
00919     if (!contains(optimized_node_poses_, localization_->ref_node))
00920       optimizePoses();
00921 
00922     const NodeId old_ref_node = localization_->ref_node;
00923     localization_ = localizer_->nextState(*localization_, last_fixed_frame_pose_->pose,
00924                                              last_scan_, graph_, optimized_node_poses_);
00925     // \todo This is overly complicated and error-prone.  The interface to constraint generators really needs to be simplified.
00926     if (old_ref_node != localization_->ref_node) 
00927       updateConstraintLocalizations();
00928       
00929       
00930     ROS_DEBUG_STREAM_NAMED ("localization", "Localization is now " << toString2D(localization_->node_frame_pose)
00931                             << " w.r.t. " << localization_->ref_node);
00932     
00933     // Publish the identity of the current node
00934     Int64 frame_msg;
00935     frame_msg.data = localization_->ref_node.getId();
00936     current_frame_pub_.publish(frame_msg);
00937 
00938     std::vector<tf::StampedTransform> transforms;
00939     // Publish transform from optimized graph to fixed frame such that the odometric pose and
00940     // the node-frame pose of the current localization agree
00941     {
00942       tf::Transform opt_to_fixed;
00943       Affine3d opt_to_node(poseToWorldTransform(keyValue(optimized_node_poses_, localization_->ref_node)).inverse());
00944       TransformEigenToTF(localization_->node_to_fixed_frame*opt_to_node, opt_to_fixed);
00945       tf::StampedTransform stamped(opt_to_fixed, localization_->stamp, fixed_frame_, OPTIMIZED_FRAME);
00946       transforms.push_back(stamped);
00947     }
00948 
00949     // Publish optimized node poses
00950     const ros::Time now = ros::Time::now();
00951     BOOST_FOREACH (const NodePoseMap::value_type& e, optimized_node_poses_) {
00952       tf::Transform pose_to_opt;
00953       tf::poseMsgToTF(e.second, pose_to_opt);
00954       tf::StampedTransform stamped(pose_to_opt, now, OPTIMIZED_FRAME, PoseGraph::nodeFrameName(e.first));
00955       transforms.push_back(stamped);
00956     }
00957 
00958     
00959     tf_broadcaster_.sendTransform(transforms);
00960   }
00961 }
00962 
00963 
00964 /************************************************************
00965  * Thread 5: Occasionally check that everything looks ok
00966  ************************************************************/
00967 
00968 void GraphMapper::statusCheck (const WallTimerEvent& e)
00969 {
00970   static unsigned count=0;
00971   if (count++) {
00972     if (!last_node_ || !localization_ || !initialized_) {
00973       ROS_WARN_COND_NAMED (!last_node_, "graph_mapper_status_check", "No nodes added yet.");
00974       ROS_WARN_COND_NAMED (!localization_ && last_node_, "graph_mapper_status_check", "No localization");
00975       ROS_WARN_STREAM_NAMED ("graph_mapper_status_check", "scan_received = " << (last_scan_.get()!=0)
00976                              << ", fixed_frame_pose_valid_ = " << (bool) last_fixed_frame_pose_ <<
00977                              ", initialized_ = " << initialized_);
00978     }
00979   }
00980 }
00981 
00982 } // namespace graph_slam
00983 
00984 
00985 
00986 
00987 
00988 int main (int argc, char** argv)
00989 {
00990   ros::init(argc, argv, "graph_mapper");
00991   ros::AsyncSpinner spinner(3);
00992   spinner.start();
00993   graph_slam::GraphMapper node;
00994   ROS_INFO ("Initialization complete");
00995   ros::waitForShutdown();
00996   return 0;
00997 }


graph_slam
Author(s): Bhaskara Marthi
autogenerated on Tue Jan 7 2014 11:17:21