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
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
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
00118 if (getParam<bool>("useROSLogger", false))
00119 PointMatcherSupport::setLogger(new PointMatcherSupport::ROSLogger);
00120
00121
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
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
00187 publishTransform();
00188 publishThread = boost::thread(boost::bind(&Mapper::publishLoop, this, tfPublishPeriod));
00189 }
00190
00191 Mapper::~Mapper()
00192 {
00193
00194 if (mapBuildingInProgress)
00195 {
00196 mapBuildingFuture.wait();
00197 if (mapBuildingFuture.has_value())
00198 delete mapBuildingFuture.get();
00199 }
00200
00201 publishThread.join();
00202
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
00226 processNewMapIfAvailable();
00227
00228
00229 timer t;
00230
00231
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;
00243
00244
00245
00246 inputFilters.apply(*newPointCloud);
00247
00248 ROS_INFO_STREAM("Input filters took " << t.elapsed() << " [s]");
00249 }
00250
00251
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
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
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
00284 if(!icp.hasMap())
00285 {
00286 ROS_INFO_STREAM("Creating an initial map");
00287 mapCreationTime = stamp;
00288 setMap(updateMap(newPointCloud.release(), TscannerToMap, false));
00289
00290 return;
00291 }
00292
00293
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
00303 PM::TransformationParameters Ticp;
00304 Ticp = icp(*newPointCloud, TscannerToMap);
00305
00306 ROS_DEBUG_STREAM("Ticp:\n" << Ticp);
00307
00308
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
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
00335 publishLock.lock();
00336 TOdomToMap = Ticp * TOdomToScanner;
00337 publishLock.unlock();
00338
00339
00340 tfBroadcaster.sendTransform(PointMatcher_ros::eigenMatrixToStampedTransform<float>(TOdomToMap, mapFrame, odomFrame, stamp));
00341
00342
00343 if (odomPub.getNumSubscribers())
00344 odomPub.publish(PointMatcher_ros::eigenMatrixToOdomMsg<float>(TOdomToMap, mapFrame, stamp));
00345
00346
00347 if (((estimatedOverlap < maxOverlapToMerge) || (icp.getInternalMap().features.cols() < minMapPointCount)) && (!mapBuildingInProgress))
00348 {
00349
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
00380 if (mapPointCloud)
00381 delete mapPointCloud;
00382
00383
00384 mapPointCloud = newPointCloud;
00385 icp.setMap(*mapPointCloud);
00386
00387
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
00397 *newPointCloud = transformation->compute(*newPointCloud, Ticp);
00398
00399
00400 mapPreFilters.apply(*newPointCloud);
00401
00402
00403
00404 if (updateExisting)
00405 newPointCloud->concatenate(*mapPointCloud);
00406
00407
00408
00409
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
00465 TOdomToMap = PM::TransformationParameters::Identity(4,4);
00466 icp.clearMap();
00467 return true;
00468 }
00469
00470
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 }