mapper.cpp
Go to the documentation of this file.
00001 #include <fstream>
00002 
00003 #include <boost/thread.hpp>
00004 #include <boost/thread/future.hpp>
00005 
00006 #include "ros/ros.h"
00007 #include "ros/console.h"
00008 #include "pointmatcher/PointMatcher.h"
00009 #include "pointmatcher/Timer.h"
00010 
00011 #include "pointmatcher_ros/point_cloud.h"
00012 #include "pointmatcher_ros/transform.h"
00013 #include "pointmatcher_ros/get_params_from_server.h"
00014 #include "pointmatcher_ros/aliases.h"
00015 #include "pointmatcher_ros/ros_logger.h"
00016 
00017 #include "nav_msgs/Odometry.h"
00018 #include "tf/transform_broadcaster.h"
00019 #include "tf_conversions/tf_eigen.h"
00020 #include "tf/transform_listener.h"
00021 #include "tf/transform_broadcaster.h"
00022 
00023 #include "std_msgs/String.h"
00024 #include "std_srvs/Empty.h"
00025 #include "map_msgs/GetPointMap.h"
00026 #include "map_msgs/SaveMap.h"
00027 
00028 using namespace std;
00029 using namespace PointMatcherSupport;
00030 
00031 class Mapper
00032 {
00033         ros::NodeHandle& n;
00034         ros::NodeHandle& pn;
00035         
00036         ros::Subscriber scanSub;
00037         ros::Subscriber cloudSub;
00038         ros::Publisher mapPub;
00039         ros::Publisher odomPub;
00040         ros::ServiceServer getPointMapSrv;
00041         ros::ServiceServer saveMapSrv;
00042         ros::ServiceServer resetSrv;
00043 
00044         PM::DataPointsFilters inputFilters;
00045         PM::DataPointsFilters mapPreFilters;
00046         PM::DataPointsFilters mapPostFilters;
00047         unique_ptr<PM::Transformation> transformation;
00048         ros::Time mapCreationTime;
00049         PM::DataPoints *mapPointCloud;
00050         PM::ICPSequence icp;
00051         
00052         // multi-threading mapper
00053         typedef boost::packaged_task<PM::DataPoints*> MapBuildingTask;
00054         typedef boost::unique_future<PM::DataPoints*> MapBuildingFuture;
00055         boost::thread mapBuildingThread;
00056         MapBuildingTask mapBuildingTask;
00057         MapBuildingFuture mapBuildingFuture;
00058         bool mapBuildingInProgress;
00059         bool changeModel;
00060 
00061         // Parameters
00062         int minReadingPointCount;
00063         int minMapPointCount;
00064         double minOverlap;
00065         double maxOverlapToMerge;
00066         double tfPublishPeriod;
00067         string odomFrame;
00068         string mapFrame;
00069         string vtkFinalMapName; 
00070         bool useConstMotionModel; 
00071 
00072         PM::TransformationParameters TOdomToMap;
00073         boost::thread publishThread;
00074         boost::mutex publishLock;
00075         
00076         tf::TransformListener tfListener;
00077         tf::TransformBroadcaster tfBroadcaster;
00078         
00079 public:
00080         Mapper(ros::NodeHandle& n, ros::NodeHandle& pn);
00081         ~Mapper();
00082         
00083 protected:
00084         void gotScan(const sensor_msgs::LaserScan& scanMsgIn);
00085         void gotCloud(const sensor_msgs::PointCloud2& cloudMsgIn);
00086         void processCloud(unique_ptr<DP> cloud, const std::string& scannerFrame, const ros::Time& stamp, uint32_t seq);
00087         void processNewMapIfAvailable();
00088         void setMap(DP* newPointCloud);
00089         DP* updateMap(DP* newPointCloud, const PM::TransformationParameters Ticp, bool updateExisting);
00090         
00091         void publishLoop(double publishPeriod);
00092         void publishTransform();
00093         
00094         bool getPointMap(map_msgs::GetPointMap::Request &req, map_msgs::GetPointMap::Response &res);
00095         bool saveMap(map_msgs::SaveMap::Request &req, map_msgs::SaveMap::Response &res);
00096         bool reset(std_srvs::Empty::Request &req, std_srvs::Empty::Response &res);
00097 };
00098 
00099 Mapper::Mapper(ros::NodeHandle& n, ros::NodeHandle& pn):
00100         n(n),
00101         pn(pn),
00102         transformation(PM::get().REG(Transformation).create("RigidTransformation")),
00103         mapPointCloud(0),
00104         mapBuildingInProgress(false),
00105         changeModel(false),
00106         minReadingPointCount(getParam<int>("minReadingPointCount", 2000)),
00107         minMapPointCount(getParam<int>("minMapPointCount", 500)),
00108         minOverlap(getParam<double>("minOverlap", 0.5)),
00109         maxOverlapToMerge(getParam<double>("maxOverlapToMerge", 0.9)),
00110         tfPublishPeriod(getParam<double>("tfPublishPeriod", 0.1)),
00111         odomFrame(getParam<string>("odom_frame", "odom")),
00112         mapFrame(getParam<string>("map_frame", "map")),
00113         vtkFinalMapName(getParam<string>("vtkFinalMapName", "finalMap.vtk")),
00114         useConstMotionModel(getParam<bool>("useConstMotionModel", false)),
00115         TOdomToMap(PM::TransformationParameters::Identity(4,4))
00116 {
00117         // set logger
00118         if (getParam<bool>("useROSLogger", false))
00119                 PointMatcherSupport::setLogger(new PointMatcherSupport::ROSLogger);
00120 
00121         // load configs
00122         string configFileName;
00123         if (ros::param::get("~icpConfig", configFileName))
00124         {
00125                 ifstream ifs(configFileName.c_str());
00126                 if (ifs.good())
00127                 {
00128                         icp.loadFromYaml(ifs);
00129                 }
00130                 else
00131                 {
00132                         ROS_ERROR_STREAM("Cannot load ICP config from YAML file " << configFileName);
00133                         icp.setDefault();
00134                 }
00135         }
00136         if (getParam<bool>("useROSLogger", false))
00137                 PointMatcherSupport::setLogger(new PointMatcherSupport::ROSLogger);
00138         if (ros::param::get("~inputFiltersConfig", configFileName))
00139         {
00140                 ifstream ifs(configFileName.c_str());
00141                 if (ifs.good())
00142                 {
00143                         inputFilters = PM::DataPointsFilters(ifs);
00144                 }
00145                 else
00146                 {
00147                         ROS_ERROR_STREAM("Cannot load input filters config from YAML file " << configFileName);
00148                 }
00149         }
00150         if (ros::param::get("~mapPreFiltersConfig", configFileName))
00151         {
00152                 ifstream ifs(configFileName.c_str());
00153                 if (ifs.good())
00154                 {
00155                         mapPreFilters = PM::DataPointsFilters(ifs);
00156                 }
00157                 else
00158                 {
00159                         ROS_ERROR_STREAM("Cannot load map pre-filters config from YAML file " << configFileName);
00160                 }
00161         }
00162         if (ros::param::get("~mapPostFiltersConfig", configFileName))
00163         {
00164                 ifstream ifs(configFileName.c_str());
00165                 if (ifs.good())
00166                 {
00167                         mapPostFilters = PM::DataPointsFilters(ifs);
00168                 }
00169                 else
00170                 {
00171                         ROS_ERROR_STREAM("Cannot load map post-filters config from YAML file " << configFileName);
00172                 }
00173         }
00174         
00175         // topics and services initialization
00176         if (getParam<bool>("subscribe_scan", true))
00177                 scanSub = n.subscribe("scan", 10, &Mapper::gotScan, this);
00178         if (getParam<bool>("subscribe_cloud", true))
00179                 cloudSub = n.subscribe("cloud_in", 10, &Mapper::gotCloud, this);
00180         mapPub = n.advertise<sensor_msgs::PointCloud2>("point_map", 2, true);
00181         odomPub = n.advertise<nav_msgs::Odometry>("icp_odom", 50, true);
00182         getPointMapSrv = n.advertiseService("dynamic_point_map", &Mapper::getPointMap, this);
00183         saveMapSrv = pn.advertiseService("save_map", &Mapper::saveMap, this);
00184         resetSrv = pn.advertiseService("reset", &Mapper::reset, this);
00185 
00186         // initial transform
00187         publishTransform();
00188         publishThread = boost::thread(boost::bind(&Mapper::publishLoop, this, tfPublishPeriod));
00189 }
00190 
00191 Mapper::~Mapper()
00192 {
00193         // wait for map-building thread
00194         if (mapBuildingInProgress)
00195         {
00196                 mapBuildingFuture.wait();
00197                 if (mapBuildingFuture.has_value())
00198                         delete mapBuildingFuture.get();
00199         }
00200         // wait for publish thread
00201         publishThread.join();
00202         // save point cloud
00203         if (mapPointCloud)
00204         {
00205                 mapPointCloud->save(vtkFinalMapName);
00206                 delete mapPointCloud;
00207         }
00208 }
00209 
00210 void Mapper::gotScan(const sensor_msgs::LaserScan& scanMsgIn)
00211 {
00212         const ros::Time endScanTime(scanMsgIn.header.stamp + ros::Duration(scanMsgIn.time_increment * (scanMsgIn.ranges.size() - 1)));
00213         unique_ptr<DP> cloud(new DP(PointMatcher_ros::rosMsgToPointMatcherCloud<float>(scanMsgIn, &tfListener, odomFrame)));
00214         processCloud(move(cloud), scanMsgIn.header.frame_id, endScanTime, scanMsgIn.header.seq);
00215 }
00216 
00217 void Mapper::gotCloud(const sensor_msgs::PointCloud2& cloudMsgIn)
00218 {
00219         unique_ptr<DP> cloud(new DP(PointMatcher_ros::rosMsgToPointMatcherCloud<float>(cloudMsgIn)));
00220         processCloud(move(cloud), cloudMsgIn.header.frame_id, cloudMsgIn.header.stamp, cloudMsgIn.header.seq);
00221 }
00222 
00223 void Mapper::processCloud(unique_ptr<DP> newPointCloud, const std::string& scannerFrame, const ros::Time& stamp, uint32_t seq)
00224 {
00225         // if the future has completed, use the new map
00226         processNewMapIfAvailable();
00227         
00228         // IMPORTANT:  We need to receive the point clouds in local coordinates (scanner or robot)
00229         timer t;
00230         
00231         // Convert point cloud
00232         const size_t goodCount(newPointCloud->features.cols());
00233         if (goodCount == 0)
00234         {
00235                 ROS_ERROR("I found no good points in the cloud");
00236                 return;
00237         }
00238         
00239         const int dimp1(newPointCloud->features.rows());
00240         ROS_INFO("Processing new point cloud");
00241         {
00242                 timer t; // Print how long take the algo
00243 
00244                 
00245                 // Apply filters to incoming cloud, in scanner coordinates
00246                 inputFilters.apply(*newPointCloud);
00247                 
00248                 ROS_INFO_STREAM("Input filters took " << t.elapsed() << " [s]");
00249         }
00250         
00251         // Fetch initial guess and transform cloud with it
00252         string reason;
00253         if (!tfListener.waitForTransform(mapFrame,scannerFrame,stamp,ros::Duration(0.1), ros::Duration(0.01), &reason))
00254         {
00255                 ROS_ERROR_STREAM("Cannot lookup TscannerToMap (" << scannerFrame << " to " << mapFrame << "): " << reason);
00256                 // Publish tf (generate the first transform)
00257                 if(!icp.hasMap())
00258                 {
00259                         tfBroadcaster.sendTransform(PointMatcher_ros::eigenMatrixToStampedTransform<float>(TOdomToMap, mapFrame, odomFrame, stamp));    
00260                 }
00261                 return;
00262         }
00263         const PM::TransformationParameters TscannerToMap(
00264                 PointMatcher_ros::eigenMatrixToDim<float>(
00265                         PointMatcher_ros::transformListenerToEigenMatrix<float>(
00266                                 tfListener, 
00267                                 mapFrame,
00268                                 scannerFrame,
00269                                 stamp 
00270                         ), dimp1
00271                 )
00272         );
00273         ROS_DEBUG_STREAM("TscannerToMap (" << scannerFrame << " to " << mapFrame << "):\n" << TscannerToMap);
00274         
00275         // Ensure a minimum amount of point after filtering
00276         const int ptsCount = newPointCloud->features.cols();
00277         if(ptsCount < minReadingPointCount)
00278         {
00279                 ROS_ERROR_STREAM("Not enough points in newPointCloud: only " << ptsCount << " pts.");
00280                 return;
00281         }
00282 
00283         // Initialize the map if empty
00284         if(!icp.hasMap())
00285         {
00286                 ROS_INFO_STREAM("Creating an initial map");
00287                 mapCreationTime = stamp;
00288                 setMap(updateMap(newPointCloud.release(), TscannerToMap, false));
00289                 // we must not delete newPointCloud because we just stored it in the mapPointCloud
00290                 return;
00291         }
00292         
00293         // Check dimension
00294         if (newPointCloud->features.rows() != icp.getInternalMap().features.rows())
00295         {
00296                 ROS_ERROR_STREAM("Dimensionality missmatch: incoming cloud is " << newPointCloud->features.rows()-1 << " while map is " << icp.getInternalMap().features.rows()-1);
00297                 return;
00298         }
00299         
00300         try 
00301         {
00302                 // Apply ICP
00303                 PM::TransformationParameters Ticp;
00304                 Ticp = icp(*newPointCloud, TscannerToMap);
00305 
00306                 ROS_DEBUG_STREAM("Ticp:\n" << Ticp);
00307                 
00308                 // Ensure minimum overlap between scans
00309                 const double estimatedOverlap = icp.errorMinimizer->getOverlap();
00310                 ROS_INFO_STREAM("Overlap: " << estimatedOverlap);
00311                 if (estimatedOverlap < minOverlap)
00312                 {
00313                         ROS_ERROR_STREAM("Estimated overlap too small, ignoring ICP correction!");
00314                         return;
00315                 }
00316                 
00317                 // Fetch transformation from scanner to odom
00318                 if (!tfListener.canTransform(scannerFrame,odomFrame,stamp,&reason))
00319                 {
00320                         ROS_ERROR_STREAM("Cannot lookup TOdomToScanner(" << odomFrame<< " to " << scannerFrame << "):\n" << reason);
00321                         return;
00322                 }
00323                 const PM::TransformationParameters TOdomToScanner(
00324                         PointMatcher_ros::eigenMatrixToDim<float>(
00325                                 PointMatcher_ros::transformListenerToEigenMatrix<float>(
00326                                 tfListener,
00327                                 scannerFrame,
00328                                 odomFrame,
00329                                 stamp
00330                         ), dimp1)
00331                 );
00332                 ROS_DEBUG_STREAM("TOdomToScanner(" << odomFrame<< " to " << scannerFrame << "):\n" << TOdomToScanner);
00333                 
00334                 // Compute tf
00335                 publishLock.lock();
00336                 TOdomToMap = Ticp * TOdomToScanner;
00337                 publishLock.unlock();
00338                 
00339                 // Publish tf
00340                 tfBroadcaster.sendTransform(PointMatcher_ros::eigenMatrixToStampedTransform<float>(TOdomToMap, mapFrame, odomFrame, stamp));
00341                 
00342                 // Publish odometry message
00343                 if (odomPub.getNumSubscribers())
00344                         odomPub.publish(PointMatcher_ros::eigenMatrixToOdomMsg<float>(TOdomToMap, mapFrame, stamp));
00345                 
00346                 // check if news points should be added to the map
00347                 if (((estimatedOverlap < maxOverlapToMerge) || (icp.getInternalMap().features.cols() < minMapPointCount)) && (!mapBuildingInProgress))
00348                 {
00349                         // make sure we process the last available map
00350                         ROS_INFO("Adding new points to the map in background");
00351                         mapCreationTime = stamp;
00352                         mapBuildingTask = MapBuildingTask(boost::bind(&Mapper::updateMap, this, newPointCloud.release(), Ticp, true));
00353                         mapBuildingFuture = mapBuildingTask.get_future();
00354                         mapBuildingThread = boost::thread(boost::move(boost::ref(mapBuildingTask)));
00355                         mapBuildingInProgress = true;
00356                 }
00357         }
00358         catch (PM::ConvergenceError error)
00359         {
00360                 ROS_ERROR_STREAM("ICP failed to converge: " << error.what());
00361                 return;
00362         }
00363         
00364         ROS_INFO_STREAM("Total ICP took: " << t.elapsed() << " [s]");
00365 }
00366 
00367 void Mapper::processNewMapIfAvailable()
00368 {
00369         if (mapBuildingInProgress && mapBuildingFuture.has_value())
00370         {
00371                 ROS_INFO_STREAM("New map available");
00372                 setMap(mapBuildingFuture.get());
00373                 mapBuildingInProgress = false;
00374         }
00375 }
00376 
00377 void Mapper::setMap(DP* newPointCloud)
00378 {
00379         // delete old map
00380         if (mapPointCloud)
00381                 delete mapPointCloud;
00382         
00383         // set new map
00384         mapPointCloud = newPointCloud;
00385         icp.setMap(*mapPointCloud);
00386         
00387         // Publish map point cloud
00388         if (mapPub.getNumSubscribers())
00389                 mapPub.publish(PointMatcher_ros::pointMatcherCloudToRosMsg<float>(*mapPointCloud, mapFrame, mapCreationTime));
00390 }
00391 
00392 DP* Mapper::updateMap(DP* newPointCloud, const PM::TransformationParameters Ticp, bool updateExisting)
00393 {
00394         timer t;
00395         
00396         // Correct new points using ICP result
00397         *newPointCloud = transformation->compute(*newPointCloud, Ticp); 
00398         
00399         // Preparation of cloud for inclusion in map
00400         mapPreFilters.apply(*newPointCloud);
00401         
00402         // Merge point clouds to map
00403         // FIXME: why map in newPointCloud instead of the inverse?
00404         if (updateExisting)
00405                 newPointCloud->concatenate(*mapPointCloud);
00406 
00407         // Map maintenance
00408         // FIXME: why minMapPointCount confuse with the use of that parameter in the rest of the code
00409         //if(newPointCloud->features.cols() > minMapPointCount)
00410         mapPostFilters.apply(*newPointCloud);
00411         
00412         ROS_INFO_STREAM("New map available (" << newPointCloud->features.cols() << " pts), update took " << t.elapsed() << " [s]");
00413         
00414         return newPointCloud;
00415 }
00416 
00417 void Mapper::publishLoop(double publishPeriod)
00418 {
00419         if(publishPeriod == 0)
00420                 return;
00421         ros::Rate r(1.0 / publishPeriod);
00422         while(ros::ok())
00423         {
00424                 publishTransform();
00425                 r.sleep();
00426         }
00427 }
00428 
00429 void Mapper::publishTransform()
00430 {
00431         publishLock.lock();
00432         tfBroadcaster.sendTransform(PointMatcher_ros::eigenMatrixToStampedTransform<float>(TOdomToMap, mapFrame, odomFrame, ros::Time::now()));
00433         publishLock.unlock();
00434 }
00435 
00436 bool Mapper::getPointMap(map_msgs::GetPointMap::Request &req, map_msgs::GetPointMap::Response &res)
00437 {
00438         if (!mapPointCloud)
00439                 return false;
00440         
00441         res.map = PointMatcher_ros::pointMatcherCloudToRosMsg<float>(*mapPointCloud, mapFrame, ros::Time::now());
00442         return true;
00443 }
00444 
00445 bool Mapper::saveMap(map_msgs::SaveMap::Request &req, map_msgs::SaveMap::Response &res)
00446 {
00447         if (!mapPointCloud)
00448                 return false;
00449         
00450         try
00451         {
00452                 mapPointCloud->save(req.filename.data);
00453         }
00454         catch (const std::runtime_error& e)
00455         {
00456                 return false;
00457         }
00458         
00459         return true;
00460 }
00461 
00462 bool Mapper::reset(std_srvs::Empty::Request &req, std_srvs::Empty::Response &res)
00463 {
00464         // note: no need for locking as we do ros::spin(), to update if we go for multi-threading
00465         TOdomToMap = PM::TransformationParameters::Identity(4,4);
00466         icp.clearMap();
00467         return true;
00468 }
00469 
00470 // Main function supporting the Mapper class
00471 int main(int argc, char **argv)
00472 {
00473         ros::init(argc, argv, "mapper");
00474         ros::NodeHandle n;
00475         ros::NodeHandle pn("~");
00476         Mapper mapper(n, pn);
00477         ros::spin();
00478         
00479         return 0;
00480 }
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Friends


modular_cloud_matcher
Author(s): François Pomerleau and Stéphane Magnenat
autogenerated on Mon Dec 17 2012 20:43:32