graph_manager.cpp
Go to the documentation of this file.
00001 /* This file is part of RGBDSLAM.
00002  * 
00003  * RGBDSLAM is free software: you can redistribute it and/or modify
00004  * it under the terms of the GNU General Public License as published by
00005  * the Free Software Foundation, either version 3 of the License, or
00006  * (at your option) any later version.
00007  * 
00008  * RGBDSLAM is distributed in the hope that it will be useful,
00009  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00010  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00011  * GNU General Public License for more details.
00012  * 
00013  * You should have received a copy of the GNU General Public License
00014  * along with RGBDSLAM.  If not, see <http://www.gnu.org/licenses/>.
00015  */
00016 
00017 
00018 #include <sys/time.h>
00019 #include <visualization_msgs/Marker.h>
00020 #include <geometry_msgs/Point.h>
00021 //#include <rgbdslam/CloudTransforms.h>
00022 #include "graph_manager.h"
00023 #include "misc.h"
00024 //#include <sensor_msgs/PointCloud2.h>
00025 #include <opencv2/features2d/features2d.hpp>
00026 #include <QThread>
00027 #include <qtconcurrentrun.h>
00028 #include <QtConcurrentMap> 
00029 #include <utility>
00030 #include <fstream>
00031 #include <limits>
00032 #include <boost/foreach.hpp>
00033 #include <pcl_ros/point_cloud.h>
00034 
00035 #include "g2o/types/slam3d/se3quat.h"
00036 #include "g2o/types/slam3d/edge_se3.h"
00037 
00038 #include "g2o/core/block_solver.h"
00039 #include "g2o/core/optimizable_graph.h"
00040 #include "g2o/solvers/csparse/linear_solver_csparse.h"
00041 #include "g2o/solvers/cholmod/linear_solver_cholmod.h"
00042 #include "g2o/solvers/pcg/linear_solver_pcg.h"
00043 #include "g2o/solvers/dense/linear_solver_dense.h"
00044 #include "g2o/core/sparse_optimizer.h"
00045 #include "g2o/core/optimization_algorithm_dogleg.h"
00046 #include "g2o/core/optimization_algorithm_levenberg.h"
00047 #include "scoped_timer.h"
00048 
00049 
00050 //typedef g2o::BlockSolver< g2o::BlockSolverTraits<-1, -1> >  SlamBlockSolver;
00051 typedef g2o::BlockSolver< g2o::BlockSolverTraits<6, 3> >  SlamBlockSolver;
00052 typedef g2o::LinearSolverCSparse<SlamBlockSolver::PoseMatrixType> SlamLinearCSparseSolver;
00053 typedef g2o::LinearSolverCholmod<SlamBlockSolver::PoseMatrixType> SlamLinearCholmodSolver;
00054 typedef g2o::LinearSolverPCG<SlamBlockSolver::PoseMatrixType> SlamLinearPCGSolver;
00055 typedef g2o::LinearSolverDense<SlamBlockSolver::PoseMatrixType> SlamLinearDenseSolver;
00056 //typedef std::map<int, g2o::VertexSE3*> VertexIDMap;
00057 typedef std::tr1::unordered_map<int, g2o::HyperGraph::Vertex*>     VertexIDMap;
00058 typedef std::pair<int, g2o::HyperGraph::Vertex*> VertexIDPair;
00059 //std::tr1::unordered_map<int, g2o::HyperGraph::Vertex* >
00060 typedef std::set<g2o::HyperGraph::Edge*> EdgeSet;
00061 
00062 
00063 GraphManager::GraphManager() :
00064     optimizer_(NULL), 
00065     reset_request_(false),
00066     marker_id_(0),
00067     batch_processing_runs_(false),
00068     process_node_runs_(false),
00069     localization_only_(false),
00070     loop_closures_edges(0), sequential_edges(0),
00071     next_seq_id(0), next_vertex_id(0),
00072     current_backend_("none"),
00073     last_odom_time_(0),
00074     calibration_vertex_(NULL)
00075 {
00076   ScopedTimer s(__FUNCTION__);
00077 #ifdef DO_FEATURE_OPTIMIZATION
00078  next_landmark_id = 0;
00079 #endif
00080 
00081   ParameterServer* ps = ParameterServer::instance();
00082   createOptimizer(ps->get<std::string>("backend_solver"));
00083   ros::NodeHandle nh;
00084   batch_cloud_pub_ = nh.advertise<pointcloud_type>(ps->get<std::string>("individual_cloud_out_topic"),
00085                                                    ps->get<int>("publisher_queue_size"));
00086   online_cloud_pub_ = nh.advertise<pointcloud_type>(ps->get<std::string>("online_cloud_out_topic"),
00087                                                    ps->get<int>("publisher_queue_size"));
00088   whole_cloud_pub_ = nh.advertise<pointcloud_type>(ps->get<std::string>("aggregate_cloud_out_topic"),
00089                                                    ps->get<int>("publisher_queue_size"));
00090   ransac_marker_pub_ = nh.advertise<visualization_msgs::Marker>("/rgbdslam/correspondence_marker", 
00091                                                                 ps->get<int>("publisher_queue_size"));
00092   marker_pub_ = nh.advertise<visualization_msgs::Marker>("/rgbdslam/pose_graph_markers",
00093                                                          ps->get<int>("publisher_queue_size"));
00094   computed_motion_ = tf::Transform::getIdentity();
00095   init_base_pose_  = tf::Transform::getIdentity();
00096   std::string fixed_frame = ParameterServer::instance()->get<std::string>("fixed_frame_name");
00097   std::string base_frame  = ParameterServer::instance()->get<std::string>("base_frame_name");
00098     
00099   latest_transform_cache_ = tf::StampedTransform(tf::Transform::getIdentity(), ros::Time::now(), base_frame, fixed_frame);
00100   timer_ = nh.createTimer(ros::Duration(0.1), &GraphManager::broadcastLatestTransform, this);
00101 }
00102 
00103 
00104 void GraphManager::setOptimizerVerbose(bool verbose){
00105     optimizer_->setVerbose(verbose);
00106 }
00107 void GraphManager::createOptimizer(std::string backend, g2o::SparseOptimizer* optimizer)
00108 {
00109   QMutexLocker locker(&optimizer_mutex_);
00110   QMutexLocker locker2(&optimization_mutex_);
00111   // allocating the optimizer
00112   if(optimizer == NULL){
00113     if(optimizer_ != NULL){
00114       for(g2o::SparseOptimizer::VertexIDMap::iterator it = optimizer_->vertices().begin(); it != optimizer_->vertices().end(); it++)
00115       {
00116         it->second->edges().clear();
00117       }
00118       for(EdgeSet::iterator it = optimizer_->edges().begin(); it != optimizer_->edges().end(); it++)
00119       {
00120         //delete *it;
00121       }
00122       optimizer_->edges().clear();
00123       optimizer_->vertices().clear();
00124     }
00125     delete optimizer_; 
00126     optimizer_ = new g2o::SparseOptimizer();
00127     optimizer_->setVerbose(false);
00128   } else if (optimizer_ != optimizer){
00129     delete optimizer_; 
00130     optimizer_ = new g2o::SparseOptimizer();
00131     optimizer_->setVerbose(false);
00132   } 
00133 
00134   //optimizer_->setMethod(g2o::SparseOptimizer::LevenbergMarquardt);
00135 
00136   ParameterServer* ps = ParameterServer::instance();
00137   if(ps->get<bool>("optimize_landmarks")){
00138      g2o::BlockSolverX::LinearSolverType *linearSolver = new g2o::LinearSolverCSparse<g2o::BlockSolverX::PoseMatrixType>(); // alternative: CHOLMOD
00139      g2o::BlockSolverX *solver_ptr = new g2o::BlockSolverX(linearSolver);
00140      //optimizer_->setSolver(solver_ptr);
00141     g2o::OptimizationAlgorithmDogleg * algo = new g2o::OptimizationAlgorithmDogleg(solver_ptr);
00142     optimizer_->setAlgorithm(algo);
00143   }
00144   else
00145   {
00146     SlamBlockSolver* solver = NULL;
00147     if(backend == "cholmod" || backend == "auto"){
00148       SlamLinearCholmodSolver* linearSolver = new SlamLinearCholmodSolver();
00149       linearSolver->setBlockOrdering(false);
00150       solver = new SlamBlockSolver(linearSolver);
00151       current_backend_ = "cholmod";
00152     }
00153     else if(backend == "csparse"){
00154       SlamLinearCSparseSolver* linearSolver = new SlamLinearCSparseSolver();
00155       linearSolver->setBlockOrdering(false);
00156       solver = new SlamBlockSolver(linearSolver);
00157       current_backend_ = "csparse";
00158     }
00159     else if(backend == "dense"){
00160       SlamLinearDenseSolver* linearSolver = new SlamLinearDenseSolver();
00161       solver = new SlamBlockSolver(linearSolver);
00162       current_backend_ = "dense";
00163     }
00164     else if(backend == "pcg"){
00165       SlamLinearPCGSolver* linearSolver = new SlamLinearPCGSolver();
00166       solver = new SlamBlockSolver(linearSolver);
00167       current_backend_ = "pcg";
00168     }
00169     else {
00170       ROS_ERROR("Bad Parameter for g2o Solver backend: %s. User cholmod, csparse or pcg", backend.c_str());
00171       ROS_INFO("Falling Back to Cholmod Solver");
00172       SlamLinearCholmodSolver* linearSolver = new SlamLinearCholmodSolver();
00173       linearSolver->setBlockOrdering(false);
00174       solver = new SlamBlockSolver(linearSolver);
00175       current_backend_ = "cholmod";
00176     }
00177     //optimizer_->setSolver(solver);
00178     g2o::OptimizationAlgorithmLevenberg * algo = new g2o::OptimizationAlgorithmLevenberg(solver);
00179     //g2o::OptimizationAlgorithmDogleg * algo = new g2o::OptimizationAlgorithmDogleg(solver);
00180     optimizer_->setAlgorithm(algo);
00181   }
00182 
00183 
00184 
00185  optimizer_->setVerbose(false);
00186  //optimizer_->setUserLambdaInit(100.);
00187 
00188 #ifdef DO_FEATURE_OPTIMIZATION
00189  float fx = ps->get<double>("depth_camera_fx") > 0 ? ps->get<double>("depth_camera_fx") : 525; 
00190  float fy = ps->get<double>("depth_camera_fy") > 0 ? ps->get<double>("depth_camera_fy") : 525;
00191  float cx = ps->get<double>("depth_camera_cx") > 0 ? ps->get<double>("depth_camera_cx") : 319.5;
00192  float cy = ps->get<double>("depth_camera_cy") > 0 ? ps->get<double>("depth_camera_cy") : 239.5;
00193 
00194  g2o::ParameterCamera* cameraParams = new g2o::ParameterCamera();
00195  cameraParams->setKcam(fx, fy, cx, cy);
00196  g2o::SE3Quat offset; // identity
00197  cameraParams->setOffset(offset);
00198  cameraParams->setId(0);
00199  optimizer_->addParameter(cameraParams);
00200 #endif
00201 }
00202 
00203 
00204 QList<int> GraphManager::getPotentialEdgeTargetsWithDijkstra(const Node* new_node, int sequential_targets, int geodesic_targets, int sampled_targets, int predecessor_id, bool include_predecessor)
00205 {
00206     QList<int> ids_to_link_to; //return value
00207     if(predecessor_id < 0) predecessor_id = graph_.size()-1;
00208     //Prepare output
00209     std::stringstream ss;
00210     ss << "Node ID's to compare with candidate for node " << graph_.size() << ". Sequential: ";
00211 
00212    if((int)camera_vertices.size() <= sequential_targets+geodesic_targets+sampled_targets ||
00213       camera_vertices.size() <= 1)
00214     { //if less prev nodes available than targets requestet, just use all
00215       sequential_targets = sequential_targets+geodesic_targets+sampled_targets;
00216       geodesic_targets = 0;
00217       sampled_targets = 0;
00218       predecessor_id = graph_.size()-1;
00219     }
00220 
00221     if(sequential_targets > 0){
00222       //all the sequential targets (will be checked last)
00223       for (int i=1; i < sequential_targets+1 && predecessor_id-i >= 0; i++) { 
00224           ids_to_link_to.push_back(predecessor_id-i); 
00225           ss << ids_to_link_to.back() << ", " ; 
00226       }
00227     }
00228 
00229     if(geodesic_targets > 0){
00230       g2o::HyperDijkstra hypdij(optimizer_);
00231       g2o::UniformCostFunction cost_function;
00232       g2o::VertexSE3* prev_vertex = dynamic_cast<g2o::VertexSE3*>(optimizer_->vertex(graph_[predecessor_id]->vertex_id_));
00233       hypdij.shortestPaths(prev_vertex,&cost_function,ParameterServer::instance()->get<int>("geodesic_depth"));
00234       g2o::HyperGraph::VertexSet& vs = hypdij.visited();
00235 
00236       //Need to convert vertex_id to node id
00237       std::map<int, int> vertex_id_to_node_id;
00238       for (graph_it it = graph_.begin(); it !=graph_.end(); ++it){
00239             Node *node = it->second;
00240             vertex_id_to_node_id[node->vertex_id_] = node->id_;
00241             //ROS_WARN("ID Pair: %d, %d", node->vertex_id_, node->id_);
00242       }
00243 
00244       //Geodesic Neighbours except sequential
00245       std::map<int,int> neighbour_indices; //maps neighbour ids to their weights in sampling
00246       int sum_of_weights=0;
00247       for (g2o::HyperGraph::VertexSet::iterator vit=vs.begin(); vit!=vs.end(); vit++) { //FIXME: Mix of vertex id and graph node (with features) id
00248         int vid = (*vit)->id();
00249         //ROS_WARN("Vertex ID: %d", vid);
00250         int id = 0;
00251         try{
00252           id = vertex_id_to_node_id.at(vid);
00253         }
00254         catch (std::exception e){//Catch exceptions: Unexpected problems shouldn't crash the application
00255           ROS_ERROR("Vertex ID %d has no corresponding node", vid);
00256           ROS_ERROR("Map Content:");
00257           for(std::map<int,int>::const_iterator it = vertex_id_to_node_id.begin(); it != vertex_id_to_node_id.end(); it++){
00258             ROS_ERROR("Node ID %d: Vertex ID %d", it->first, it->second);
00259           }
00260           for(g2o::SparseOptimizer::VertexIDMap::iterator it = optimizer_->vertices().begin(); it != optimizer_->vertices().end(); it++)
00261           {
00262             ROS_ERROR("Vertex ID %d", it->first);
00263           }
00264         }
00265         if(!graph_.at(id)->matchable_) continue;
00266         if(id < predecessor_id-sequential_targets || (id > predecessor_id && id <= (int)graph_.size()-1)){ //Geodesic Neighbours except sequential 
00267             int weight = abs(predecessor_id-id);
00268             neighbour_indices[id] = weight; //higher probability to be drawn if far away
00269             sum_of_weights += weight;
00270         }
00271       }
00272 
00273       //Sample targets from graph-neighbours
00274       ss << "Dijkstra: ";
00275       while(ids_to_link_to.size() < sequential_targets+geodesic_targets && neighbour_indices.size() != 0){ 
00276         int random_pick = rand() % sum_of_weights;
00277         ROS_DEBUG("Pick: %d/%d", random_pick, sum_of_weights);
00278         int weight_so_far = 0;
00279         for(std::map<int,int>::iterator map_it = neighbour_indices.begin(); map_it != neighbour_indices.end(); map_it++ ){
00280           weight_so_far += map_it->second;
00281           ROS_DEBUG("Checking: %d, %d, %d", map_it->first, map_it-> second, weight_so_far);
00282           if(weight_so_far > random_pick){//found the selected one
00283             int sampled_id = map_it->first;
00284             ids_to_link_to.push_front(sampled_id);
00285             ss << ids_to_link_to.front() << ", " ; 
00286             sum_of_weights -= map_it->second;
00287             ROS_DEBUG("Taking ID: %d, decreasing sum of weights to %d", map_it->first, sum_of_weights);
00288             neighbour_indices.erase(map_it);
00289             ROS_ERROR_COND(sum_of_weights<0, "Sum of weights should never be zero");
00290             break;
00291           }
00292           ROS_DEBUG("Skipping ID: %d", map_it->first);
00293         }//for
00294       }
00295     }
00296     
00297     if(sampled_targets > 0){
00298       ss << "Random Sampling: ";
00299       std::vector<int> non_neighbour_indices;//initially holds all, then neighbours are deleted
00300       non_neighbour_indices.reserve(graph_.size());
00301       for (QList<int>::iterator it = keyframe_ids_.begin(); it != keyframe_ids_.end(); it++){
00302         if(ids_to_link_to.contains(*it) == 0 && graph_.at(*it)->matchable_){
00303           non_neighbour_indices.push_back(*it); 
00304         }
00305       }
00306 
00307       //Sample targets from non-neighbours (search new loops)
00308       while(ids_to_link_to.size() < geodesic_targets+sampled_targets+sequential_targets && non_neighbour_indices.size() != 0){ 
00309           int index_of_v_id = rand() % non_neighbour_indices.size();
00310           int sampled_id = non_neighbour_indices[index_of_v_id];
00311           non_neighbour_indices[index_of_v_id] = non_neighbour_indices.back(); //copy last id to position of the used id
00312           non_neighbour_indices.resize(non_neighbour_indices.size()-1); //drop last id
00313           ids_to_link_to.push_front(sampled_id);
00314           ss << ids_to_link_to.front() << ", " ; 
00315       }
00316     }
00317 
00318     if(include_predecessor){
00319       ids_to_link_to.push_back(predecessor_id); 
00320       ss << predecessor_id;
00321     }
00322     ROS_INFO("%s", ss.str().c_str());
00323     return ids_to_link_to; //only compare to first frame
00324 }
00325 
00326 void GraphManager::resetGraph(){
00327     #ifdef DO_FEATURE_OPTIMIZATION
00328      next_landmark_id = 0;
00329      landmarks.clear();
00330     #endif
00331 
00332      next_seq_id = next_vertex_id = 0;
00333      camera_vertices.clear();
00334      cam_cam_edges_.clear();
00335      odometry_edges_.clear();
00336     marker_id_ =0;
00337     ParameterServer* ps = ParameterServer::instance();
00338     createOptimizer(ps->get<std::string>("backend_solver"));
00339 
00340     //Q_FOREACH(Node* node, graph_) { delete node; }
00341     BOOST_FOREACH(GraphNodeType entry, graph_){ delete entry.second; entry.second = NULL; }
00342     //for(unsigned int i = 0; i < graph_.size(); delete graph_[i++]);//No body
00343     graph_.clear();
00344     keyframe_ids_.clear();
00345     Q_EMIT resetGLViewer();
00346     curr_best_result_ = MatchingResult();
00347     //current_poses_.clear();
00348     current_edges_.clear();
00349     reset_request_ = false;
00350     loop_closures_edges = 0; 
00351     sequential_edges = 0;
00352 }
00353 /*NEW
00354 void GraphManager::addOutliers(Node* new_node, std::vector<cv::DMatch> inlier_matches){
00355     std::vector<bool> outlier_flags(new_node->feature_locations_3d_.size(), true);
00356     BOOST_FOREACH(cv::DMatch& m, inlier_matches){ 
00357       outlier_flags[m.queryIdx] = false;
00358 }
00359 */
00360 void GraphManager::firstNode(Node* new_node) 
00361 {
00362     Q_EMIT renderableOctomap(&co_server_);
00363     //set the node id only if the node is actually added to the graph
00364     //needs to be done here as the graph size can change inside this function
00365     new_node->id_ = graph_.size();
00366     new_node->seq_id_ = next_seq_id++; // allways incremented, even if node is not added
00367     init_base_pose_ =  new_node->getGroundTruthTransform();//identity if no MoCap available
00368     printTransform("Ground Truth Transform for First Node", init_base_pose_);
00369     //new_node->buildFlannIndex(); // create index so that next nodes can use it
00370     g2o::VertexSE3* reference_pose = new g2o::VertexSE3;
00371 
00372     new_node->vertex_id_ = next_vertex_id++;
00373     graph_[new_node->id_] = new_node;
00374     reference_pose->setId(new_node->vertex_id_);
00375 
00376     camera_vertices.insert(reference_pose);
00377 
00378     ROS_INFO("Adding initial node with id %i and seq %i, v_id: %i", new_node->id_, new_node->seq_id_, new_node->vertex_id_);
00379     g2o::SE3Quat g2o_ref_se3 = tf2G2O(init_base_pose_);
00380     reference_pose->setEstimate(g2o_ref_se3);
00381     reference_pose->setFixed(true);//fix at origin
00382     optimizer_mutex_.lock();
00383     optimizer_->addVertex(reference_pose); 
00384     optimizer_mutex_.unlock();
00385     QString message;
00386     Q_EMIT setGUIInfo(message.sprintf("Added first node with %i keypoints to the graph", (int)new_node->feature_locations_2d_.size()));
00387     //pointcloud_type::Ptr the_pc(new_node->pc_col); //this would delete the cloud after the_pc gets out of scope
00388     QMatrix4x4 latest_transform = g2o2QMatrix(g2o_ref_se3);
00389     if(!ParameterServer::instance()->get<bool>("glwidget_without_clouds")) { 
00390       Q_EMIT setPointCloud(new_node->pc_col.get(), latest_transform);
00391       Q_EMIT setFeatures(&(new_node->feature_locations_3d_));
00392     }
00393     //current_poses_.append(latest_transform);
00394     this->addKeyframe(new_node->id_);
00395     if(ParameterServer::instance()->get<bool>("octomap_online_creation")) { 
00396       optimizeGraph(); //will do the following at the end:
00397       //updateCloudOrigin(new_node);
00398       //renderToOctomap(new_node);
00399     }
00400 
00401     process_node_runs_ = false;
00402 }
00403 
00404 /*/Translational norm
00405 inline float sqrTransNorm(const Eigen::Matrix4f& t){
00406     return t(0,3)*t(0,3)+t(1,3)*t(1,3)+t(2,3)*t(2,3);
00407 }
00408 */
00409 void updateInlierFeatures(const MatchingResult& mr, Node* new_node, Node* old_node)
00410 {
00411   BOOST_FOREACH(const cv::DMatch& match, mr.inlier_matches){
00412     assert(new_node->feature_matching_stats_.size() > match.queryIdx);
00413     assert(old_node->feature_matching_stats_.size() > match.trainIdx);
00414     unsigned char& new_flag = new_node->feature_matching_stats_[match.queryIdx];
00415     if(new_flag < 255) ++new_flag;
00416     unsigned char& old_flag = old_node->feature_matching_stats_[match.trainIdx];
00417     if(old_flag < 255) ++old_flag;
00418   }
00419 }
00420 
00421 bool GraphManager::nodeComparisons(Node* new_node, 
00422                                    QMatrix4x4& curr_motion_estimate,
00423                                    bool& edge_to_keyframe)
00424 {
00426     ScopedTimer s(__FUNCTION__);
00427     process_node_runs_ = true;
00428 
00429     ParameterServer* ps = ParameterServer::instance();
00430     int num_keypoints = (int)new_node->feature_locations_2d_.size();
00431     if (num_keypoints < ps->get<int>("min_matches") && 
00432         ! ps->get<bool>("keep_all_nodes"))
00433     {
00434         ROS_INFO("Found only %i features on image, node is not included", num_keypoints);
00435         process_node_runs_ = false;
00436         return false;
00437     }
00438 
00439     //setting of the node id needs to be done here as the graph size can change inside this method
00440     new_node->id_ = graph_.size();
00441     new_node->seq_id_ = next_seq_id++; // allways incremented, even if node is not added
00442 
00443 
00444     earliest_loop_closure_node_ = new_node->id_;
00445     unsigned int num_edges_before = cam_cam_edges_.size();
00446     edge_to_keyframe = false; //not yet found
00447     marker_id_ = 0; //overdraw old markers
00448     ROS_DEBUG("Graphsize: %d Nodes", (int) graph_.size());
00449 
00450     int sequentially_previous_id = graph_.rbegin()->second->id_; 
00451     //int best_match_candidate_id = sequentially_previous_id; 
00452     MatchingResult mr;
00453     int prev_best = mr.edge.id1;
00454     curr_best_result_ = mr;
00455 
00456     //Initial Comparison ######################################################################
00457     bool predecessor_matched = false;
00458     if(ps->get<double>("min_translation_meter") > 0.0 ||
00459        ps->get<double>("min_rotation_degree") > 0.0)
00460     {
00461       //First check if trafo to last frame is big
00462       //Node* prev_frame = graph_[graph_.size()-1];
00463       Node* prev_frame = graph_[graph_.size()-1];
00464       if(localization_only_ && curr_best_result_.edge.id1 > 0){ prev_frame =  graph_[curr_best_result_.edge.id1]; }
00465       ROS_INFO("Comparing new node (%i) with previous node %i", new_node->id_, prev_frame->id_);
00466       mr = new_node->matchNodePair(prev_frame);
00467       ROS_INFO("Node comparison result: %s", mr.toString());
00468       if(mr.edge.id1 >= 0 && mr.edge.id2 >= 0) {//Found trafo
00469         ros::Time time1 = prev_frame->header_.stamp;
00470         ros::Time time2 = new_node->header_.stamp;
00471         ros::Duration delta_time =  time2 - time1;
00472         if(!isBigTrafo(mr.edge.transform) || !isSmallTrafo(mr.edge.transform, delta_time.toSec())){ //Found trafo, but bad trafo (too small to big)
00473             ROS_WARN("Transformation not within bounds. Did not add as Node");
00474             //Send the current pose via tf nevertheless
00475             tf::Transform incremental = eigenTransf2TF(mr.edge.transform);
00476             g2o::VertexSE3* v = dynamic_cast<g2o::VertexSE3*>(optimizer_->vertex(graph_[prev_frame->id_]->vertex_id_));
00477             tf::Transform previous = eigenTransf2TF(v->estimate());
00478             tf::Transform combined = previous*incremental;
00479             latest_transform_cache_ = stampedTransformInWorldFrame(new_node, combined);
00480             printTransform("Computed new transform", latest_transform_cache_);
00481             broadcastTransform(latest_transform_cache_);
00482             process_node_runs_ = false;
00483             curr_best_result_ = mr;
00484             return false;
00485         } else { //Good Transformation
00486           ROS_DEBUG_STREAM("Information Matrix for Edge (" << mr.edge.id1 << "<->" << mr.edge.id2 << ") \n" << mr.edge.informationMatrix);
00487           if (addEdgeToG2O(mr.edge, prev_frame, new_node,  true, true, curr_motion_estimate)) 
00488           {
00489             graph_[new_node->id_] = new_node; //Needs to be added
00490             if(keyframe_ids_.contains(mr.edge.id1)) edge_to_keyframe = true;
00491 #ifdef DO_FEATURE_OPTIMIZATION
00492             updateLandmarks(mr, prev_frame,new_node);
00493 #endif
00494             updateInlierFeatures(mr, new_node, prev_frame);
00495             graph_[mr.edge.id1]->valid_tf_estimate_ = true;
00496             ROS_INFO("Added Edge between %i and %i. Inliers: %i",mr.edge.id1,mr.edge.id2,(int) mr.inlier_matches.size());
00497             curr_best_result_ = mr;
00498             //addOutliers(Node* new_node, mr.inlier_matches);
00499 
00500           } else {
00501             ROS_INFO("Edge not added");
00502             process_node_runs_ = false;
00503             return false;
00504           }
00505         }
00506         predecessor_matched = true;
00507       }
00508       else {
00509         ROS_WARN("Found no transformation to predecessor (edge ids are negative)");
00510       // No transformation to predecessor. No other choice than try other candidates. This is done below 
00511       }
00512     }//end: Initial Comparison ######################################################################
00513 
00514     //Eigen::Matrix4f ransac_trafo, final_trafo;
00515     QList<int> vertices_to_comp;
00516     int  seq_cand = localization_only_ ? 0 : ps->get<int>("predecessor_candidates") - 1; //minus one, because the first predecessor has already been checked
00517     int geod_cand = ps->get<int>("neighbor_candidates");
00518     int samp_cand = ps->get<int>("min_sampled_candidates");
00519     if(predecessor_matched){
00520       vertices_to_comp = getPotentialEdgeTargetsWithDijkstra(new_node, seq_cand, geod_cand, samp_cand, curr_best_result_.edge.id1); 
00521     } else {
00522       vertices_to_comp = getPotentialEdgeTargetsWithDijkstra(new_node, seq_cand, geod_cand, samp_cand, sequentially_previous_id, true); 
00523     }
00524     if(prev_best >= 0 && !vertices_to_comp.contains(prev_best)){
00525       vertices_to_comp.append(prev_best);//Test: definitely reuse best (currently: the oldest) matched node from last
00526     }
00527 
00528     QList<const Node* > nodes_to_comp;//only necessary for parallel computation
00529 
00530     //MAIN LOOP: Compare node pairs ######################################################################
00531     if (ps->get<bool>("concurrent_edge_construction")) 
00532     {
00533         std::stringstream ss;
00534         for (int id_of_id = (int) vertices_to_comp.size() - 1; id_of_id >= 0; id_of_id--) {
00535             //First compile a qlist of the nodes to be compared, then run the comparisons in parallel,
00536             //collecting a qlist of the results (using the blocking version of mapped).
00537             nodes_to_comp.push_front(graph_[vertices_to_comp[id_of_id]]); 
00538             ss << vertices_to_comp[id_of_id] << ", ";
00539         }
00540         ROS_INFO_STREAM("Nodes to compare: " << ss);
00541         QThreadPool* qtp = QThreadPool::globalInstance();
00542         ROS_INFO("Running node comparisons in parallel in %i (of %i) available threads", qtp->maxThreadCount() - qtp->activeThreadCount(), qtp->maxThreadCount());
00543         if (qtp->maxThreadCount() - qtp->activeThreadCount() == 1) {
00544             //Never seen this problem...
00545             ROS_WARN("Few Threads Remaining: Increasing maxThreadCount to %i", qtp->maxThreadCount()+1);
00546             qtp->setMaxThreadCount(qtp->maxThreadCount() + 1);
00547         }
00548         QList<MatchingResult> results = QtConcurrent::blockingMapped(nodes_to_comp, boost::bind(&Node::matchNodePair, new_node, _1));
00549 
00550         for (int i = 0; i < results.size(); i++) 
00551         {
00552             MatchingResult& mr = results[i];
00553             ROS_INFO("Result of comparison %d: %s", i, mr.toString());
00554             if (mr.edge.id1 >= 0 ) {
00555               //ROS_INFO("new node has id %i", new_node->id_);
00556               assert(graph_[mr.edge.id1]);
00557 
00558               ros::Duration delta_time = new_node->header_.stamp - graph_[mr.edge.id1]->header_.stamp;
00559               if (isSmallTrafo(mr.edge.transform, delta_time.toSec()) &&
00560                   addEdgeToG2O(mr.edge,graph_[mr.edge.id1],new_node, isBigTrafo(mr.edge.transform), mr.inlier_matches.size() > curr_best_result_.inlier_matches.size(), curr_motion_estimate))
00561                 { 
00562                   graph_[new_node->id_] = new_node; //Needs to be added
00563                   if(mr.edge.id1 == mr.edge.id2-1 ) {//older == newer-1
00564                     predecessor_matched = true;
00565                   }
00566 
00567 #ifdef DO_FEATURE_OPTIMIZATION
00568                   updateLandmarks(mr, graph_[mr.edge.id1],new_node);
00569 #endif
00570                   updateInlierFeatures(mr, new_node, graph_[mr.edge.id1]);
00571                   graph_[mr.edge.id1]->valid_tf_estimate_ = true;
00572                   ROS_INFO("Added Edge between %i and %i. Inliers: %i",mr.edge.id1,mr.edge.id2,(int) mr.inlier_matches.size());
00573                   if (mr.inlier_matches.size() > curr_best_result_.inlier_matches.size()) {
00574                   //if (curr_best_result_.edge.id1 == -1 || sqrTransNorm(mr.final_trafo) < sqrTransNorm(curr_best_result_.final_trafo)) {
00575                     curr_best_result_ = mr;
00576                   }
00577                   if(keyframe_ids_.contains(mr.edge.id1)) edge_to_keyframe = true;
00578                 }
00579                 else{
00580                   ROS_WARN("Rejected edge from %d to %d", mr.edge.id1, mr.edge.id2);
00581                 }
00582             }
00583         }
00584     } else { //Nonconcurrent
00585         for (int id_of_id = (int) vertices_to_comp.size() - 1; id_of_id >= 0; id_of_id--) {
00586             Node* node_to_compare = graph_[vertices_to_comp[id_of_id]];
00587             ROS_INFO("Comparing new node (%i) with node %i / %i", new_node->id_, vertices_to_comp[id_of_id], node_to_compare->id_);
00588             MatchingResult mr = new_node->matchNodePair(node_to_compare);
00589             ROS_INFO("Result of comparison: %s", mr.toString());
00590 
00591             if (mr.edge.id1 >= 0) {
00592               ros::Duration delta_time = new_node->header_.stamp - graph_[mr.edge.id1]->header_.stamp;
00593               if (isSmallTrafo(mr.edge.transform, delta_time.toSec()) &&
00594                   addEdgeToG2O(mr.edge, node_to_compare, new_node, isBigTrafo(mr.edge.transform), mr.inlier_matches.size() > curr_best_result_.inlier_matches.size(), curr_motion_estimate))
00595               {
00596 #ifdef DO_FEATURE_OPTIMIZATION
00597                 updateLandmarks(mr, node_to_compare, new_node);
00598 #endif
00599                 graph_[new_node->id_] = new_node; //Needs to be added
00600                 if(mr.edge.id1 == mr.edge.id2-1 ) {//older == newer-1
00601                   predecessor_matched = true;
00602                 }
00603                 updateInlierFeatures(mr, new_node, node_to_compare);
00604                 graph_[mr.edge.id1]->valid_tf_estimate_ = true;
00605                 ROS_INFO("Added Edge between %i and %i. Inliers: %i",mr.edge.id1,mr.edge.id2,(int) mr.inlier_matches.size());
00606                 if (mr.inlier_matches.size() > curr_best_result_.inlier_matches.size()) {
00607                   //if (curr_best_result_.edge.id1 == -1 || sqrTransNorm(mr.final_trafo) < sqrTransNorm(curr_best_result_.final_trafo)) {
00608                   curr_best_result_ = mr;
00609                 }
00610                 if(keyframe_ids_.contains(mr.edge.id1)) edge_to_keyframe = true;
00611               }
00612               else {
00613                 ROS_INFO("Matching result rejected for being too big? Time Delta: %f", delta_time.toSec());
00614               }
00615             }
00616             else 
00617             {
00618               ROS_INFO("Matching result rejected for edge.id1");
00619             }
00620         }
00621     }
00622 
00623     //END OF MAIN LOOP: Compare node pairs ######################################################################
00624     bool found_trafo = (cam_cam_edges_.size() != num_edges_before);
00625     bool valid_odometry = !ps->get<std::string>("odom_frame_name").empty();// || odom_tf_old.frame_id_ == "missing_odometry" || odom_tf_new.frame_id_ == "missing_odometry"; 
00626 
00627     bool keep_anyway = (ps->get<bool>("keep_all_nodes") || 
00628                         (((int)new_node->feature_locations_3d_.size() > ps->get<int>("min_matches")) 
00629                          && ps->get<bool>("keep_good_nodes")));
00630     ros::Duration delta_time = new_node->header_.stamp - graph_[sequentially_previous_id]->header_.stamp;
00631     float time_delta_sec = fabs(delta_time.toSec());
00632     ROS_WARN_COND(time_delta_sec >= 0.1, "Time jump (time delta: %.2f)", time_delta_sec);
00633 
00634     //If no trafo is found, only keep if a parameter says so or odometry is available. 
00635     //Otherwise only add a constant position edge, if the predecessor wasn't matched and its timestamp is nearby
00636     if((!found_trafo && valid_odometry) || 
00637        ((!found_trafo && keep_anyway) || 
00638         (!predecessor_matched && time_delta_sec < 0.1))) //FIXME: Add parameter for constant position assumption and time_delta
00639     { 
00640       LoadedEdge3D odom_edge;
00641 
00642       odom_edge.id1 = sequentially_previous_id;
00643       odom_edge.id2 = new_node->id_;
00644       odom_edge.transform.setIdentity();
00645       curr_motion_estimate = eigenTF2QMatrix(odom_edge.transform);
00646       odom_edge.informationMatrix = Eigen::Matrix<double,6,6>::Zero(); 
00647       ROS_WARN("No valid (sequential) transformation between %d and %d: Using constant position assumption.", odom_edge.id1, odom_edge.id2);
00648       odom_edge.informationMatrix = Eigen::Matrix<double,6,6>::Identity() / time_delta_sec;//e-9; 
00649       addEdgeToG2O(odom_edge,graph_[sequentially_previous_id],new_node, true,true, curr_motion_estimate);
00650       graph_[new_node->id_] = new_node; //Needs to be added
00651       new_node->valid_tf_estimate_ = false; //Don't use for postprocessing, rendering etc
00652       MatchingResult mr;
00653       mr.edge = odom_edge;
00654       curr_best_result_ = mr;
00655     }
00656 
00657     return cam_cam_edges_.size() > num_edges_before;
00658 }
00659 
00660 void GraphManager::localizationUpdate(Node* new_node, QMatrix4x4 motion_estimate)
00661 {
00662     //First render the cloud with the best frame-to-frame estimate
00663     //The transform will get updated when optimizeGraph finishes
00664     pointcloud_type* cloud_to_visualize = new_node->pc_col.get();
00665     std_vector_of_eigen_vector4f * features_to_visualize = &(new_node->feature_locations_3d_);
00666     if(!new_node->valid_tf_estimate_){
00667       cloud_to_visualize = new pointcloud_type();
00668       features_to_visualize = new std_vector_of_eigen_vector4f();
00669     }
00670 
00671     Q_EMIT setPointCloud(cloud_to_visualize, motion_estimate);
00672     Q_EMIT setFeatures(features_to_visualize);
00673 
00674     optimizeGraph();
00675     g2o::VertexSE3* new_v = dynamic_cast<g2o::VertexSE3*>(optimizer_->vertex(graph_[new_node->id_]->vertex_id_));
00676     QMutexLocker locker(&optimizer_mutex_);
00677     QMutexLocker locker2(&optimization_mutex_);
00678     optimizer_->removeVertex(new_v); //Also removes the edges
00679 }
00680 // returns true, iff node could be added to the cloud
00681 bool GraphManager::addNode(Node* new_node) 
00682 {
00683   ScopedTimer s(__FUNCTION__, false);
00684 
00685   if(reset_request_) resetGraph(); 
00686   ParameterServer* ps = ParameterServer::instance();
00687   if (new_node->feature_locations_2d_.size() < ps->get<int>("min_matches")) {
00688       ROS_WARN("Skipping node because it has only %zu features (minimum is %d)",new_node->feature_locations_2d_.size(), ps->get<int>("min_matches"));
00689       return false;
00690   }
00691 
00692   //First Node, so only build its index, insert into storage and add a
00693   //vertex at the origin, of which the position is very certain
00694   if (graph_.size()==0){
00695       firstNode(new_node);
00696       return true;
00697   }
00698 
00699   //All other nodes are processed in the following
00700   QMatrix4x4 motion_estimate;
00701   bool edge_to_last_keyframe_found = false;
00702   bool found_match = nodeComparisons(new_node, motion_estimate, edge_to_last_keyframe_found);
00703 
00704   if (found_match) 
00705   { //Success
00706     if(localization_only_)
00707     {
00708       ROS_INFO("Localizing (only)");
00709       localizationUpdate(new_node, motion_estimate);
00710     }
00711     else //Mapping
00712     {
00713       //This needs to be done before rendering, so deleting the cloud always works
00714       graph_[new_node->id_] = new_node; //Node->id_ == Graph_ Index
00715       //First render the cloud with the best frame-to-frame estimate
00716       //The transform will get updated when optimizeGraph finishes
00717       pointcloud_type* cloud_to_visualize = new_node->pc_col.get();
00718       std_vector_of_eigen_vector4f * features_to_visualize = &(new_node->feature_locations_3d_);
00719       if(!new_node->valid_tf_estimate_) {
00720         cloud_to_visualize = new pointcloud_type();
00721         features_to_visualize = new std_vector_of_eigen_vector4f();
00722       }
00723       ROS_INFO("Adding node with id %i and seq id %i to the graph", new_node->id_, new_node->seq_id_);
00724 
00725       //Juergen: bad hack, should instead prevent the creation of the cloud, but this is faster implementation wise
00726       ROS_INFO_STREAM("create cloud " << new_node->id_ << " " << ps->get<int>("create_cloud_every_nth_node") << " " << new_node->id_%ps->get<int>("create_cloud_every_nth_node")) ;
00727       if((new_node->id_%ps->get<int>("create_cloud_every_nth_node"))!=0){
00728         new_node->clearPointCloud();
00729       }
00730 
00731       if(!edge_to_last_keyframe_found && earliest_loop_closure_node_ > keyframe_ids_.back()) {
00732         this->addKeyframe(new_node->id_-1);//use the id of the node before, because that one is still localized w.r.t. a keyframe. So keyframes are connected
00733       } else {
00734         if(ps->get<bool>("visualize_keyframes_only")){
00735           cloud_to_visualize = new pointcloud_type();
00736           features_to_visualize = new std_vector_of_eigen_vector4f();
00737         }
00738       }
00739       if(ps->get<bool>("glwidget_without_clouds")){
00740         cloud_to_visualize = new pointcloud_type();
00741         features_to_visualize = new std_vector_of_eigen_vector4f();
00742       }
00743 
00744       Q_EMIT setPointCloud(cloud_to_visualize, motion_estimate);
00745       Q_EMIT setFeatures(features_to_visualize);
00746       ROS_INFO("Added Node, new graphsize: %i nodes", (int) graph_.size());
00747       if(ps->get<int>("optimizer_skip_step") > 0 && 
00748           (camera_vertices.size() % ps->get<int>("optimizer_skip_step")) == 0)
00749       { 
00750         optimizeGraph();
00751       }
00752 
00753       //This is old stuff for visualization via rviz - not tested in a long time, would be safe to delete _if_ nobody uses it
00754       visualizeGraphEdges();
00755       visualizeGraphNodes();
00756       visualizeFeatureFlow3D(marker_id_++);
00757 
00758     } 
00759   }
00760   else //Unsuccesful
00761   { 
00762     if(graph_.size() == 1){//if there is only one node which has less features, replace it by the new one
00763       ROS_WARN("Choosing new initial node, because it has more features");
00764       if(new_node->feature_locations_2d_.size() > graph_[0]->feature_locations_2d_.size()){
00765         this->resetGraph();
00766         process_node_runs_ = false;
00767         firstNode(new_node);
00768         return true;
00769       }
00770     } else { //delete new_node; //is now  done by auto_ptr
00771       ROS_WARN("Did not add as Node");
00772     }
00773   }
00774 
00775  //Info output
00776  QString message;
00777  Q_EMIT setGUIInfo(message.sprintf("%s, Camera Pose Graph Size: %iN/%iE, Duration: %f, Inliers:%5i",// &chi;<sup>2</sup>: %f", 
00778        found_match ? "Added" : "Ignored", (int)camera_vertices.size(), (int)cam_cam_edges_.size(), s.elapsed(), (int)curr_best_result_.inlier_matches.size()));//, optimizer_->chi2()));
00779  process_node_runs_ = false;
00780  ROS_INFO("%s", qPrintable(message));
00781  return found_match;
00782 }
00783 
00784 void GraphManager::addKeyframe(int id)
00785 {
00786   ScopedTimer s(__FUNCTION__);
00787   //Delete content of nodes that are behind the keyframe that is added
00788   if(ParameterServer::instance()->get<bool>("clear_non_keyframes")){
00789     if(keyframe_ids_.size() >= 2){
00790       int most_recent= keyframe_ids_.back();
00791       int second_most_recent= keyframe_ids_.at(keyframe_ids_.size() - 2);
00792       ROS_INFO("Clearing out data for nodes between keyframes %d and %d", second_most_recent, most_recent);
00793       for (std::map<int, Node*>::iterator it=graph_.begin(); it!=graph_.end(); ++it){
00794         Node* mynode = it->second;
00795         if(mynode->id_ > second_most_recent && mynode->id_ < most_recent){
00796           //mynode->getMemoryFootprint(true);//print 
00797           mynode->clearPointCloud();
00798           mynode->clearFeatureInformation();
00799         }
00800       }
00801     }
00802   }
00803 
00804   keyframe_ids_.push_back(id); 
00805 
00806   std::stringstream ss; ss << keyframe_ids_.size() << " Keyframes: ";
00807   BOOST_FOREACH(int i, keyframe_ids_){ ss << i << ", "; }
00808   ROS_INFO("%s", ss.str().c_str());
00809 }
00810 
00811 bool GraphManager::addEdgeToG2O(const LoadedEdge3D& edge,
00812                                 Node* n1, Node* n2,  
00813                                 bool largeEdge, bool set_estimate, 
00814                                 QMatrix4x4& motion_estimate) //Pure output
00815 {
00816     ScopedTimer s(__FUNCTION__);
00817     assert(n1);
00818     assert(n2);
00819     assert(n1->id_ == edge.id1);
00820     assert(n2->id_ == edge.id2);
00821 
00822     QMutexLocker locker(&optimizer_mutex_);
00823     g2o::VertexSE3* v1 = dynamic_cast<g2o::VertexSE3*>(optimizer_->vertex(n1->vertex_id_));
00824     g2o::VertexSE3* v2 = dynamic_cast<g2o::VertexSE3*>(optimizer_->vertex(n2->vertex_id_));
00825 
00826     // at least one vertex has to be created, assert that the transformation
00827     // is large enough to avoid to many vertices on the same spot
00828     if (!v1 || !v2){
00829         if (!largeEdge) {
00830             ROS_INFO("Edge to new vertex is too short, vertex will not be inserted");
00831             return false; 
00832         }
00833     }
00834 
00835     if(!v1 && !v2){
00836       ROS_ERROR("Missing both vertices: %i, %i, cannot create edge", edge.id1, edge.id2);
00837       return false;
00838     }
00839     else if (!v1 && v2) {
00840         v1 = new g2o::VertexSE3;
00841         assert(v1);
00842         int v_id = next_vertex_id++;
00843         v1->setId(v_id);
00844 
00845         n1->vertex_id_ = v_id; // save vertex id in node so that it can find its vertex
00846         v1->setEstimate(v2->estimate() * edge.transform.inverse());
00847         camera_vertices.insert(v1);
00848         optimizer_->addVertex(v1); 
00849         motion_estimate = eigenTF2QMatrix(v1->estimate()); 
00850         ROS_WARN("Creating previous id. This is unexpected by the programmer");
00851     }
00852     else if (!v2 && v1) {
00853         v2 = new g2o::VertexSE3;
00854         assert(v2);
00855         int v_id = next_vertex_id++;
00856         v2->setId(v_id);
00857         n2->vertex_id_ = v_id;
00858         v2->setEstimate(v1->estimate() * edge.transform);
00859         camera_vertices.insert(v2);
00860         optimizer_->addVertex(v2); 
00861         motion_estimate = eigenTF2QMatrix(v2->estimate()); 
00862     }
00863     else if(set_estimate){
00864         v2->setEstimate(v1->estimate() * edge.transform);
00865         motion_estimate = eigenTF2QMatrix(v2->estimate()); 
00866     }
00867     g2o::EdgeSE3* g2o_edge = new g2o::EdgeSE3;
00868     g2o_edge->vertices()[0] = v1;
00869     g2o_edge->vertices()[1] = v2;
00870     Eigen::Isometry3d meancopy(edge.transform); 
00871     g2o_edge->setMeasurement(meancopy);
00872     //Change setting from which mahal distance the robust kernel is used: robust_kernel_.setDelta(1.0);
00873     g2o_edge->setRobustKernel(&robust_kernel_);
00874     // g2o_edge->setInverseMeasurement(edge.trannsform.inverse());
00875     g2o_edge->setInformation(edge.informationMatrix);
00876     optimizer_->addEdge(g2o_edge);
00877     //ROS_DEBUG_STREAM("Added Edge ("<< edge.id1 << "-" << edge.id2 << ") to Optimizer:\n" << edge.transform << "\nInformation Matrix:\n" << edge.informationMatrix);
00878     cam_cam_edges_.insert(g2o_edge);
00879     current_match_edges_.insert(g2o_edge); //Used if all previous vertices are fixed ("pose_relative_to" == "all")
00880 //    new_edges_.append(qMakePair(edge.id1, edge.id2));
00881 
00882     if(abs(edge.id1 - edge.id2) > ParameterServer::instance()->get<int>("predecessor_candidates")){
00883       loop_closures_edges++;
00884     } else {
00885       sequential_edges++;
00886     }
00887     current_edges_.append( qMakePair(n1->id_, n2->id_));
00888 
00889     if (ParameterServer::instance()->get<std::string>("pose_relative_to") == "inaffected") {
00890       v1->setFixed(false);
00891       v2->setFixed(false);
00892     }
00893     else if(ParameterServer::instance()->get<std::string>("pose_relative_to") == "largest_loop") {
00894       earliest_loop_closure_node_ = std::min(earliest_loop_closure_node_, edge.id1);
00895       earliest_loop_closure_node_ = std::min(earliest_loop_closure_node_, edge.id2);
00896     }
00897     return true;
00898 }
00899 
00900 double GraphManager::optimizeGraph(double break_criterion, bool nonthreaded){
00901   if(ParameterServer::instance()->get<bool>("concurrent_optimization") && !nonthreaded) {
00902     ROS_DEBUG("Optimization done in Thread");
00903     QtConcurrent::run(this, &GraphManager::optimizeGraphImpl, break_criterion); 
00904     return -1.0;
00905   }
00906   else { //Non-concurrent
00907     return optimizeGraphImpl(break_criterion);//regular function call
00908   }
00909 }
00910 
00911 void fixationOfVertices(std::string strategy, 
00912                         g2o::SparseOptimizer* optimizer, 
00913                         std::map<int, Node* >& graph,
00914                         g2o::HyperGraph::VertexSet& camera_vertices,
00915                         int earliest_loop_closure_node
00916                         ){
00917     //Fixation strategies
00918     if (strategy == "previous" && graph.size() > 2) {
00919       optimizer->setFixed(camera_vertices, false);
00920       optimizer->vertex(graph[graph.size() - 2]->vertex_id_)->setFixed(true);
00921     }
00922     else if (strategy == "largest_loop"){
00923       //std::stringstream ss; ss << "Nodes in or outside loop: ";
00924       for (std::map<int, Node*>::iterator it=graph.begin(); it!=graph.end(); ++it){
00925         Node* mynode = it->second;
00926         //Even before oldest matched node?
00927         bool is_outside_largest_loop =  mynode->id_ < earliest_loop_closure_node;
00928         //ss << mynode->id_ << (is_outside_largest_loop ? "o, " : "i, ");
00929         optimizer->vertex(mynode->vertex_id_)->setFixed(is_outside_largest_loop);
00930       }
00931       //ROS_INFO("%s", ss.str().c_str());
00932     }
00933     else if (strategy == "first") {
00934       optimizer->setFixed(camera_vertices, false);
00935       optimizer->vertex(graph[0]->vertex_id_)->setFixed(true);
00936     }
00937 }
00938 double GraphManager::optimizeGraphImpl(double break_criterion)
00939 {
00940   ScopedTimer s(__FUNCTION__, false, true); // not only for logging
00941   ParameterServer* ps = ParameterServer::instance();
00942   double stop_cond = break_criterion > 0.0 ? break_criterion : ps->get<double>("optimizer_iterations");
00943   ROS_WARN_NAMED("eval", "Loop Closures: %u, Sequential Edges: %u", loop_closures_edges, sequential_edges);
00944   ROS_WARN("Starting Optimization");
00945   double chi2 = std::numeric_limits<double>::max();
00946   if(!optimization_mutex_.tryLock(2/*milliseconds*/))
00947   {
00948     ROS_INFO("Attempted Graph Optimization, but it is already running. Skipping.");
00949     return -1.0;
00950   }
00951   else //Got the lock
00952   {
00953     optimizer_mutex_.lock();
00954     if(optimizer_->vertices().size() == 0){
00955       ROS_ERROR("Attempted Graph Optimization on an empty graph!");
00956       return -1.0;
00957     }
00958 
00959     fixationOfVertices(ps->get<std::string>("pose_relative_to"), 
00960                        optimizer_, graph_, camera_vertices, earliest_loop_closure_node_); 
00961 
00962 #ifdef DO_FEATURE_OPTIMIZATION
00963    printLandmarkStatistic();
00964    if (ps->get<bool>("optimize_landmarks")){
00965      updateProjectionEdges();
00966      optimizer_->initializeOptimization(cam_lm_edges);
00967    } else /*continued as else if below*/
00968 #endif
00969    if (ps->get<std::string>("pose_relative_to") == "inaffected") {
00970      ScopedTimer s2("Optimizer Initialization (inaffected)");
00971      g2o::HyperDijkstra hypdij(optimizer_);
00972      g2o::UniformCostFunction cost_function;
00973      g2o::VertexSE3* new_vertex = dynamic_cast<g2o::VertexSE3*>(optimizer_->vertex(graph_[graph_.size()-1]->vertex_id_));
00974      hypdij.shortestPaths(new_vertex,&cost_function,4);
00975      g2o::HyperGraph::VertexSet& vs = hypdij.visited();
00976      optimizer_->initializeOptimization(vs);
00977    } 
00978    else {
00979       g2o::HyperGraph::EdgeSet edges;
00980       if (!ps->get<bool>("use_robot_odom_only")){
00981         edges.insert(cam_cam_edges_.begin(), cam_cam_edges_.end());
00982       }
00983       if (ps->get<bool>("use_robot_odom")){
00984           edges.insert(odometry_edges_.begin(), odometry_edges_.end());
00985       }
00986       optimizer_->initializeOptimization(edges);
00987     }
00988 
00989     {
00990       ScopedTimer s2("Optimizer Initialization");
00991       optimizer_->initializeOptimization(cam_cam_edges_);
00992     }
00993 
00994     ROS_WARN_NAMED("eval", "Optimization with %zu cams, %zu nodes and %zu edges in the graph", graph_.size(), optimizer_->vertices().size(), optimizer_->edges().size());
00995     Q_EMIT iamBusy(1, "Optimizing Graph", 0); 
00996     int currentIt = 0;
00997     //Optimize certain number of iterations
00998     if(stop_cond >= 1.0){ 
00999       do {
01000         currentIt += optimizer_->optimize(ceil(stop_cond / 10));//optimize in maximally 10 steps
01001       } while(ros::ok() && currentIt < stop_cond && currentIt > 0); //the latter avoids infinite looping if there's nothing to optimize
01002       optimizer_->computeActiveErrors();
01003       chi2 = optimizer_->chi2();
01004     } 
01005     //Optimize to convergence
01006     else { 
01007       double prev_chi2;
01008       do {
01009         prev_chi2 = chi2; //chi2 is numeric_limits::max() in first iteration
01010         currentIt += optimizer_->optimize(5);//optimize 5 iterations per step
01011         optimizer_->computeActiveErrors();
01012         chi2 = optimizer_->chi2();
01013       } while(chi2/prev_chi2 < (1.0 - stop_cond));//e.g.  999/1000 < (1.0 - 0.01) => 0.999 < 0.99
01014     }
01015 
01016     ROS_WARN_STREAM_NAMED("eval", "G2O Statistics: " << std::setprecision(15) << camera_vertices.size() 
01017                           << " cameras, " << optimizer_->edges().size() << " edges. " << chi2
01018                           << " ; chi2 "<< ", Iterations: " << currentIt);
01019     optimizer_mutex_.unlock();
01020     optimization_mutex_.unlock();
01021   }
01022 
01023   Q_EMIT progress(1, "Optimizing Graph", 1); 
01024 
01025   //ROS_INFO("Finished G2O optimization after %d iterations", i);
01026   //optimizer_->save((bagfile_name + "_g2o-optimizer-save-file-after").c_str());
01027 
01028   ROS_INFO("A: last cam: %i", last_added_cam_vertex_id());
01029 
01030   QMutexLocker locker(&optimizer_mutex_);
01031   if (ps->get<std::string>("pose_relative_to") == "inaffected") {
01032     optimizer_->setFixed(camera_vertices, true);
01033   }
01034   else {
01035     optimizer_->setFixed(camera_vertices, false);
01036   }
01037   g2o::VertexSE3* v = dynamic_cast<g2o::VertexSE3*>(optimizer_->vertex(last_added_cam_vertex_id()));
01038   //ROS_INFO("Sending Transform for Vertex ID: %d", new_node
01039   computed_motion_ =  eigenTransf2TF(v->estimate());
01040   Node* newest_node = graph_[graph_.size()-1];
01041   latest_transform_cache_ = stampedTransformInWorldFrame(newest_node, computed_motion_);
01042   //printTransform("Computed final transform", latest_transform_cache_);
01043   broadcastTransform(latest_transform_cache_);
01044   if(ps->get<bool>("octomap_online_creation")) { 
01045     if(updateCloudOrigin(newest_node)){
01046       renderToOctomap(newest_node);
01047       //v->setFixed(true);
01048     }
01049   }
01050 
01051   current_match_edges_.clear();
01052   //ROS_WARN("GM: 1198: no graph edges in visualzation"  );
01053   Q_EMIT setGraphEdges(getGraphEdges());
01054   Q_EMIT updateTransforms(getAllPosesAsMatrixList());
01055 
01056   ROS_WARN_STREAM_NAMED("eval", "Optimizer Runtime; "<< s.elapsed() <<" s");
01057 #ifdef DO_LOOP_CLOSING
01058   loopClosingTest();
01059 
01060   // createSearchTree();
01061   // std::vector<std::pair<int,float> > neighbours;
01062   // getNeighbours(graph_.begin()->first, 1, neighbours);
01063 #endif
01064   Q_EMIT setGraph(optimizer_);
01065   return chi2; 
01066 }
01067 
01068 bool containsVertex(g2o::HyperGraph::Edge* myedge, g2o::HyperGraph::Vertex* v_to_del)
01069 {
01070     std::vector<g2o::HyperGraph::Vertex*>& myvertices = myedge->vertices();
01071     g2o::VertexSE3 *v1, *v2; //used in loop as temporaries
01072     v1 = dynamic_cast<g2o::VertexSE3*>(myvertices.at(1));
01073     v2 = dynamic_cast<g2o::VertexSE3*>(myvertices.at(0));
01074     return (v1->id() == v_to_del->id() || v2->id() == v_to_del->id());
01075 }
01076 
01077 void GraphManager::deleteCameraFrame(int id)
01078 {
01079     QMutexLocker locker(&optimizer_mutex_);
01080     QMutexLocker locker2(&optimization_mutex_);
01081 
01082     g2o::VertexSE3* v_to_del = dynamic_cast<g2o::VertexSE3*>(optimizer_->vertex(graph_[id]->vertex_id_));//last vertex
01083 
01084 #ifdef DO_FEATURE_OPTIMIZATION
01085     //Erase edges from cam_cam edge set
01086     EdgeSet::iterator edge_iter = cam_cam_edges_.begin();
01087     for(;edge_iter != cam_cam_edges_.end(); edge_iter++) {
01088         if(containsVertex(*edge_iter, v_to_del))
01089           cam_cam_edges_.erase(edge_iter);
01090     }
01091     //Erase edges from cam_lm edge set
01092     edge_iter = cam_lm_edges.begin();
01093     for(;edge_iter != cam_lm_edges.end(); edge_iter++) {
01094         if(containsVertex(*edge_iter, v_to_del))
01095           cam_cam_edges_.erase(edge_iter);
01096     }
01097 #endif
01098 
01099     optimizer_->removeVertex(v_to_del); //This takes care of removing all edges too
01100     camera_vertices.erase(v_to_del);
01101     graph_.erase(id);
01102 }
01103 
01106 unsigned int GraphManager::pruneEdgesWithErrorAbove(float thresh){
01107     QMutexLocker locker(&optimizer_mutex_);
01108     QMutexLocker locker2(&optimization_mutex_);
01109 
01110     optimizer_->computeActiveErrors();
01111     unsigned int counter = 0;
01112 
01113 #ifdef DO_FEATURE_OPTIMIZATION
01114     //remove feature edges
01115     if (ParameterServer::instance()->get<bool>("optimize_landmarks")){
01116       EdgeSet::iterator edge_iter = cam_lm_edges.begin();
01117       for(;edge_iter != cam_lm_edges.end(); edge_iter++) 
01118       {
01119           g2o::EdgeSE3PointXYZDepth* myedge = dynamic_cast<g2o::EdgeSE3PointXYZDepth*>(*edge_iter);
01120           Eigen::Vector3d ev = myedge->error();
01121           if(ev.squaredNorm() > thresh){
01122             optimizer_->removeEdge(myedge); 
01123             cam_lm_edges.erase(edge_iter);
01124           }
01125       }
01126     }
01127 #endif
01128 
01129     //This block is only required for the ROS_INFO message
01130     g2o::VertexSE3 *v1, *v2; //used in loop
01131     std::map<int, int> vertex_id_to_node_id;
01132     for (graph_it it = graph_.begin(); it !=graph_.end(); ++it){
01133           Node *node = it->second;
01134           vertex_id_to_node_id[node->vertex_id_] = node->id_;
01135     }
01136 
01137     //Discount cam2cam edges
01138     g2o::HyperGraph::EdgeSet remaining_cam_cam_edges, edges_to_remove;
01139     EdgeSet::iterator edge_iter = cam_cam_edges_.begin();
01140     for(;edge_iter != cam_cam_edges_.end(); edge_iter++) {
01141         g2o::EdgeSE3* myedge = dynamic_cast<g2o::EdgeSE3*>(*edge_iter);
01142         g2o::EdgeSE3::ErrorVector ev = myedge->error();
01143 
01144         //For Output Message:
01145         std::vector<g2o::HyperGraph::Vertex*>& myvertices = myedge->vertices();
01146         v1 = dynamic_cast<g2o::VertexSE3*>(myvertices.at(1));
01147         v2 = dynamic_cast<g2o::VertexSE3*>(myvertices.at(0));
01148         int n_id1 = vertex_id_to_node_id[v1->id()]; 
01149         int n_id2 = vertex_id_to_node_id[v2->id()];
01150 
01151         ROS_DEBUG("Mahalanobis Distance for edge from node %d to %d is %f", n_id1, n_id2, myedge->chi2());
01152         if(myedge->chi2() > thresh){
01153           counter++;
01154 
01155             //Constant position estimate 
01156             Eigen::Quaterniond eigen_quat(1,0,0,0);
01157             Eigen::Vector3d translation(0,0,0);
01158             g2o::SE3Quat unit_tf(eigen_quat, translation);
01159             myedge->setMeasurement(unit_tf);
01160 
01161             if(abs(n_id1 - n_id2) != 1)//Non-Consecutive
01162             { 
01163               //If not disconnecting a vertex, remove it completely
01164               if(v1->edges().size() > 1 && v2->edges().size() > 1){
01165                 ROS_INFO("Removing edge from node %d to %d, because error is %f", n_id1, n_id2, ev.squaredNorm());
01166                 edges_to_remove.insert(myedge);
01167               }
01168               else {
01169                 ROS_WARN("Setting edge from node %d to %d to Identity and Information to diag(1e-100), because error is %f", n_id1, n_id2, ev.squaredNorm());
01170                 //Set highly uncertain, so non-erroneous edges prevail
01171                 Eigen::Matrix<double,6,6> new_info = Eigen::Matrix<double,6,6>::Identity()* 1e-100;
01172                 new_info(3,3) = 1e-100;
01173                 new_info(4,4) = 1e-100;
01174                 new_info(5,5) = 1e-100;
01175                 myedge->setInformation(new_info);
01176               }
01177             } else {
01178               //consecutive: zero motion assumption
01179               ROS_WARN("Setting edge from node %d to %d and Information to Identity because error is %f", n_id1, n_id2, ev.squaredNorm());
01180               Eigen::Matrix<double,6,6> new_info = Eigen::Matrix<double,6,6>::Identity();
01181               myedge->setInformation(new_info);
01182             }
01183           /*
01184           }
01185           else{
01186             //Remove erroneous loop closures competely
01187             optimizer_->removeEdge(myedge); 
01188             ROS_INFO("Removing edge from node %d to %d because error is %f", n_id1, n_id2, ev.squaredNorm());
01189             continue; //do not take into remaining edge set
01190           }
01191           */
01192         }
01193         /*
01194         else
01195         { //for subsequent nodes, check distance
01196           int n_id1 = vertex_id_to_node_id[v1->id()]; 
01197           int n_id2 = vertex_id_to_node_id[v2->id()];
01198           if(abs(n_id1 - n_id2) == 1){ //predecessor-successor
01199             ros::Duration delta_time = graph_[n_id2]->header_.stamp - graph_[n_id1]->header_.stamp;
01200             if(!isSmallTrafo(myedge->measurement(), delta_time.toSec()))
01201             { 
01202               ROS_INFO("Setting edge from node %d to %d to Identity because translation is too large", n_id1, n_id2);
01203               //Constant position estimate
01204               Eigen::Quaterniond eigen_quat(1,0,0,0);
01205               Eigen::Vector3d translation(0,0,0);
01206               g2o::SE3Quat unit_tf(eigen_quat, translation);
01207               myedge->setMeasurement(unit_tf);
01208               //Set highly uncertain, so non-erroneous edges prevail
01209               Eigen::Matrix<double,6,6> new_info = Eigen::Matrix<double,6,6>::Identity()* 1e-2;
01210               new_info(3,3) = 1e-4;
01211               new_info(4,4) = 1e-4;
01212               new_info(5,5) = 1e-4;
01213               myedge->setInformation(new_info);
01214             }
01215           }
01216         }*/
01217         //remaining_cam_cam_edges.insert(*edge_iter);
01218     }
01219     EdgeSet::iterator remove_edge_iter = edges_to_remove.begin();
01220     for(;remove_edge_iter!= edges_to_remove.end();remove_edge_iter++) {
01221       cam_cam_edges_.erase(*remove_edge_iter);
01222     }
01223     //Now cut, without making vertices disconnected
01224     /*
01225     for(g2o::SparseOptimizer::VertexIDMap::iterator it = optimizer_->vertices().begin(); it != optimizer_->vertices().end(); it++)
01226     {
01227       //Go through all edges of vertex
01228       g2o::HyperGraph::EdgeSet es = it->second->edges();
01229       EdgeSet::iterator edge_iter = es.begin(); 
01230       unsigned int nonconsec_edges = 0;
01231       bool edge_to_prev = false, edge_to_next = false;
01232       for(;edge_iter != es.end(); ++edge_iter) {
01233         g2o::EdgeSE3* curr_edge = dynamic_cast<g2o::EdgeSE3*>(*edge_iter);
01234         g2o::EdgeSE3::ErrorVector ev = curr_edge->error();
01235 
01236         if(curr_edge->chi2() > thresh){
01237           optimizer_->removeEdge(curr_edge); 
01238           continue;
01239         } 
01240       }
01241     }
01242     */
01243     //cam_cam_edges_.swap(remaining_cam_cam_edges_);
01244     //Q_EMIT setGraphEdges(getGraphEdges());
01245     return counter;
01246 }
01247 
01248 QList<QPair<int, int> >* GraphManager::getGraphEdges()
01249 {
01250     ScopedTimer s(__FUNCTION__);
01251     //QList<QPair<int, int> >* edge_list = new QList<QPair<int, int> >();
01252     g2o::VertexSE3 *v1, *v2; //used in loop
01253     std::map<int, int> vertex_id_to_node_id;
01254     for (graph_it it = graph_.begin(); it !=graph_.end(); ++it){
01255           Node *node = it->second;
01256           vertex_id_to_node_id[node->vertex_id_] = node->id_;
01257     }
01258     QList<QPair<int, int> >* current_edges = new QList<QPair<int, int> >();
01259     EdgeSet odom_and_feature_edges(odometry_edges_);
01260     odometry_edges_.insert(cam_cam_edges_.begin(), cam_cam_edges_.end());
01261     EdgeSet::iterator edge_iter = odom_and_feature_edges.begin();
01262     for(;edge_iter != odom_and_feature_edges.end(); edge_iter++) {
01263         g2o::EdgeSE3* myedge = dynamic_cast<g2o::EdgeSE3*>(*edge_iter);
01264         if(myedge){
01265           std::vector<g2o::HyperGraph::Vertex*>& myvertices = myedge->vertices();
01266           v1 = dynamic_cast<g2o::VertexSE3*>(myvertices.at(1));
01267           v2 = dynamic_cast<g2o::VertexSE3*>(myvertices.at(0));
01268         } else {
01269           ROS_WARN("Unexpected edge type in getGraphEdges()");
01270           continue;
01271         }
01272 
01273         if(vertex_id_to_node_id.find(v1->id()) == vertex_id_to_node_id.end()){
01274             ROS_WARN("Vertex ID %d does not match any Node ", v1->id());
01275         }
01276         if(vertex_id_to_node_id.find(v2->id()) == vertex_id_to_node_id.end()){
01277             ROS_WARN("Vertex ID %d does not match any Node ", v2->id());
01278         }
01279         std::map<int, int>::iterator node_id1_it = vertex_id_to_node_id.find(v1->id());
01280         if (node_id1_it == vertex_id_to_node_id.end()) continue;
01281         std::map<int, int>::iterator node_id2_it = vertex_id_to_node_id.find(v2->id());
01282         if (node_id2_it == vertex_id_to_node_id.end()) continue;
01283         current_edges->append( qMakePair(node_id1_it->second, node_id2_it->second));
01284     }
01285     return current_edges;
01286 }
01287 
01288 QList<QMatrix4x4>* GraphManager::getAllPosesAsMatrixList() const{
01289     ScopedTimer s(__FUNCTION__);
01290     //QList<QMatrix4x4> current_poses;
01291     ROS_DEBUG("Retrieving all transformations from optimizer");
01292     QList<QMatrix4x4>* current_poses = new QList<QMatrix4x4>();
01293     //current_poses.clear();
01294 #if defined(QT_VERSION) && QT_VERSION >= 0x040700
01295     current_poses->reserve(camera_vertices.size()+10);//only allocates the internal pointer array. +10 for things like calibration vertices or whatever
01296 #endif
01297 
01298     for (std::map<int, Node* >::const_iterator it = graph_.begin(); it !=graph_.end(); ++it){
01299       const Node *node = it->second;
01300       g2o::VertexSE3* v = dynamic_cast<g2o::VertexSE3*>(optimizer_->vertex( node->vertex_id_));
01301       if(v){ 
01302         current_poses->push_back(eigenTF2QMatrix(v->estimate())); 
01303       } else {
01304         ROS_ERROR("Nullpointer in graph at position %i!", it->first);
01305       }
01306     }
01307     return current_poses;// new QList<QMatrix4x4>(current_poses); //pointer to a copy
01308 }
01309 
01310 void GraphManager::reducePointCloud(pointcloud_type const * pc) {
01311   double vfs = ParameterServer::instance()->get<double>("voxelfilter_size");
01312   BOOST_REVERSE_FOREACH(GraphNodeType entry, graph_){
01313     if(entry.second->pc_col.get() == pc){
01314       entry.second->reducePointCloud(vfs);
01315       ROS_INFO("Reduced PointCloud after rendering to openGL list.");
01316       return;
01317     }
01318   }
01319 }
01320 
01321 void GraphManager::printEdgeErrors(QString filename){
01322   QMutexLocker locker(&optimizer_mutex_);
01323   std::fstream filestr;
01324   filestr.open (qPrintable(filename),  std::fstream::out );
01325 
01326  EdgeSet::iterator edge_iter = cam_cam_edges_.begin();
01327  for(int i =0;edge_iter != cam_cam_edges_.end(); edge_iter++, i++) {
01328       g2o::EdgeSE3* myedge = dynamic_cast<g2o::EdgeSE3*>(*edge_iter);
01329       g2o::EdgeSE3::ErrorVector ev = myedge->error();
01330       ROS_INFO_STREAM("Error Norm for edge " << i << ": " << ev.squaredNorm());
01331       filestr << "Error for edge " << i << ": " << ev.squaredNorm() << std::endl;
01332   }
01333   filestr.close();
01334 }
01335 
01336 double GraphManager::geodesicDiscount(g2o::HyperDijkstra& hypdij, const MatchingResult& mr){
01337     //Discount by geodesic distance to root node
01338     const g2o::HyperDijkstra::AdjacencyMap am = hypdij.adjacencyMap();
01339 
01340     g2o::VertexSE3* older_vertex = dynamic_cast<g2o::VertexSE3*>(optimizer_->vertex( nodeId2VertexId(mr.edge.id1) ));
01341     double discount_factor = am.at(older_vertex).distance();
01342     discount_factor = discount_factor > 0.0? 1.0/discount_factor : 1.0;//avoid inf
01343     ROS_INFO("Discount weight for connection to Node %i = %f", mr.edge.id1, discount_factor);
01344     return discount_factor;
01345 }
01346 
01347 void GraphManager::sanityCheck(float thresh){ 
01348   thresh *=thresh; //squaredNorm
01349   QMutexLocker locker(&optimizer_mutex_);
01350   QMutexLocker locker2(&optimization_mutex_);
01351   EdgeSet::iterator edge_iter = cam_cam_edges_.begin();
01352   for(int i =0;edge_iter != cam_cam_edges_.end(); edge_iter++, i++) {
01353     g2o::EdgeSE3* myedge = dynamic_cast<g2o::EdgeSE3*>(*edge_iter);
01354     Eigen::Vector3d ev = myedge->measurement().translation();
01355     if(ev.squaredNorm() > thresh){
01356       //optimizer_->removeEdge(myedge); 
01357       myedge->setInformation(Eigen::Matrix<double,6,6>::Identity()* 0.000001);
01358     }
01359   }
01360 }
01361 
01362 void GraphManager::filterNodesByPosition(float x, float y, float z)
01363 {
01364   ROS_WARN("filtering Nodes");
01365     Eigen::Vector3f center(x,y,z);
01366     float radius = 0.5;
01367     for (graph_it it = graph_.begin(); it !=graph_.end(); ++it){
01368           Node *node = it->second;
01369           Node* clone = node->copy_filtered(center, radius);
01370     }
01371 }
01372 void GraphManager::occupancyFilterClouds(){
01373   Q_EMIT iamBusy(0, "Filtering Clouds by Occupancy", graph_.size());
01374   BOOST_FOREACH(GraphNodeType entry, graph_){ 
01375     ROS_INFO("Filtering points of Node %d", entry.first);
01376     co_server_.occupancyFilter(entry.second->pc_col, entry.second->pc_col, ParameterServer::instance()->get<double>("occupancy_filter_threshold"));
01377     Q_EMIT progress(0, "Filtering  Clouds by Occupancy", entry.first);
01378   }
01379     Q_EMIT progress(0,  "Filtering  Clouds by Occupancy", 1e6);
01380   ROS_INFO("Done Filtering, display will not be updated!");
01381 }


rgbdslam_v2
Author(s): Felix Endres, Juergen Hess, Nikolas Engelhard
autogenerated on Thu Jun 6 2019 21:49:45