00001 #include <fstream>
00002 #include <iostream>
00003 #include <fstream>
00004
00005 #include <boost/version.hpp>
00006 #include <boost/thread.hpp>
00007 #if BOOST_VERSION >= 104100
00008 #include <boost/thread/future.hpp>
00009 #endif // BOOST_VERSION >= 104100
00010
00011 #include "ros/ros.h"
00012 #include "ros/console.h"
00013 #include "pointmatcher/PointMatcher.h"
00014 #include "pointmatcher/Timer.h"
00015
00016 #include "nabo/nabo.h"
00017
00018 #include "pointmatcher_ros/point_cloud.h"
00019 #include "pointmatcher_ros/transform.h"
00020 #include "pointmatcher_ros/get_params_from_server.h"
00021 #include "pointmatcher_ros/ros_logger.h"
00022
00023 #include "nav_msgs/Odometry.h"
00024 #include "tf/transform_broadcaster.h"
00025 #include "tf_conversions/tf_eigen.h"
00026 #include "tf/transform_listener.h"
00027 #include "eigen_conversions/eigen_msg.h"
00028
00029
00030 #include "std_msgs/String.h"
00031 #include "std_srvs/Empty.h"
00032 #include "map_msgs/GetPointMap.h"
00033 #include "map_msgs/SaveMap.h"
00034 #include "ethzasl_icp_mapper/LoadMap.h"
00035 #include "ethzasl_icp_mapper/CorrectPose.h"
00036 #include "ethzasl_icp_mapper/SetMode.h"
00037 #include "ethzasl_icp_mapper/GetMode.h"
00038 #include "ethzasl_icp_mapper/GetBoundedMap.h"
00039
00040 using namespace std;
00041 using namespace PointMatcherSupport;
00042
00043 class Mapper
00044 {
00045 typedef PointMatcher<float> PM;
00046 typedef PM::DataPoints DP;
00047 typedef PM::Matches Matches;
00048
00049 typedef typename Nabo::NearestNeighbourSearch<float> NNS;
00050 typedef typename NNS::SearchType NNSearchType;
00051
00052 ros::NodeHandle& n;
00053 ros::NodeHandle& pn;
00054
00055
00056 ros::Subscriber scanSub;
00057 ros::Subscriber cloudSub;
00058
00059
00060 ros::Publisher mapPub;
00061 ros::Publisher debugPub;
00062 ros::Publisher odomPub;
00063 ros::Publisher odomErrorPub;
00064
00065
00066 ros::ServiceServer getPointMapSrv;
00067 ros::ServiceServer saveMapSrv;
00068 ros::ServiceServer loadMapSrv;
00069 ros::ServiceServer resetSrv;
00070 ros::ServiceServer correctPoseSrv;
00071 ros::ServiceServer setModeSrv;
00072 ros::ServiceServer getModeSrv;
00073 ros::ServiceServer getBoundedMapSrv;
00074
00075
00076 ros::Time mapCreationTime;
00077 ros::Time lastPoinCloudTime;
00078
00079
00080 PM::DataPointsFilters inputFilters;
00081 PM::DataPointsFilters mapPreFilters;
00082 PM::DataPointsFilters mapPostFilters;
00083 PM::DataPoints *mapPointCloud;
00084 PM::ICPSequence icp;
00085 unique_ptr<PM::Transformation> transformation;
00086
00087
00088 #if BOOST_VERSION >= 104100
00089 typedef boost::packaged_task<PM::DataPoints*> MapBuildingTask;
00090 typedef boost::unique_future<PM::DataPoints*> MapBuildingFuture;
00091 boost::thread mapBuildingThread;
00092 MapBuildingTask mapBuildingTask;
00093 MapBuildingFuture mapBuildingFuture;
00094 bool mapBuildingInProgress;
00095 #endif // BOOST_VERSION >= 104100
00096 bool processingNewCloud;
00097
00098
00099 bool publishMapTf;
00100 bool useConstMotionModel;
00101 bool localizing;
00102 bool mapping;
00103 int minReadingPointCount;
00104 int minMapPointCount;
00105 int inputQueueSize;
00106 double minOverlap;
00107 double maxOverlapToMerge;
00108 double tfRefreshPeriod;
00109 string odomFrame;
00110 string mapFrame;
00111 string vtkFinalMapName;
00112
00113 const double mapElevation;
00114
00115
00116 const float priorStatic;
00117 const float priorDyn;
00118 const float maxAngle;
00119 const float eps_a;
00120 const float eps_d;
00121 const float alpha;
00122 const float beta;
00123 const float maxDyn;
00124 const float maxDistNewPoint;
00125 const float sensorMaxRange;
00126
00127
00128 std::vector<PM::TransformationParameters> path;
00129 std::vector<PM::TransformationParameters> errors;
00130
00131 PM::TransformationParameters TOdomToMap;
00132 boost::thread publishThread;
00133 boost::mutex publishLock;
00134 ros::Time publishStamp;
00135
00136 tf::TransformListener tfListener;
00137 tf::TransformBroadcaster tfBroadcaster;
00138
00139 const float eps;
00140
00141 public:
00142 Mapper(ros::NodeHandle& n, ros::NodeHandle& pn);
00143 ~Mapper();
00144
00145 protected:
00146 void gotScan(const sensor_msgs::LaserScan& scanMsgIn);
00147 void gotCloud(const sensor_msgs::PointCloud2& cloudMsgIn);
00148 void processCloud(unique_ptr<DP> cloud, const std::string& scannerFrame, const ros::Time& stamp, uint32_t seq);
00149 void processNewMapIfAvailable();
00150 void setMap(DP* newPointCloud);
00151 DP* updateMap(DP* newPointCloud, const PM::TransformationParameters Ticp, bool updateExisting);
00152 void waitForMapBuildingCompleted();
00153
00154 void publishLoop(double publishPeriod);
00155 void publishTransform();
00156
00157 string getTransParamCsvHeader(const PM::TransformationParameters T, const string prefix);
00158 string serializeTransParamCSV(const PM::TransformationParameters T, const bool getHeader = false, const string prefix = "");
00159 void savePathToCsv(string fileName);
00160
00161
00162 bool getPointMap(map_msgs::GetPointMap::Request &req, map_msgs::GetPointMap::Response &res);
00163 bool saveMap(map_msgs::SaveMap::Request &req, map_msgs::SaveMap::Response &res);
00164 bool loadMap(ethzasl_icp_mapper::LoadMap::Request &req, ethzasl_icp_mapper::LoadMap::Response &res);
00165 bool reset(std_srvs::Empty::Request &req, std_srvs::Empty::Response &res);
00166 bool correctPose(ethzasl_icp_mapper::CorrectPose::Request &req, ethzasl_icp_mapper::CorrectPose::Response &res);
00167 bool setMode(ethzasl_icp_mapper::SetMode::Request &req, ethzasl_icp_mapper::SetMode::Response &res);
00168 bool getMode(ethzasl_icp_mapper::GetMode::Request &req, ethzasl_icp_mapper::GetMode::Response &res);
00169 bool getBoundedMap(ethzasl_icp_mapper::GetBoundedMap::Request &req, ethzasl_icp_mapper::GetBoundedMap::Response &res);
00170 };
00171
00172 string Mapper::getTransParamCsvHeader(const PM::TransformationParameters T, const string prefix)
00173 {
00174 return serializeTransParamCSV(T, true, prefix);
00175
00176 }
00177
00178 string Mapper::serializeTransParamCSV(const PM::TransformationParameters T, const bool getHeader, const string prefix)
00179 {
00180
00181 std::stringstream stream;
00182 for(int col=0; col < T.cols(); col++)
00183 {
00184 for(int row=0; row < T.rows(); row++)
00185 {
00186 if(getHeader)
00187 {
00188 stream << prefix << row << col;
00189 }
00190 else
00191 {
00192 stream << T(row,col);
00193 }
00194
00195 if((col != (T.cols()-1)) || (row != (T.rows()-1)))
00196 {
00197 stream << ", ";
00198 }
00199 }
00200 }
00201
00202 return stream.str();
00203 }
00204
00205
00206
00207
00208 void Mapper::savePathToCsv(string fileName)
00209 {
00210 assert(path.size() == errors.size());
00211 assert(path[0].cols() == errors[0].cols());
00212
00213 ofstream file;
00214 file.open (fileName);
00215
00216
00217 file << getTransParamCsvHeader(path[0], "T") << ", ";
00218 file << getTransParamCsvHeader(errors[0], "Delta") << "\n";
00219
00220 for(size_t k=0; k < path.size(); ++k)
00221 {
00222 file << serializeTransParamCSV(path[k]) << ", ";
00223 file << serializeTransParamCSV(errors[k]) << "\n";
00224 }
00225
00226 file.close();
00227
00228 }
00229
00230 Mapper::Mapper(ros::NodeHandle& n, ros::NodeHandle& pn):
00231 n(n),
00232 pn(pn),
00233 mapPointCloud(0),
00234 transformation(PM::get().REG(Transformation).create("RigidTransformation")),
00235 #if BOOST_VERSION >= 104100
00236 mapBuildingInProgress(false),
00237 #endif
00238 processingNewCloud(false),
00239 publishMapTf(getParam<bool>("publishMapTf", true)),
00240 useConstMotionModel(getParam<bool>("useConstMotionModel", false)),
00241 localizing(getParam<bool>("localizing", true)),
00242 mapping(getParam<bool>("mapping", true)),
00243 minReadingPointCount(getParam<int>("minReadingPointCount", 2000)),
00244 minMapPointCount(getParam<int>("minMapPointCount", 500)),
00245 inputQueueSize(getParam<int>("inputQueueSize", 10)),
00246 minOverlap(getParam<double>("minOverlap", 0.5)),
00247 maxOverlapToMerge(getParam<double>("maxOverlapToMerge", 0.9)),
00248 tfRefreshPeriod(getParam<double>("tfRefreshPeriod", 0.01)),
00249 odomFrame(getParam<string>("odom_frame", "odom")),
00250 mapFrame(getParam<string>("map_frame", "map")),
00251 vtkFinalMapName(getParam<string>("vtkFinalMapName", "finalMap.vtk")),
00252 mapElevation(getParam<double>("mapElevation", 0)),
00253 priorStatic(getParam<double>("priorStatic", 0.5)),
00254 priorDyn(getParam<double>("priorDyn", 0.5)),
00255 maxAngle(getParam<double>("maxAngle", 0.02)),
00256 eps_a(getParam<double>("eps_a", 0.05)),
00257 eps_d(getParam<double>("eps_d", 0.02)),
00258 alpha(getParam<double>("alpha", 0.99)),
00259 beta(getParam<double>("beta", 0.99)),
00260 maxDyn(getParam<double>("maxDyn", 0.95)),
00261 maxDistNewPoint(pow(getParam<double>("maxDistNewPoint", 0.1),2)),
00262 sensorMaxRange(getParam<double>("sensorMaxRange", 80.0)),
00263 TOdomToMap(PM::TransformationParameters::Identity(4, 4)),
00264 publishStamp(ros::Time::now()),
00265 tfListener(ros::Duration(30)),
00266 eps(0.0001)
00267 {
00268
00269
00270 if(localizing == false)
00271 mapping = false;
00272 if(mapping == true)
00273 localizing = true;
00274
00275
00276 if (getParam<bool>("useROSLogger", false))
00277 PointMatcherSupport::setLogger(new PointMatcherSupport::ROSLogger);
00278
00279
00280 string configFileName;
00281 if (ros::param::get("~icpConfig", configFileName))
00282 {
00283 ifstream ifs(configFileName.c_str());
00284 if (ifs.good())
00285 {
00286 icp.loadFromYaml(ifs);
00287 }
00288 else
00289 {
00290 ROS_ERROR_STREAM("Cannot load ICP config from YAML file " << configFileName);
00291 icp.setDefault();
00292 }
00293 }
00294 else
00295 {
00296 ROS_INFO_STREAM("No ICP config file given, using default");
00297 icp.setDefault();
00298 }
00299 if (getParam<bool>("useROSLogger", false))
00300 PointMatcherSupport::setLogger(new PointMatcherSupport::ROSLogger);
00301
00302 if (ros::param::get("~inputFiltersConfig", configFileName))
00303 {
00304 ifstream ifs(configFileName.c_str());
00305 if (ifs.good())
00306 {
00307 inputFilters = PM::DataPointsFilters(ifs);
00308 }
00309 else
00310 {
00311 ROS_ERROR_STREAM("Cannot load input filters config from YAML file " << configFileName);
00312 }
00313 }
00314 else
00315 {
00316 ROS_INFO_STREAM("No input filters config file given, not using these filters");
00317 }
00318
00319 if (ros::param::get("~mapPreFiltersConfig", configFileName))
00320 {
00321 ifstream ifs(configFileName.c_str());
00322 if (ifs.good())
00323 {
00324 mapPreFilters = PM::DataPointsFilters(ifs);
00325 }
00326 else
00327 {
00328 ROS_ERROR_STREAM("Cannot load map pre-filters config from YAML file " << configFileName);
00329 }
00330 }
00331 else
00332 {
00333 ROS_INFO_STREAM("No map pre-filters config file given, not using these filters");
00334 }
00335
00336 if (ros::param::get("~mapPostFiltersConfig", configFileName))
00337 {
00338 ifstream ifs(configFileName.c_str());
00339 if (ifs.good())
00340 {
00341 mapPostFilters = PM::DataPointsFilters(ifs);
00342 }
00343 else
00344 {
00345 ROS_ERROR_STREAM("Cannot load map post-filters config from YAML file " << configFileName);
00346 }
00347 }
00348 else
00349 {
00350 ROS_INFO_STREAM("No map post-filters config file given, not using these filters");
00351 }
00352
00353
00354 if (getParam<bool>("subscribe_scan", true))
00355 scanSub = n.subscribe("scan", inputQueueSize, &Mapper::gotScan, this);
00356 if (getParam<bool>("subscribe_cloud", true))
00357 cloudSub = n.subscribe("cloud_in", inputQueueSize, &Mapper::gotCloud, this);
00358 mapPub = n.advertise<sensor_msgs::PointCloud2>("point_map", 2, true);
00359 debugPub = n.advertise<sensor_msgs::PointCloud2>("debugPointCloud", 2, true);
00360 odomPub = n.advertise<nav_msgs::Odometry>("icp_odom", 50, true);
00361 odomErrorPub = n.advertise<nav_msgs::Odometry>("icp_error_odom", 50, true);
00362 getPointMapSrv = n.advertiseService("dynamic_point_map", &Mapper::getPointMap, this);
00363 saveMapSrv = pn.advertiseService("save_map", &Mapper::saveMap, this);
00364 loadMapSrv = pn.advertiseService("load_map", &Mapper::loadMap, this);
00365 resetSrv = pn.advertiseService("reset", &Mapper::reset, this);
00366 correctPoseSrv = pn.advertiseService("correct_pose", &Mapper::correctPose, this);
00367 setModeSrv = pn.advertiseService("set_mode", &Mapper::setMode, this);
00368 getModeSrv = pn.advertiseService("get_mode", &Mapper::getMode, this);
00369 getBoundedMapSrv = pn.advertiseService("get_bounded_map", &Mapper::getBoundedMap, this);
00370
00371
00372 publishThread = boost::thread(boost::bind(&Mapper::publishLoop, this, tfRefreshPeriod));
00373
00374 }
00375
00376 Mapper::~Mapper()
00377 {
00378 savePathToCsv("path.csv");
00379
00380 #if BOOST_VERSION >= 104100
00381
00382 if (mapBuildingInProgress)
00383 {
00384 mapBuildingFuture.wait();
00385 if (mapBuildingFuture.has_value())
00386 delete mapBuildingFuture.get();
00387 }
00388 #endif // BOOST_VERSION >= 104100
00389
00390 publishThread.join();
00391
00392 if (mapPointCloud)
00393 {
00394 mapPointCloud->save(vtkFinalMapName);
00395 delete mapPointCloud;
00396 }
00397
00398
00399 }
00400
00401 void Mapper::gotScan(const sensor_msgs::LaserScan& scanMsgIn)
00402 {
00403 if(localizing)
00404 {
00405 const ros::Time endScanTime(scanMsgIn.header.stamp + ros::Duration(scanMsgIn.time_increment * (scanMsgIn.ranges.size() - 1)));
00406 unique_ptr<DP> cloud(new DP(PointMatcher_ros::rosMsgToPointMatcherCloud<float>(scanMsgIn, &tfListener, odomFrame)));
00407 processCloud(move(cloud), scanMsgIn.header.frame_id, endScanTime, scanMsgIn.header.seq);
00408 }
00409 }
00410
00411 void Mapper::gotCloud(const sensor_msgs::PointCloud2& cloudMsgIn)
00412 {
00413 if(localizing)
00414 {
00415 unique_ptr<DP> cloud(new DP(PointMatcher_ros::rosMsgToPointMatcherCloud<float>(cloudMsgIn)));
00416 processCloud(move(cloud), cloudMsgIn.header.frame_id, cloudMsgIn.header.stamp, cloudMsgIn.header.seq);
00417 }
00418 }
00419
00420 struct BoolSetter
00421 {
00422 public:
00423 bool toSetValue;
00424 BoolSetter(bool& target, bool toSetValue):
00425 toSetValue(toSetValue),
00426 target(target)
00427 {}
00428 ~BoolSetter()
00429 {
00430 target = toSetValue;
00431 }
00432 protected:
00433 bool& target;
00434 };
00435
00436
00437 void Mapper::processCloud(unique_ptr<DP> newPointCloud, const std::string& scannerFrame, const ros::Time& stamp, uint32_t seq)
00438 {
00439
00440
00441
00442
00443 processNewMapIfAvailable();
00444 cerr << "received new map" << endl;
00445
00446 timer t;
00447
00448 processingNewCloud = true;
00449 BoolSetter stopProcessingSetter(processingNewCloud, false);
00450
00451
00452
00453 const size_t goodCount(newPointCloud->features.cols());
00454 if (goodCount == 0)
00455 {
00456 ROS_ERROR("I found no good points in the cloud");
00457 return;
00458 }
00459
00460
00461 const int dimp1(newPointCloud->features.rows());
00462
00463 if(!(newPointCloud->descriptorExists("stamps_Msec") && newPointCloud->descriptorExists("stamps_sec") && newPointCloud->descriptorExists("stamps_nsec")))
00464 {
00465 const float Msec = round(stamp.sec/1e6);
00466 const float sec = round(stamp.sec - Msec*1e6);
00467 const float nsec = round(stamp.nsec);
00468
00469 const PM::Matrix desc_Msec = PM::Matrix::Constant(1, goodCount, Msec);
00470 const PM::Matrix desc_sec = PM::Matrix::Constant(1, goodCount, sec);
00471 const PM::Matrix desc_nsec = PM::Matrix::Constant(1, goodCount, nsec);
00472 newPointCloud->addDescriptor("stamps_Msec", desc_Msec);
00473 newPointCloud->addDescriptor("stamps_sec", desc_sec);
00474 newPointCloud->addDescriptor("stamps_nsec", desc_nsec);
00475
00476 cout << "Adding time" << endl;
00477
00478 }
00479
00480 ROS_INFO("Processing new point cloud");
00481 {
00482 timer t;
00483
00484
00485 inputFilters.apply(*newPointCloud);
00486
00487
00488 ROS_INFO_STREAM("Input filters took " << t.elapsed() << " [s]");
00489 }
00490
00491 string reason;
00492
00493 if(!icp.hasMap())
00494 {
00495
00496 publishLock.lock();
00497 TOdomToMap = PM::TransformationParameters::Identity(dimp1, dimp1);
00498 TOdomToMap(2,3) = mapElevation;
00499 publishLock.unlock();
00500 }
00501
00502
00503
00504
00505 PM::TransformationParameters TOdomToScanner;
00506 try
00507 {
00508 TOdomToScanner = PointMatcher_ros::eigenMatrixToDim<float>(
00509 PointMatcher_ros::transformListenerToEigenMatrix<float>(
00510 tfListener,
00511 scannerFrame,
00512 odomFrame,
00513 stamp
00514 ), dimp1);
00515 }
00516 catch(tf::ExtrapolationException e)
00517 {
00518 ROS_ERROR_STREAM("Extrapolation Exception. stamp = "<< stamp << " now = " << ros::Time::now() << " delta = " << ros::Time::now() - stamp << endl << e.what() );
00519 return;
00520 }
00521
00522
00523 ROS_DEBUG_STREAM("TOdomToScanner(" << odomFrame<< " to " << scannerFrame << "):\n" << TOdomToScanner);
00524 ROS_DEBUG_STREAM("TOdomToMap(" << odomFrame<< " to " << mapFrame << "):\n" << TOdomToMap);
00525
00526 const PM::TransformationParameters TscannerToMap = TOdomToMap * TOdomToScanner.inverse();
00527 ROS_DEBUG_STREAM("TscannerToMap (" << scannerFrame << " to " << mapFrame << "):\n" << TscannerToMap);
00528
00529
00530 const int ptsCount = newPointCloud->features.cols();
00531 if(ptsCount < minReadingPointCount)
00532 {
00533 ROS_ERROR_STREAM("Not enough points in newPointCloud: only " << ptsCount << " pts.");
00534 return;
00535 }
00536
00537
00538 if(!icp.hasMap())
00539 {
00540 ROS_INFO_STREAM("Creating an initial map");
00541 mapCreationTime = stamp;
00542 setMap(updateMap(newPointCloud.release(), TscannerToMap, false));
00543
00544 return;
00545 }
00546
00547
00548 if (newPointCloud->features.rows() != icp.getInternalMap().features.rows())
00549 {
00550 ROS_ERROR_STREAM("Dimensionality missmatch: incoming cloud is " << newPointCloud->features.rows()-1 << " while map is " << icp.getInternalMap().features.rows()-1);
00551 return;
00552 }
00553
00554 try
00555 {
00556
00557 PM::TransformationParameters Ticp;
00558 Ticp = icp(*newPointCloud, TscannerToMap);
00559 Ticp = transformation->correctParameters(Ticp);
00560
00561
00562 PM::TransformationParameters Tdelta = Ticp * TscannerToMap.inverse();
00563
00564
00565
00566
00567
00568
00569
00570
00571
00572
00573
00574
00575
00576
00577
00578
00579
00580
00581
00582 const double estimatedOverlap = icp.errorMinimizer->getOverlap();
00583 ROS_INFO_STREAM("Overlap: " << estimatedOverlap);
00584 if (estimatedOverlap < minOverlap)
00585 {
00586 ROS_ERROR_STREAM("Estimated overlap too small, ignoring ICP correction!");
00587 return;
00588 }
00589
00590
00591 publishStamp = stamp;
00592 publishLock.lock();
00593 TOdomToMap = Ticp * TOdomToScanner;
00594
00595 PM::TransformationParameters Terror = TscannerToMap.inverse() * Ticp;
00596
00597 cerr << "Correcting translation error of " << Terror.block(0,3, 3,1).norm() << " m" << endl;
00598
00599
00600 path.push_back(Ticp);
00601 errors.push_back(Terror);
00602
00603
00604 if(publishMapTf == true)
00605 {
00606 tfBroadcaster.sendTransform(PointMatcher_ros::eigenMatrixToStampedTransform<float>(TOdomToMap, mapFrame, odomFrame, stamp));
00607 }
00608
00609 publishLock.unlock();
00610 processingNewCloud = false;
00611
00612 ROS_DEBUG_STREAM("TOdomToMap:\n" << TOdomToMap);
00613
00614
00615 if (odomPub.getNumSubscribers())
00616 odomPub.publish(PointMatcher_ros::eigenMatrixToOdomMsg<float>(Tdelta, mapFrame, stamp));
00617
00618
00619
00620 if (odomErrorPub.getNumSubscribers())
00621 odomErrorPub.publish(PointMatcher_ros::eigenMatrixToOdomMsg<float>(TOdomToMap, mapFrame, stamp));
00622
00623
00624
00625
00626
00627
00628
00629 if (
00630 mapping &&
00631 ((estimatedOverlap < maxOverlapToMerge) || (icp.getInternalMap().features.cols() < minMapPointCount)) &&
00632 (!mapBuildingInProgress)
00633 )
00634 {
00635 cout << "map Creation..." << endl;
00636
00637 mapCreationTime = stamp;
00638 ROS_INFO("Adding new points to the map in background");
00639 mapBuildingTask = MapBuildingTask(boost::bind(&Mapper::updateMap, this, newPointCloud.release(), Ticp, true));
00640 mapBuildingFuture = mapBuildingTask.get_future();
00641 mapBuildingThread = boost::thread(boost::move(boost::ref(mapBuildingTask)));
00642 mapBuildingInProgress = true;
00643 }
00644 }
00645 catch (PM::ConvergenceError error)
00646 {
00647 ROS_ERROR_STREAM("ICP failed to converge: " << error.what());
00648 return;
00649 }
00650
00651
00652 int realTimeRatio = 100*t.elapsed() / (stamp.toSec()-lastPoinCloudTime.toSec());
00653 ROS_INFO_STREAM("[TIME] Total ICP took: " << t.elapsed() << " [s]");
00654 if(realTimeRatio < 80)
00655 ROS_INFO_STREAM("[TIME] Real-time capability: " << realTimeRatio << "%");
00656 else
00657 ROS_WARN_STREAM("[TIME] Real-time capability: " << realTimeRatio << "%");
00658
00659 lastPoinCloudTime = stamp;
00660 }
00661
00662 void Mapper::processNewMapIfAvailable()
00663 {
00664 if (mapBuildingInProgress && mapBuildingFuture.has_value())
00665 {
00666 ROS_INFO_STREAM("New map available");
00667 setMap(mapBuildingFuture.get());
00668 mapBuildingInProgress = false;
00669 }
00670 }
00671
00672 void Mapper::setMap(DP* newPointCloud)
00673 {
00674
00675 if (mapPointCloud)
00676 delete mapPointCloud;
00677
00678
00679 mapPointCloud = newPointCloud;
00680 cerr << "copying map to ICP" << endl;
00681
00682 icp.setMap(*mapPointCloud);
00683
00684
00685 cerr << "publishing map" << endl;
00686
00687
00688 if (mapPub.getNumSubscribers())
00689 mapPub.publish(PointMatcher_ros::pointMatcherCloudToRosMsg<float>(*mapPointCloud, mapFrame, mapCreationTime));
00690 }
00691
00692 Mapper::DP* Mapper::updateMap(DP* newMap, const PM::TransformationParameters Ticp, bool updateExisting)
00693 {
00694 timer t;
00695
00696
00697
00698 if(newMap->descriptorExists("probabilityStatic") == false)
00699 {
00700
00701 newMap->addDescriptor("probabilityStatic", PM::Matrix::Constant(1, newMap->features.cols(), priorStatic));
00702 }
00703
00704 if(newMap->descriptorExists("probabilityDynamic") == false)
00705 {
00706
00707 newMap->addDescriptor("probabilityDynamic", PM::Matrix::Constant(1, newMap->features.cols(), priorDyn));
00708 }
00709
00710 if(newMap->descriptorExists("debug") == false)
00711 {
00712 newMap->addDescriptor("debug", PM::Matrix::Zero(1, newMap->features.cols()));
00713 }
00714
00715 if (!updateExisting)
00716 {
00717
00718 cout << "Jumping map creation" << endl;
00719 *newMap = transformation->compute(*newMap, Ticp);
00720 mapPostFilters.apply(*newMap);
00721 return newMap;
00722 }
00723
00724
00725 mapPointCloud->getDescriptorViewByName("debug") = PM::Matrix::Zero(1, mapPointCloud->features.cols());
00726
00727
00728
00729 const int newMapPts(newMap->features.cols());
00730 const int mapPtsCount(mapPointCloud->features.cols());
00731
00732
00733 PM::Matrix radius_newMap = newMap->features.topRows(3).colwise().norm();
00734
00735 PM::Matrix angles_newMap(2, newMapPts);
00736
00737
00738 for(int i=0; i<newMapPts; i++)
00739 {
00740 const float ratio = newMap->features(2,i)/radius_newMap(0,i);
00741
00742 angles_newMap(0,i) = acos(ratio);
00743 angles_newMap(1,i) = atan2(newMap->features(1,i), newMap->features(0,i));
00744 }
00745
00746 std::shared_ptr<NNS> kdtree;
00747 kdtree.reset( NNS::create(angles_newMap));
00748
00749
00750
00751 DP mapLocal = transformation->compute(*mapPointCloud, Ticp.inverse());
00752
00753
00754
00755
00756
00757
00758 PM::Matrix globalId(1, mapPtsCount);
00759
00760 int ROIpts = 0;
00761 int notROIpts = 0;
00762
00763
00764 DP mapLocalROI(mapLocal.createSimilarEmpty());
00765 for (int i = 0; i < mapPtsCount; i++)
00766 {
00767
00768 if (mapLocal.features.col(i).head(3).norm() < sensorMaxRange)
00769 {
00770 mapLocalROI.setColFrom(ROIpts, mapLocal, i);
00771 globalId(0,ROIpts) = i;
00772 ROIpts++;
00773 }
00774 else
00775 {
00776 mapLocal.setColFrom(notROIpts, mapLocal, i);
00777 notROIpts++;
00778 }
00779 }
00780
00781 mapLocalROI.conservativeResize(ROIpts);
00782 mapLocal.conservativeResize(notROIpts);
00783
00784
00785
00786 PM::Matrix radius_map = mapLocalROI.features.topRows(3).colwise().norm();
00787 PM::Matrix angles_map(2, ROIpts);
00788
00789
00790
00791 for(int i=0; i < ROIpts; i++)
00792 {
00793 const float ratio = mapLocalROI.features(2,i)/radius_map(0,i);
00794
00795
00796
00797 angles_map(0,i) = acos(ratio);
00798
00799 angles_map(1,i) = atan2(mapLocalROI.features(1,i), mapLocalROI.features(0,i));
00800 }
00801
00802
00803 DP::View viewOn_Msec_overlap = newMap->getDescriptorViewByName("stamps_Msec");
00804 DP::View viewOn_sec_overlap = newMap->getDescriptorViewByName("stamps_sec");
00805 DP::View viewOn_nsec_overlap = newMap->getDescriptorViewByName("stamps_nsec");
00806
00807 DP::View viewOnProbabilityStatic = mapLocalROI.getDescriptorViewByName("probabilityStatic");
00808 DP::View viewOnProbabilityDynamic = mapLocalROI.getDescriptorViewByName("probabilityDynamic");
00809 DP::View viewDebug = mapLocalROI.getDescriptorViewByName("debug");
00810
00811 DP::View viewOn_normals_map = mapLocalROI.getDescriptorViewByName("normals");
00812 DP::View viewOn_Msec_map = mapLocalROI.getDescriptorViewByName("stamps_Msec");
00813 DP::View viewOn_sec_map = mapLocalROI.getDescriptorViewByName("stamps_sec");
00814 DP::View viewOn_nsec_map = mapLocalROI.getDescriptorViewByName("stamps_nsec");
00815
00816
00817 Matches::Dists dists(1, ROIpts);
00818 Matches::Ids ids(1, ROIpts);
00819
00820 kdtree->knn(angles_map, ids, dists, 1, 0, NNS::ALLOW_SELF_MATCH, maxAngle);
00821
00822
00823
00824 for(int i=0; i < ROIpts; i++)
00825 {
00826 const int mapId = i;
00827 viewDebug(0,mapId) = 1;
00828
00829 if(dists(i) != numeric_limits<float>::infinity())
00830 {
00831 const int readId = ids(0,i);
00832
00833
00834
00835 const Eigen::Vector3f readPt = newMap->features.col(readId).head(3);
00836 const Eigen::Vector3f mapPt = mapLocalROI.features.col(i).head(3);
00837 const Eigen::Vector3f mapPt_n = mapPt.normalized();
00838 const float delta = (readPt - mapPt).norm();
00839 const float d_max = eps_a * readPt.norm();
00840
00841 const Eigen::Vector3f normal_map = viewOn_normals_map.col(mapId);
00842
00843
00844 const float w_v = eps + (1 - eps)*fabs(normal_map.dot(mapPt_n));
00845 const float w_d1 = eps + (1 - eps)*(1 - sqrt(dists(i))/maxAngle);
00846
00847 const float offset = delta - eps_d;
00848 float w_d2 = 1;
00849 if(delta < eps_d || mapPt.norm() > readPt.norm())
00850 {
00851 w_d2 = eps;
00852 }
00853 else
00854 {
00855 if (offset < d_max)
00856 {
00857 w_d2 = eps + (1 - eps )*offset/d_max;
00858 }
00859 }
00860
00861
00862 float w_p2 = eps;
00863 if(delta < eps_d)
00864 {
00865 w_p2 = 1;
00866 }
00867 else
00868 {
00869 if(offset < d_max)
00870 {
00871 w_p2 = eps + (1 - eps)*(1 - offset/d_max);
00872 }
00873 }
00874
00875
00876
00877 if((readPt.norm() + eps_d + d_max) >= mapPt.norm())
00878 {
00879 const float lastDyn = viewOnProbabilityDynamic(0,mapId);
00880 const float lastStatic = viewOnProbabilityStatic(0, mapId);
00881
00882 const float c1 = (1 - (w_v*(1 - w_d1)));
00883 const float c2 = w_v*(1 - w_d1);
00884
00885
00886 if(lastDyn < maxDyn)
00887 {
00888 viewOnProbabilityDynamic(0,mapId) = c1*lastDyn + c2*w_d2*((1 - alpha)*lastStatic + beta*lastDyn);
00889 viewOnProbabilityStatic(0, mapId) = c1*lastStatic + c2*w_p2*(alpha*lastStatic + (1 - beta)*lastDyn);
00890 }
00891 else
00892 {
00893 viewOnProbabilityStatic(0,mapId) = eps;
00894 viewOnProbabilityDynamic(0,mapId) = 1-eps;
00895 }
00896
00897
00898 const float sumZ = viewOnProbabilityDynamic(0,mapId) + viewOnProbabilityStatic(0, mapId);
00899 assert(sumZ >= eps);
00900
00901 viewOnProbabilityDynamic(0,mapId) /= sumZ;
00902 viewOnProbabilityStatic(0,mapId) /= sumZ;
00903
00904
00905
00906
00907
00908 viewOn_Msec_map(0,mapId) = viewOn_Msec_overlap(0,readId);
00909 viewOn_sec_map(0,mapId) = viewOn_sec_overlap(0,readId);
00910 viewOn_nsec_map(0,mapId) = viewOn_nsec_overlap(0,readId);
00911 }
00912 }
00913 }
00914
00915
00916 const int mapLocalROIPts = mapLocalROI.features.cols();
00917 cout << "build first kdtree with " << mapLocalROIPts << endl;
00918
00919 std::shared_ptr<NNS> kdtree2;
00920 kdtree2.reset( NNS::create(mapLocalROI.features, mapLocalROI.features.rows() - 1, NNS::KDTREE_LINEAR_HEAP, NNS::TOUCH_STATISTICS));
00921
00922 PM::Matches matches_overlap(
00923 Matches::Dists(1, newMapPts),
00924 Matches::Ids(1, newMapPts)
00925 );
00926
00927 kdtree2->knn(newMap->features, matches_overlap.ids, matches_overlap.dists, 1, 0, NNS::ALLOW_SELF_MATCH, maxDistNewPoint);
00928
00929
00930
00931 DP newMapOverlap(newMap->createSimilarEmpty());
00932 PM::Matrix minDist = PM::Matrix::Constant(1,mapLocalROIPts, std::numeric_limits<float>::max());
00933
00934
00935 int ptsOut = 0;
00936 int ptsIn = 0;
00937
00938 bool hasSensorNoise = mapLocalROI.descriptorExists("simpleSensorNoise") && newMap->descriptorExists("simpleSensorNoise");
00939 if(hasSensorNoise)
00940 {
00941 DP::View viewOn_noise_mapLocal = mapLocalROI.getDescriptorViewByName("simpleSensorNoise");
00942 DP::View viewOn_noise_newMap = newMap->getDescriptorViewByName("simpleSensorNoise");
00943
00944 for (int i = 0; i < newMapPts; ++i)
00945 {
00946 const int localMapId = matches_overlap.ids(i);
00947
00948 if (matches_overlap.dists(i) == numeric_limits<float>::infinity())
00949 {
00950 newMap->setColFrom(ptsOut, *newMap, i);
00951 ptsOut++;
00952 }
00953 else
00954 {
00955
00956 if(viewOn_noise_newMap(0,i) < viewOn_noise_mapLocal(0,localMapId))
00957 {
00958 if(matches_overlap.dists(i) < minDist(localMapId))
00959 {
00960 minDist(localMapId) = matches_overlap.dists(i);
00961 const float debug = viewDebug(0, localMapId);
00962 const float probDyn = viewOnProbabilityDynamic(0, localMapId);
00963 const float probStatic = viewOnProbabilityStatic(0, localMapId);
00964
00965 mapLocalROI.setColFrom(localMapId, *newMap, i);
00966 viewDebug(0, localMapId) = 2;
00967 viewOnProbabilityDynamic(0, localMapId) = probDyn;
00968 viewOnProbabilityStatic(0, localMapId) = probStatic;
00969 }
00970 }
00971
00972 newMapOverlap.setColFrom(ptsIn, *newMap, i);
00973 ptsIn++;
00974 }
00975 }
00976 }
00977 else
00978 {
00979 for (int i = 0; i < newMapPts; ++i)
00980 {
00981 if (matches_overlap.dists(i) == numeric_limits<float>::infinity())
00982 {
00983 newMap->setColFrom(ptsOut, *newMap, i);
00984 ptsOut++;
00985 }
00986 else
00987 {
00988 newMapOverlap.setColFrom(ptsIn, *newMap, i);
00989 ptsIn++;
00990 }
00991 }
00992 }
00993
00994
00995 newMap->conservativeResize(ptsOut);
00996 newMapOverlap.conservativeResize(ptsIn);
00997
00998 cout << "ptsOut=" << ptsOut << ", ptsIn=" << ptsIn << endl;
00999
01000
01001
01002
01003
01004
01005
01006
01007
01008
01009
01010
01011 newMap->concatenate(mapLocalROI);
01012
01013
01014 mapPostFilters.apply(*newMap);
01015
01016
01017 newMap->concatenate(mapLocal);
01018
01019
01020 *newMap = transformation->compute(*newMap, Ticp);
01021
01022 cout << "... end map creation" << endl;
01023 ROS_INFO_STREAM("[TIME] New map available (" << newMap->features.cols() << " pts), update took " << t.elapsed() << " [s]");
01024
01025 return newMap;
01026 }
01027
01028 void Mapper::waitForMapBuildingCompleted()
01029 {
01030 #if BOOST_VERSION >= 104100
01031 if (mapBuildingInProgress)
01032 {
01033
01034 mapBuildingFuture.wait();
01035 mapBuildingInProgress = false;
01036 }
01037 #endif // BOOST_VERSION >= 104100
01038 }
01039
01040 void Mapper::publishLoop(double publishPeriod)
01041 {
01042 if(publishPeriod == 0)
01043 return;
01044 ros::Rate r(1.0 / publishPeriod);
01045 while(ros::ok())
01046 {
01047 publishTransform();
01048 r.sleep();
01049 }
01050 }
01051
01052 void Mapper::publishTransform()
01053 {
01054 if(processingNewCloud == false && publishMapTf == true)
01055 {
01056 publishLock.lock();
01057
01058 tfBroadcaster.sendTransform(PointMatcher_ros::eigenMatrixToStampedTransform<float>(TOdomToMap, mapFrame, odomFrame, ros::Time::now()));
01059 publishLock.unlock();
01060 }
01061 else
01062 {
01063
01064 }
01065 }
01066
01067 bool Mapper::getPointMap(map_msgs::GetPointMap::Request &req, map_msgs::GetPointMap::Response &res)
01068 {
01069 if (!mapPointCloud)
01070 return false;
01071
01072
01073 res.map = PointMatcher_ros::pointMatcherCloudToRosMsg<float>(*mapPointCloud, mapFrame, ros::Time::now());
01074 return true;
01075 }
01076
01077 bool Mapper::saveMap(map_msgs::SaveMap::Request &req, map_msgs::SaveMap::Response &res)
01078 {
01079 if (!mapPointCloud)
01080 return false;
01081
01082 int lastindex = req.filename.data.find_last_of(".");
01083 string rawname = req.filename.data.substr(0, lastindex);
01084
01085 try
01086 {
01087 savePathToCsv(rawname+"_path.csv");
01088 mapPointCloud->save(req.filename.data);
01089 }
01090 catch (const std::runtime_error& e)
01091 {
01092 ROS_ERROR_STREAM("Unable to save: " << e.what());
01093 return false;
01094 }
01095
01096 ROS_INFO_STREAM("Map saved at " << req.filename.data << " with " << mapPointCloud->features.cols() << " points.");
01097 return true;
01098 }
01099
01100 bool Mapper::loadMap(ethzasl_icp_mapper::LoadMap::Request &req, ethzasl_icp_mapper::LoadMap::Response &res)
01101 {
01102 waitForMapBuildingCompleted();
01103
01104 DP* cloud(new DP(DP::load(req.filename.data)));
01105
01106 const int dim = cloud->features.rows();
01107 const int nbPts = cloud->features.cols();
01108 ROS_INFO_STREAM("Loading " << dim-1 << "D point cloud (" << req.filename.data << ") with " << nbPts << " points.");
01109
01110 publishLock.lock();
01111 TOdomToMap = PM::TransformationParameters::Identity(dim,dim);
01112
01113
01114
01115
01116 publishLock.unlock();
01117
01118
01119 if(cloud->descriptorExists("probabilityStatic") == false)
01120 {
01121 cloud->addDescriptor("probabilityStatic", PM::Matrix::Constant(1, cloud->features.cols(), priorStatic));
01122 }
01123
01124 if(cloud->descriptorExists("probabilityDynamic") == false)
01125 {
01126 cloud->addDescriptor("probabilityDynamic", PM::Matrix::Constant(1, cloud->features.cols(), priorDyn));
01127 }
01128
01129 if(cloud->descriptorExists("debug") == false)
01130 {
01131 cloud->addDescriptor("debug", PM::Matrix::Zero(1, cloud->features.cols()));
01132 }
01133
01134 setMap(cloud);
01135
01136 return true;
01137 }
01138
01139 bool Mapper::reset(std_srvs::Empty::Request &req, std_srvs::Empty::Response &res)
01140 {
01141 waitForMapBuildingCompleted();
01142
01143
01144 publishLock.lock();
01145 TOdomToMap = PM::TransformationParameters::Identity(4,4);
01146 publishLock.unlock();
01147
01148 icp.clearMap();
01149
01150 return true;
01151 }
01152
01153 bool Mapper::correctPose(ethzasl_icp_mapper::CorrectPose::Request &req, ethzasl_icp_mapper::CorrectPose::Response &res)
01154 {
01155 publishLock.lock();
01156 TOdomToMap = PointMatcher_ros::odomMsgToEigenMatrix<float>(req.odom);
01157
01158
01159
01161
01162
01163
01164
01165
01166
01167
01168
01169 tfBroadcaster.sendTransform(PointMatcher_ros::eigenMatrixToStampedTransform<float>(TOdomToMap, mapFrame, odomFrame, ros::Time::now()));
01170 publishLock.unlock();
01171
01172 return true;
01173 }
01174
01175 bool Mapper::setMode(ethzasl_icp_mapper::SetMode::Request &req, ethzasl_icp_mapper::SetMode::Response &res)
01176 {
01177
01178 if(req.localize == false && req.map == true)
01179 return false;
01180
01181 localizing = req.localize;
01182 mapping = req.map;
01183
01184 return true;
01185 }
01186
01187 bool Mapper::getMode(ethzasl_icp_mapper::GetMode::Request &req, ethzasl_icp_mapper::GetMode::Response &res)
01188 {
01189 res.localize = localizing;
01190 res.map = mapping;
01191 return true;
01192 }
01193
01194
01195
01196 bool Mapper::getBoundedMap(ethzasl_icp_mapper::GetBoundedMap::Request &req, ethzasl_icp_mapper::GetBoundedMap::Response &res)
01197 {
01198 if (!mapPointCloud)
01199 return false;
01200
01201 const float max_x = req.topRightCorner.x;
01202 const float max_y = req.topRightCorner.y;
01203 const float max_z = req.topRightCorner.z;
01204
01205 const float min_x = req.bottomLeftCorner.x;
01206 const float min_y = req.bottomLeftCorner.y;
01207 const float min_z = req.bottomLeftCorner.z;
01208
01209 cerr << "min [" << min_x << ", " << min_y << ", " << min_z << "] " << endl;
01210 cerr << "max [" << max_x << ", " << max_y << ", " << max_z << "] " << endl;
01211
01212
01213
01214 tf::StampedTransform stampedTr;
01215
01216 Eigen::Affine3d eigenTr;
01217 tf::poseMsgToEigen(req.mapCenter, eigenTr);
01218 Eigen::MatrixXf T = eigenTr.matrix().inverse().cast<float>();
01219
01220
01221 cerr << "T:" << endl << T << endl;
01222 T = transformation->correctParameters(T);
01223
01224
01225
01226 const DP centeredPointCloud = transformation->compute(*mapPointCloud, T);
01227 DP cutPointCloud = centeredPointCloud.createSimilarEmpty();
01228
01229 cerr << centeredPointCloud.features.topLeftCorner(3, 10) << endl;
01230 cerr << T << endl;
01231
01232 int newPtCount = 0;
01233 for(int i=0; i < centeredPointCloud.features.cols(); i++)
01234 {
01235 const float x = centeredPointCloud.features(0,i);
01236 const float y = centeredPointCloud.features(1,i);
01237 const float z = centeredPointCloud.features(2,i);
01238
01239 if(x < max_x && x > min_x &&
01240 y < max_y && y > min_y &&
01241 z < max_z && z > min_z )
01242 {
01243 cutPointCloud.setColFrom(newPtCount, centeredPointCloud, i);
01244 newPtCount++;
01245 }
01246 }
01247
01248 cerr << "Extract " << newPtCount << " points from the map" << endl;
01249
01250 cutPointCloud.conservativeResize(newPtCount);
01251 cutPointCloud = transformation->compute(cutPointCloud, T.inverse());
01252
01253
01254
01255 res.boundedMap = PointMatcher_ros::pointMatcherCloudToRosMsg<float>(cutPointCloud, mapFrame, ros::Time::now());
01256 return true;
01257 }
01258
01259
01260 int main(int argc, char **argv)
01261 {
01262 ros::init(argc, argv, "mapper");
01263 ros::NodeHandle n;
01264 ros::NodeHandle pn("~");
01265 Mapper mapper(n, pn);
01266 ros::spin();
01267
01268 return 0;
01269 }