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 "pcl_ros/transforms.h"
00025 #include "pcl/io/pcd_io.h"
00026 #include <sensor_msgs/PointCloud2.h>
00027 #include <opencv2/features2d/features2d.hpp>
00028 #include <QThread>
00029 #include <qtconcurrentrun.h>
00030 #include <QtConcurrentMap> 
00031 #include <QFile>
00032 #include <utility>
00033 #include <fstream>
00034 #include <limits>
00035 #include <boost/foreach.hpp>
00036 #include <pcl_ros/point_cloud.h>
00037 
00038 #include "g2o/math_groups/se3quat.h"
00039 #include "g2o/types/slam3d/edge_se3_quat.h"
00040 
00041 #include "g2o/core/block_solver.h"
00042 #include "g2o/solvers/csparse/linear_solver_csparse.h"
00043 #include "g2o/solvers/cholmod/linear_solver_cholmod.h"
00044 #include "g2o/solvers/pcg/linear_solver_pcg.h"
00045 
00046 //typedef g2o::BlockSolver< g2o::BlockSolverTraits<-1, -1> >  SlamBlockSolver;
00047 typedef g2o::BlockSolver< g2o::BlockSolverTraits<6, 3> >  SlamBlockSolver;
00048 typedef g2o::LinearSolverCSparse<SlamBlockSolver::PoseMatrixType> SlamLinearCSparseSolver;
00049 typedef g2o::LinearSolverCholmod<SlamBlockSolver::PoseMatrixType> SlamLinearCholmodSolver;
00050 typedef g2o::LinearSolverPCG<SlamBlockSolver::PoseMatrixType> SlamLinearPCGSolver;
00051 //typedef std::map<int, g2o::VertexSE3*> VertexIDMap;
00052 typedef std::tr1::unordered_map<int, g2o::HyperGraph::Vertex*>     VertexIDMap;
00053 typedef std::pair<int, g2o::HyperGraph::Vertex*> VertexIDPair;
00054 //std::tr1::unordered_map<int, g2o::HyperGraph::Vertex* >
00055 typedef std::set<g2o::HyperGraph::Edge*> EdgeSet;
00056 
00057 
00058 GraphManager::GraphManager(ros::NodeHandle nh) :
00059     optimizer_(NULL), 
00060     latest_transform_(), //constructs identity
00061     reset_request_(false),
00062     marker_id_(0),
00063     last_matching_node_(-1),
00064     batch_processing_runs_(false),
00065     process_node_runs_(false),
00066     localization_only_(false),
00067     someone_is_waiting_for_me_(false),
00068     loop_closures_edges(0), sequential_edges(0),
00069     current_backend_("none")
00070 {
00071   struct timespec starttime, finish; double elapsed; clock_gettime(CLOCK_MONOTONIC, &starttime);
00072 
00073   ParameterServer* ps = ParameterServer::instance();
00074   createOptimizer(ps->get<std::string>("backend_solver"));
00075 
00076   batch_cloud_pub_ = nh.advertise<pointcloud_type>(ps->get<std::string>("individual_cloud_out_topic"),
00077                                                    ps->get<int>("publisher_queue_size"));
00078   whole_cloud_pub_ = nh.advertise<pointcloud_type>(ps->get<std::string>("aggregate_cloud_out_topic"),
00079                                                    ps->get<int>("publisher_queue_size"));
00080   ransac_marker_pub_ = nh.advertise<visualization_msgs::Marker>("/rgbdslam/correspondence_marker", 
00081                                                                 ps->get<int>("publisher_queue_size"));
00082   marker_pub_ = nh.advertise<visualization_msgs::Marker>("/rgbdslam/pose_graph_markers",
00083                                                          ps->get<int>("publisher_queue_size"));
00084   computed_motion_ = tf::Transform::getIdentity();
00085   init_base_pose_  = tf::Transform::getIdentity();
00086   base2points_     = tf::Transform::getIdentity();
00087   //timer_ = nh.createTimer(ros::Duration(0.1), &GraphManager::broadcastTransform, this);
00088   Max_Depth = -1;
00089 
00090   clock_gettime(CLOCK_MONOTONIC, &finish); elapsed = (finish.tv_sec - starttime.tv_sec); elapsed += (finish.tv_nsec - starttime.tv_nsec) / 1000000000.0; ROS_INFO_STREAM_COND_NAMED(elapsed > ParameterServer::instance()->get<double>("min_time_reported"), "timings", __FUNCTION__ << " runtime: "<< elapsed <<" s");
00091 }
00092 
00093 void GraphManager::createOptimizer(std::string backend, g2o::SparseOptimizer* optimizer)
00094 {
00095   QMutexLocker locker(&optimizer_mutex);
00096   // allocating the optimizer
00097   if(optimizer == NULL){
00098     delete optimizer_; 
00099     optimizer_ = new g2o::SparseOptimizer();
00100     optimizer_->setVerbose(true);
00101   } else if (optimizer_ != optimizer){
00102     delete optimizer_; 
00103     optimizer_ = new g2o::SparseOptimizer();
00104     optimizer_->setVerbose(true);
00105   } 
00106   SlamBlockSolver* solver = NULL;
00107   if(backend == "cholmod" || backend == "auto"){
00108     SlamLinearCholmodSolver* linearSolver = new SlamLinearCholmodSolver();
00109     linearSolver->setBlockOrdering(false);
00110     solver = new SlamBlockSolver(optimizer_, linearSolver);
00111     current_backend_ = "cholmod";
00112   }
00113   else if(backend == "csparse"){
00114     SlamLinearCSparseSolver* linearSolver = new SlamLinearCSparseSolver();
00115     linearSolver->setBlockOrdering(false);
00116     solver = new SlamBlockSolver(optimizer_, linearSolver);
00117     current_backend_ = "csparse";
00118   }
00119   else if(backend == "pcg"){
00120     SlamLinearPCGSolver* linearSolver = new SlamLinearPCGSolver();
00121     solver = new SlamBlockSolver(optimizer_, linearSolver);
00122     current_backend_ = "pcg";
00123   }
00124   else {
00125     ROS_ERROR("Bad Parameter for g2o Solver backend: %s. User cholmod, csparse or pcg", backend.c_str());
00126     ROS_INFO("Falling Back to Cholmod Solver");
00127     SlamLinearCholmodSolver* linearSolver = new SlamLinearCholmodSolver();
00128     linearSolver->setBlockOrdering(false);
00129     solver = new SlamBlockSolver(optimizer_, linearSolver);
00130     current_backend_ = "cholmod";
00131   }
00132   optimizer_->setSolver(solver);
00133 }
00134 
00135 //WARNING: Dangerous
00136 void GraphManager::deleteFeatureInformation() {
00137   ROS_WARN("Clearing out Feature information from nodes");
00138   //Q_FOREACH(Node* node, graph_) {
00139   BOOST_FOREACH(GraphNodeType entry, graph_){
00140     entry.second->clearFeatureInformation();
00141   }
00142 }
00143 
00144 GraphManager::~GraphManager() {
00145   //TODO: delete all Nodes
00146     //for (unsigned int i = 0; i < optimizer_->vertices().size(); ++i) {
00147     //Q_FOREACH(Node* node, graph_) { delete node; }
00148     BOOST_FOREACH(GraphNodeType entry, graph_) { 
00149       delete entry.second; 
00150     }
00151     graph_.clear();
00152     delete (optimizer_);
00153     ransac_marker_pub_.shutdown();
00154     whole_cloud_pub_.shutdown();
00155     marker_pub_.shutdown();
00156     batch_cloud_pub_.shutdown();
00157 
00158 }
00159 
00160 void GraphManager::drawFeatureFlow(cv::Mat& canvas, cv::Scalar line_color,
00161                                    cv::Scalar circle_color){
00162     struct timespec starttime, finish; double elapsed; clock_gettime(CLOCK_MONOTONIC, &starttime);
00163     if(!ParameterServer::instance()->get<bool>("use_gui")){ return; }
00164     ROS_DEBUG("Number of features to draw: %d", (int)last_inlier_matches_.size());
00165 
00166     const double pi_fourth = 3.14159265358979323846 / 4.0;
00167     const int line_thickness = 1;
00168     const int circle_radius = 6;
00169     const int CV_AA = 16;
00170     if(graph_.empty()) {
00171       ROS_WARN("Feature Flow for empty graph requested. Bug?");
00172       return;
00173     } else if(graph_.size() == 1 || last_matching_node_ == -1 ) {//feature flow is only available between at least two nodes
00174       Node* newernode = graph_[graph_.size()-1];
00175       cv::drawKeypoints(canvas, newernode->feature_locations_2d_, canvas, cv::Scalar(255), 5);
00176       return;
00177     } 
00178 
00179     Node* earliernode = graph_[last_matching_node_];//graph_.size()-2; //compare current to previous
00180     Node* newernode = graph_[graph_.size()-1];
00181     if(earliernode == NULL){
00182       if(newernode == NULL ){ ROS_ERROR("Nullpointer for Node %u", (unsigned int)graph_.size()-1); }
00183       ROS_ERROR("Nullpointer for Node %d", last_matching_node_);
00184       last_matching_node_ = 0;
00185       return;
00186     } else if(newernode == NULL ){
00187       ROS_ERROR("Nullpointer for Node %u", (unsigned int)graph_.size()-1);
00188       return;
00189     }
00190 
00191     //encircle all keypoints in this image
00192     //for(unsigned int feat = 0; feat < newernode->feature_locations_2d_.size(); feat++) {
00193     //    cv::Point2f p; 
00194     //    p = newernode->feature_locations_2d_[feat].pt;
00195     //    cv::circle(canvas, p, circle_radius, circle_color, line_thickness, 8);
00196     //}
00197     cv::Mat tmpimage = cv::Mat::zeros(canvas.rows, canvas.cols, canvas.type());
00198     cv::drawKeypoints(canvas, newernode->feature_locations_2d_, tmpimage, circle_color, 5);
00199     canvas+=tmpimage;
00200     for(unsigned int mtch = 0; mtch < last_inlier_matches_.size(); mtch++) {
00201         cv::Point2f p,q; //TODO: Use sub-pixel-accuracy
00202         unsigned int newer_idx = last_inlier_matches_[mtch].queryIdx;
00203         unsigned int earlier_idx = last_inlier_matches_[mtch].trainIdx;
00204         q = newernode->feature_locations_2d_[newer_idx].pt;
00205         p = earliernode->feature_locations_2d_[earlier_idx].pt;
00206 
00207         double angle;    angle = atan2( (double) p.y - q.y, (double) p.x - q.x );
00208         double hypotenuse = cv::norm(p-q);
00209             cv::line(canvas, p, q, line_color, line_thickness, CV_AA);
00210         if(hypotenuse > 1.5){  //only larger motions larger than one pix get an arrow tip
00211             cv::line( canvas, p, q, line_color, line_thickness, CV_AA );
00212             /* Now draw the tips of the arrow.  */
00213             p.x =  (q.x + 4 * cos(angle + pi_fourth));
00214             p.y =  (q.y + 4 * sin(angle + pi_fourth));
00215             cv::line( canvas, p, q, line_color, line_thickness, CV_AA );
00216             p.x =  (q.x + 4 * cos(angle - pi_fourth));
00217             p.y =  (q.y + 4 * sin(angle - pi_fourth));
00218             cv::line( canvas, p, q, line_color, line_thickness, CV_AA );
00219         } else { //draw a smaller circle into the bigger one 
00220             cv::circle(canvas, p, circle_radius-2, circle_color, line_thickness, CV_AA);
00221         }
00222     }
00223     clock_gettime(CLOCK_MONOTONIC, &finish); elapsed = (finish.tv_sec - starttime.tv_sec); elapsed += (finish.tv_nsec - starttime.tv_nsec) / 1000000000.0; ROS_INFO_STREAM_COND_NAMED(elapsed > ParameterServer::instance()->get<double>("min_time_reported"), "timings", __FUNCTION__ << " runtime: "<< elapsed <<" s");
00224 }
00225 
00226 
00227 QList<int> GraphManager::getPotentialEdgeTargetsWithDijkstra(const Node* new_node, int sequential_targets, int geodesic_targets, int sampled_targets, int predecessor_id)
00228 {
00229     QList<int> ids_to_link_to; //return value
00230     if(predecessor_id < 0) predecessor_id = graph_.size()-1;
00231     //output only loop
00232     std::stringstream ss;
00233     ss << "Node ID's to compare with candidate for node " << graph_.size() << ". Sequential: ";
00234 
00235     if((int)optimizer_->vertices().size() <= sequential_targets+geodesic_targets+sampled_targets ||
00236        optimizer_->vertices().size() <= 1)
00237     { //if less prev nodes available than targets requestet, just use all
00238       sequential_targets = sequential_targets+geodesic_targets+sampled_targets;
00239       geodesic_targets = 0;
00240       sampled_targets = 0;
00241       predecessor_id = graph_.size()-1;
00242     }
00243 
00244     if(sequential_targets > 0){
00245       //all the sequential targets (will be checked last)
00246       for (int i=1; i < sequential_targets+1 && predecessor_id-i >= 0; i++) { 
00247           ids_to_link_to.push_back(predecessor_id-i); 
00248           ss << ids_to_link_to.back() << ", " ; 
00249       }
00250     }
00251 
00252     if(geodesic_targets > 0){
00253       g2o::HyperDijkstra hypdij(optimizer_);
00254       g2o::UniformCostFunction cost_function;
00255       g2o::VertexSE3* prev_vertex = dynamic_cast<g2o::VertexSE3*>(optimizer_->vertex(predecessor_id));
00256       hypdij.shortestPaths(prev_vertex,&cost_function,3);
00257       g2o::HyperGraph::VertexSet& vs = hypdij.visited();
00258 
00259 
00260       //Geodesic Neighbours except sequential
00261       std::map<int,int> neighbour_indices; //maps neighbour ids to their weights in sampling
00262       int sum_of_weights=0;
00263       for (g2o::HyperGraph::VertexSet::iterator vit=vs.begin(); vit!=vs.end(); vit++) { //FIXME: Mix of vertex id and graph node (with features) id
00264         int id = (*vit)->id();
00265         if(id < predecessor_id-sequential_targets || (id > predecessor_id && id <= (int)graph_.size()-1)){ //Geodesic Neighbours except sequential 
00266             int weight = abs(predecessor_id-id);
00267             neighbour_indices[id] = weight; //higher probability to be drawn if far away
00268             sum_of_weights += weight;
00269         }
00270       }
00271 
00272       //Sample targets from graph-neighbours
00273       ss << "Dijkstra: ";
00274       while(ids_to_link_to.size() < sequential_targets+geodesic_targets && neighbour_indices.size() != 0){ 
00275         int random_pick = rand() % sum_of_weights;
00276         ROS_DEBUG("Pick: %d/%d", random_pick, sum_of_weights);
00277         int weight_so_far = 0;
00278         for(std::map<int,int>::iterator map_it = neighbour_indices.begin(); map_it != neighbour_indices.end(); map_it++ ){
00279           weight_so_far += map_it->second;
00280           ROS_DEBUG("Checking: %d, %d, %d", map_it->first, map_it-> second, weight_so_far);
00281           if(weight_so_far > random_pick){//found the selected one
00282             int sampled_id = map_it->first;
00283             ids_to_link_to.push_front(sampled_id);
00284             ss << ids_to_link_to.front() << ", " ; 
00285             sum_of_weights -= map_it->second;
00286             ROS_DEBUG("Taking ID: %d, decreasing sum of weights to %d", map_it->first, sum_of_weights);
00287             neighbour_indices.erase(map_it);
00288             ROS_ERROR_COND(sum_of_weights<0, "Sum of weights should never be zero");
00289             break;
00290           }
00291           ROS_DEBUG("Skipping ID: %d", map_it->first);
00292         }//for
00293       }
00294     }
00295     
00296     if(sampled_targets > 0){
00297       ss << "Random Sampling: ";
00298       std::vector<int> non_neighbour_indices;//initially holds all, then neighbours are deleted
00299       non_neighbour_indices.reserve(graph_.size());
00300       for (QList<int>::iterator it = keyframe_ids_.begin(); it != keyframe_ids_.end(); it++){
00301         if(ids_to_link_to.contains(*it) == 0){
00302           non_neighbour_indices.push_back(*it); 
00303         }
00304       }
00305 
00306       //Sample targets from non-neighbours (search new loops)
00307       while(ids_to_link_to.size() < geodesic_targets+sampled_targets+sequential_targets && non_neighbour_indices.size() != 0){ 
00308           int index_of_v_id = rand() % non_neighbour_indices.size();
00309           int sampled_id = non_neighbour_indices[index_of_v_id];
00310           non_neighbour_indices[index_of_v_id] = non_neighbour_indices.back(); //copy last id to position of the used id
00311           non_neighbour_indices.resize(non_neighbour_indices.size()-1); //drop last id
00312           ids_to_link_to.push_front(sampled_id);
00313           ss << ids_to_link_to.front() << ", " ; 
00314       }
00315     }
00316 
00317     ROS_INFO("%s", ss.str().c_str());
00318     return ids_to_link_to; //only compare to first frame
00319 }
00320 
00321 QList<int> GraphManager::getPotentialEdgeTargets(const Node* new_node, int last_targets, int sample_targets)
00322 {
00323     int gsize = graph_.size();
00324     ROS_ERROR_COND(gsize == 0, "Do not call this function as long as the graph is empty");
00325     QList<int> ids_to_link_to; //return value
00326 
00327     //Special Cases of max_targets argument
00328     if(last_targets + sample_targets <= 0) {        //Negative: No comparisons
00329         return ids_to_link_to;
00330     }
00331 
00332     //All the last few nodes
00333     if(gsize <= sample_targets+last_targets){
00334       last_targets = gsize;//Can't compare to more nodes than exist
00335       sample_targets = 0;
00336     }
00337     for(int i = 2; i <= gsize && i <= last_targets; i++){//start at two, b/c the prev node is always already checked in addNode
00338         ids_to_link_to.push_back(gsize-i);
00339     }
00340 
00341 
00342     while(ids_to_link_to.size() < sample_targets+last_targets && ids_to_link_to.size() < gsize-1){ 
00343         int sample_id = rand() % (gsize - 1);
00344         ROS_DEBUG_STREAM("Sample: " << sample_id << " Graph size: " << gsize << " ids_to_link_to.size: " << ids_to_link_to.size());
00345         QList<int>::const_iterator i1 = qFind(ids_to_link_to, sample_id);
00346         if(i1 != ids_to_link_to.end()) 
00347           continue;
00348         ids_to_link_to.push_back(sample_id);
00349     }
00350 
00351 
00352     //output only loop
00353     std::stringstream ss;
00354     ss << "Node ID's to compare with candidate for node " << graph_.size() << ":";
00355     for(int i = 0; i < (int)ids_to_link_to.size(); i++){
00356         ss << ids_to_link_to[i] << ", " ; 
00357     }
00358     ROS_INFO("%s", ss.str().c_str());
00359     return ids_to_link_to;
00360 }
00361 
00362 void GraphManager::resetGraph(){
00363     marker_id_ =0;
00364     ParameterServer* ps = ParameterServer::instance();
00365     createOptimizer(ps->get<std::string>("backend_solver"));
00366 
00367     //Q_FOREACH(Node* node, graph_) { delete node; }
00368     BOOST_FOREACH(GraphNodeType entry, graph_){ delete entry.second; }
00369     //for(unsigned int i = 0; i < graph_.size(); delete graph_[i++]);//No body
00370     graph_.clear();
00371     keyframe_ids_.clear();
00372     Q_EMIT resetGLViewer();
00373     last_matching_node_ = -1;
00374     latest_transform_.setToIdentity();
00375     current_poses_.clear();
00376     current_edges_.clear();
00377     reset_request_ = false;
00378     loop_closures_edges = 0; 
00379     sequential_edges = 0;
00380 }
00381 /*NEW
00382 void GraphManager::addOutliers(Node* new_node, std::vector<cv::DMatch> inlier_matches){
00383     std::vector<bool> outlier_flags(new_node->feature_locations_3d_.size(), true);
00384     BOOST_FOREACH(cv::DMatch& m, inlier_matches){ 
00385       outlier_flags[m.queryIdx] = false;
00386 
00387 }
00388 */
00389 void GraphManager::firstNode(Node* new_node) 
00390 {
00391     init_base_pose_ =  new_node->getGroundTruthTransform();//identity if no MoCap available
00392     printTransform("Ground Truth Transform for First Node", init_base_pose_);
00393     new_node->buildFlannIndex(); // create index so that next nodes can use it
00394     graph_[new_node->id_] = new_node;
00395     g2o::VertexSE3* reference_pose = new g2o::VertexSE3;
00396     reference_pose->setId(0);
00397     g2o::SE3Quat g2o_ref_se3 = tf2G2O(init_base_pose_);
00398     reference_pose->setEstimate(g2o_ref_se3);
00399     reference_pose->setFixed(true);//fix at origin
00400     optimizer_mutex.lock();
00401     optimizer_->addVertex(reference_pose); 
00402     optimizer_mutex.unlock();
00403     QString message;
00404     Q_EMIT setGUIInfo(message.sprintf("Added first node with %i keypoints to the graph", (int)new_node->feature_locations_2d_.size()));
00405     //pointcloud_type::Ptr the_pc(new_node->pc_col); //this would delete the cloud after the_pc gets out of scope
00406     latest_transform_ = g2o2QMatrix(g2o_ref_se3);
00407     printQMatrix4x4("Latest Transform", latest_transform_);
00408     Q_EMIT setPointCloud(new_node->pc_col.get(), latest_transform_);
00409     current_poses_.append(latest_transform_);
00410     ROS_DEBUG("GraphManager is thread %d, New Node is at (%p, %p)", (unsigned int)QThread::currentThreadId(), new_node, graph_[0]);
00411     keyframe_ids_.push_back(new_node->id_);
00412 
00413     process_node_runs_ = false;
00414 }
00415 
00416 // returns true, iff node could be added to the cloud
00417 bool GraphManager::addNode(Node* new_node) {
00419     struct timespec starttime, finish; double elapsed; clock_gettime(CLOCK_MONOTONIC, &starttime);
00420     process_node_runs_ = true;
00421 
00422     last_inlier_matches_.clear();
00423     if(reset_request_) resetGraph(); 
00424     ParameterServer* ps = ParameterServer::instance();
00425     if ((int)new_node->feature_locations_2d_.size() < ps->get<int>("min_matches") && 
00426         ! ps->get<bool>("keep_all_nodes"))
00427     {
00428         ROS_DEBUG("found only %i features on image, node is not included",(int)new_node->feature_locations_2d_.size());
00429         process_node_runs_ = false;
00430         return false;
00431     }
00432 
00433     //set the node id only if the node is actually added to the graph
00434     //needs to be done here as the graph size can change inside this function
00435     new_node->id_ = graph_.size();
00436 
00437     //First Node, so only build its index, insert into storage and add a
00438     //vertex at the origin, of which the position is very certain
00439     if (graph_.size()==0){
00440         firstNode(new_node);
00441         return true;
00442     }
00443 
00444     unsigned int num_edges_before = optimizer_->edges().size(); 
00445     bool edge_to_keyframe = false;
00446 
00447     ROS_DEBUG("Graphsize: %d Nodes", (int) graph_.size());
00448     marker_id_ = 0; //overdraw old markers
00449     //last_matching_node_ = -1;
00450 
00451 
00452     //Node* prev_frame = graph_[graph_.size()-1];
00453     //if(localization_only_ && last_matching_node_ > 0){ prev_frame =  graph_[last_matching_node_]; }
00454 
00455     //Odometry Stuff
00456     int sequentially_previous_id = graph_.rbegin()->second->id_; 
00457     tf::StampedTransform odom_tf_new = new_node->getOdomTransform();
00458     tf::StampedTransform odom_tf_old = graph_[sequentially_previous_id]->getOdomTransform();
00459     tf::Transform odom_delta_tf = odom_tf_new * odom_tf_old.inverse();
00460     printTransform("Odometry Delta", odom_delta_tf);
00461     if(odom_tf_old.frame_id_ == "missing_odometry" || odom_tf_new.frame_id_ == "missing_odometry"){
00462       ROS_WARN("No Valid Odometry, using identity");
00463       odom_delta_tf = tf::Transform::getIdentity();
00464     }
00465     //int best_match_candidate_id = sequentially_previous_id; 
00466 
00467     //Initial Comparison ######################################################################
00468     //First check if trafo to last frame is big
00469     //Node* prev_frame = graph_[graph_.size()-1];
00470     Node* prev_frame = graph_[graph_.size()-1];
00471     if(localization_only_ && last_matching_node_ > 0){ prev_frame =  graph_[last_matching_node_]; }
00472     last_matching_node_ = -1; //Reset this, so after the first comparison, we know the last_matching_node_ is from this call of this method
00473     ROS_INFO("Comparing new node (%i) with previous node %i", new_node->id_, prev_frame->id_);
00474     MatchingResult mr = new_node->matchNodePair(prev_frame);
00475     if(mr.edge.id1 >= 0 && !isBigTrafo(mr.edge.mean)){
00476         ROS_WARN("Transformation not relevant. Did not add as Node");
00477         //Send the current pose via tf nevertheless
00478         tf::Transform incremental = g2o2TF(mr.edge.mean);
00479         g2o::VertexSE3* v = dynamic_cast<g2o::VertexSE3*>(optimizer_->vertex(prev_frame->id_));
00480         tf::Transform previous = g2o2TF(v->estimate());
00481         tf::Transform combined = previous*incremental;
00482         broadcastTransform(new_node, combined);
00483         process_node_runs_ = false;
00484         return false;
00485     } else if(mr.edge.id1 >= 0){
00486 
00487         //mr.edge.informationMatrix *= geodesicDiscount(hypdij, mr); 
00488         ROS_DEBUG_STREAM("Information Matrix for Edge (" << mr.edge.id1 << "<->" << mr.edge.id2 << "\n" << mr.edge.informationMatrix);
00489 
00490         if (addEdgeToG2O(mr.edge, true, true)) {
00491             if(keyframe_ids_.contains(mr.edge.id1)) edge_to_keyframe = true;
00492             ROS_INFO("Added Edge between %i and %i. Inliers: %i",mr.edge.id1,mr.edge.id2,(int) mr.inlier_matches.size());
00493             last_matching_node_ = mr.edge.id1;
00494             last_inlier_matches_ = mr.inlier_matches;
00495             last_matches_ = mr.all_matches;
00496             edge_to_previous_node_ = mr.edge.mean;
00497             //addOutliers(Node* new_node, mr.inlier_matches);
00498 
00499         } else {
00500           process_node_runs_ = false;
00501           return false;
00502         }
00503     }
00504     //Eigen::Matrix4f ransac_trafo, final_trafo;
00505     QList<int> vertices_to_comp;
00506     int  seq_cand = localization_only_ ? 0 : ps->get<int>("predecessor_candidates");
00507     int geod_cand = ps->get<int>("neighbor_candidates");
00508     int samp_cand = ps->get<int>("min_sampled_candidates");
00509     vertices_to_comp = getPotentialEdgeTargetsWithDijkstra(new_node, seq_cand, geod_cand, samp_cand, last_matching_node_); 
00510     QList<const Node* > nodes_to_comp;//only necessary for parallel computation
00511 
00512     //MAIN LOOP: Compare node pairs ######################################################################
00513     if (ps->get<bool>("concurrent_edge_construction")) {
00514         for (int id_of_id = (int) vertices_to_comp.size() - 1; id_of_id >= 0; id_of_id--) {
00515             //First compile a qlist of the nodes to be compared, then run the comparisons in parallel,
00516             //collecting a qlist of the results (using the blocking version of mapped).
00517             nodes_to_comp.push_front(graph_[vertices_to_comp[id_of_id]]); 
00518         }
00519         QThreadPool* qtp = QThreadPool::globalInstance();
00520         ROS_INFO("Running node comparisons in parallel in %i (of %i) available threads", qtp->maxThreadCount() - qtp->activeThreadCount(), qtp->maxThreadCount());
00521         if (qtp->maxThreadCount() - qtp->activeThreadCount() == 1) {
00522             ROS_WARN("Few Threads Remaining: Increasing maxThreadCount to %i", qtp->maxThreadCount()+1);
00523             qtp->setMaxThreadCount(qtp->maxThreadCount() + 1);
00524         }
00525         QList<MatchingResult> results = QtConcurrent::blockingMapped(nodes_to_comp, boost::bind(&Node::matchNodePair, new_node, _1));
00526 
00527         /*
00528         while(!result_future.isFinished()){
00529           if(result_future.resultCount() > geod_cand+samp_cand && someone_is_waiting_for_me_){
00530             result_future.cancel();
00531             result_future.waitForFinished();//no dangling comparison that clash with the next node's comparison
00532             ROS_INFO("Enough comparisons");
00533             break;
00534           }
00535         }
00536         QList<MatchingResult> results = result_future.results();
00537         */
00538 
00539         for (int i = 0; i < results.size(); i++) {
00540             MatchingResult& mr = results[i];
00541 
00542             if (mr.edge.id1 >= 0) {
00543                 //mr.edge.informationMatrix *= geodesicDiscount(hypdij, mr);
00544                 ROS_INFO_STREAM("Information Matrix for Edge (" << mr.edge.id1 << "<->" << mr.edge.id2 << "\n" << mr.edge.informationMatrix);
00545 
00546                 if (addEdgeToG2O(mr.edge, isBigTrafo(mr.edge.mean), mr.inlier_matches.size() > last_inlier_matches_.size())) 
00547                 { 
00548                     ROS_INFO("Added Edge between %i and %i. Inliers: %i",mr.edge.id1,mr.edge.id2,(int) mr.inlier_matches.size());
00549                     if (mr.inlier_matches.size() > last_inlier_matches_.size()) {
00550                         last_matching_node_ = mr.edge.id1;
00551                         last_inlier_matches_ = mr.inlier_matches;
00552                         last_matches_ = mr.all_matches;
00553                         //last_edge_ = mr.edge.mean;
00554                     }
00555                     if(keyframe_ids_.contains(mr.edge.id1)) edge_to_keyframe = true;
00556                 }
00557             }
00558         }
00559     } else {
00560         for (int id_of_id = (int) vertices_to_comp.size() - 1; id_of_id >= 0; id_of_id--) {
00561             Node* node_to_compare = graph_[vertices_to_comp[id_of_id]];
00562             ROS_INFO("Comparing new node (%i) with node %i / %i", new_node->id_, vertices_to_comp[id_of_id], node_to_compare->id_);
00563             MatchingResult mr = new_node->matchNodePair(node_to_compare);
00564 
00565             if (mr.edge.id1 >= 0) {
00566                 //mr.edge.informationMatrix *= geodesicDiscount(hypdij, mr);
00567                 ROS_INFO_STREAM("Information Matrix for Edge (" << mr.edge.id1 << "<->" << mr.edge.id2 << "\n" << mr.edge.informationMatrix);
00568 
00569                 if (addEdgeToG2O(mr.edge, isBigTrafo(mr.edge.mean), mr.inlier_matches.size() > last_inlier_matches_.size())) 
00570                 {
00571                     ROS_INFO("Added Edge between %i and %i. Inliers: %i",mr.edge.id1,mr.edge.id2,(int) mr.inlier_matches.size());
00572                     if (mr.inlier_matches.size() > last_inlier_matches_.size()) {
00573                         last_matching_node_ = mr.edge.id1;
00574                         last_inlier_matches_ = mr.inlier_matches;
00575                         last_matches_ = mr.all_matches;
00576                         //last_edge_ = mr.edge.mean;
00577                     }
00578                     if(keyframe_ids_.contains(mr.edge.id1)) edge_to_keyframe = true;
00579                 }
00580             }
00581         }
00582     }
00583 
00584     //END OF MAIN LOOP: Compare node pairs ######################################################################
00585     bool found_trafo = (optimizer_->edges().size() != num_edges_before);
00586 
00587     if((found_trafo
00588         && !ps->get<std::string>("odom_frame_name").empty()
00589         && !(odom_tf_old.frame_id_ == "missing_odometry" 
00590              || odom_tf_new.frame_id_ == "missing_odometry")) 
00591        || (!found_trafo && ps->get<bool>("keep_all_nodes")))
00592     {
00593       ROS_INFO("Adding odometry motion edge for Node %i (if available, otherwise using identity)", (int)graph_.rbegin()->second->id_);
00594       LoadedEdge3D odom_edge;
00595       odom_edge.id2 = sequentially_previous_id;
00596       odom_edge.id1 = new_node->id_;
00597       odom_edge.mean = tf2G2O(odom_delta_tf);
00598       if(!found_trafo){
00599         latest_transform_ = g2o2QMatrix(odom_edge.mean);
00600         last_matching_node_ = odom_edge.id1;
00601         odom_edge.informationMatrix = Eigen::Matrix<double,6,6>::Identity()*(1); 
00602       }
00603       else {
00604         //FIXME get odometry information matrix and transform it to the optical frame
00605         odom_edge.informationMatrix = Eigen::Matrix<double,6,6>::Identity()*(10); 
00606         odom_edge.informationMatrix(1,1) = 100000000000000;
00607         odom_edge.informationMatrix(3,3) = 100000000000000;
00608         odom_edge.informationMatrix(5,5) = 100000000000000;
00609       }
00610       addEdgeToG2O(odom_edge, true,true) ;
00611     }
00612       
00613 
00614 
00615     if (optimizer_->edges().size() > num_edges_before) { //Success
00616         if(localization_only_)
00617         {
00618           optimizeGraph();
00619           broadcastTransform(new_node, computed_motion_);
00620           g2o::VertexSE3* new_v = dynamic_cast<g2o::VertexSE3*>(optimizer_->vertex(new_node->id_)); //FIXME: Mix of ids
00621           optimizer_->removeVertex(new_v); //Also removes the edges
00622         }
00623         else //localization_only_ == false
00624         {
00625           new_node->buildFlannIndex();
00626           graph_[new_node->id_] = new_node; //Node->id_ == Graph_ Index
00627           if(!edge_to_keyframe) {
00628             std::stringstream ss;
00629             ss << "Keyframes: ";
00630             BOOST_FOREACH(int id, keyframe_ids_){ ss << id << ", "; }
00631             ROS_INFO("%s", ss.str().c_str());
00632             keyframe_ids_.push_back(new_node->id_-1); //use the one before, because that one is still localized w.r.t. a keyframe
00633           }
00634           ROS_INFO("Added Node, new graphsize: %i nodes", (int) graph_.size());
00635           if((optimizer_->vertices().size() % ps->get<int>("optimizer_skip_step")) == 0){ 
00636             optimizeGraph();
00637           } else {
00638             current_poses_.append(latest_transform_);
00639             current_edges_.append( qMakePair((int)new_node->id_, last_matching_node_));
00640             //Q_EMIT setGraphEdges(new QList<QPair<int, int> >(current_edges_));
00641             //Q_EMIT updateTransforms(new QList<QMatrix4x4>(current_poses_));
00642           }
00643           //Q_EMIT updateTransforms(getAllPosesAsMatrixList());
00644           //Q_EMIT setGraphEdges(getGraphEdges());
00645           //make the transform of the last node known
00646           broadcastTransform(new_node, computed_motion_);
00647           visualizeGraphEdges();
00648           visualizeGraphNodes();
00649           visualizeFeatureFlow3D(marker_id_++);
00650           //if(last_matching_node_ <= 0){ cloudRendered(new_node->pc_col.get());}//delete points of non-matching nodes. They shall not be rendered
00651 
00652           //The following updates the 3D visualization. Important only if optimizeGraph is not called every frame, as that does the update too
00653           //pointcloud_type::Ptr the_pc = new_node->pc_col;
00654           pointcloud_type* cloud_to_visualize = new_node->pc_col.get();
00655           //if(!found_trafo) cloud_to_visualize = new pointcloud_type();
00656           Q_EMIT setPointCloud(cloud_to_visualize, latest_transform_);
00657         } 
00658     }else{ 
00659         if(graph_.size() == 1){//if there is only one node which has less features, replace it by the new one
00660           ROS_WARN("Choosing new initial node, because it has more features");
00661           if(new_node->feature_locations_2d_.size() > graph_[0]->feature_locations_2d_.size()){
00662             this->resetGraph();
00663             process_node_runs_ = false;
00664             return this->addNode(new_node);
00665           }
00666         } else { //delete new_node; //is now  done by auto_ptr
00667           ROS_WARN("Did not add as Node");
00668         }
00669     }
00670     QString message;
00671     clock_gettime(CLOCK_MONOTONIC, &finish); elapsed = (finish.tv_sec - starttime.tv_sec); elapsed += (finish.tv_nsec - starttime.tv_nsec) / 1000000000.0; ROS_INFO_STREAM_COND_NAMED(elapsed > ParameterServer::instance()->get<double>("min_time_reported"), "timings", __FUNCTION__ << " runtime: "<< elapsed <<" s");
00672     Q_EMIT setGUIInfo(message.sprintf("%s, Graph Size: %iN/%iE, Duration: %f, Inliers: %i, &chi;<sup>2</sup>: %f", 
00673                                      (optimizer_->edges().size() > num_edges_before) ? "Added" : "Ignored",
00674                                      (int)optimizer_->vertices().size(), (int)optimizer_->edges().size(),
00675                                      elapsed, (int)last_inlier_matches_.size(), optimizer_->chi2()));
00676     process_node_runs_ = false;
00677     someone_is_waiting_for_me_ = false;
00678     return (optimizer_->edges().size() > num_edges_before);
00679 }
00680 
00681 
00682 void GraphManager::visualizeFeatureFlow3D(unsigned int marker_id, bool draw_outlier) const
00683 {
00684     struct timespec starttime, finish; double elapsed; clock_gettime(CLOCK_MONOTONIC, &starttime);
00685     if (ransac_marker_pub_.getNumSubscribers() > 0){ //don't visualize, if nobody's looking
00686 
00687         visualization_msgs::Marker marker_lines;
00688 
00689         marker_lines.header.frame_id = "/camera_rgb_optical_frame";
00690         marker_lines.ns = "ransac_markers";
00691         marker_lines.header.stamp = ros::Time::now();
00692         marker_lines.action = visualization_msgs::Marker::ADD;
00693         marker_lines.pose.orientation.w = 1.0;
00694         marker_lines.id = marker_id;
00695         marker_lines.type = visualization_msgs::Marker::LINE_LIST;
00696         marker_lines.scale.x = 0.002;
00697         
00698         std_msgs::ColorRGBA color_red  ;  //red outlier
00699         color_red.r = 1.0;
00700         color_red.a = 1.0;
00701         std_msgs::ColorRGBA color_green;  //green inlier, newer endpoint
00702         color_green.g = 1.0;
00703         color_green.a = 1.0;
00704         std_msgs::ColorRGBA color_yellow;  //yellow inlier, earlier endpoint
00705         color_yellow.r = 1.0;
00706         color_yellow.g = 1.0;
00707         color_yellow.a = 1.0;
00708         std_msgs::ColorRGBA color_blue  ;  //red-blue outlier
00709         color_blue.b = 1.0;
00710         color_blue.a = 1.0;
00711 
00712         marker_lines.color = color_green; //just to set the alpha channel to non-zero
00713         const g2o::VertexSE3* earlier_v; //used to get the transform
00714         const g2o::VertexSE3* newer_v; //used to get the transform
00715         VertexIDMap v_idmap = optimizer_->vertices();
00716         // end of initialization
00717         ROS_DEBUG("Matches Visualization start: %zu Matches, %zu Inliers", last_matches_.size(), last_inlier_matches_.size());
00718 
00719         // write all inital matches to the line_list
00720         marker_lines.points.clear();//necessary?
00721 
00722         if (draw_outlier)
00723         {
00724             for (unsigned int i=0;i<last_matches_.size(); i++){
00725                 int newer_id = last_matches_.at(i).queryIdx; //feature id in newer node
00726                 int earlier_id = last_matches_.at(i).trainIdx; //feature id in earlier node
00727 
00728                 earlier_v = dynamic_cast<g2o::VertexSE3*>(optimizer_->vertex(last_matching_node_));
00729                 newer_v = dynamic_cast<g2o::VertexSE3*>(optimizer_->vertex(graph_.size()-1));
00730 
00731                 //Outliers are red (newer) to blue (older)
00732                 marker_lines.colors.push_back(color_red);
00733                 marker_lines.colors.push_back(color_blue);
00734 
00735                 Node* last = graph_.find(graph_.size()-1)->second;
00736                 marker_lines.points.push_back(
00737                         pointInWorldFrame(last->feature_locations_3d_[newer_id], newer_v->estimate()));
00738                 Node* prev = graph_.find(last_matching_node_)->second;
00739                 marker_lines.points.push_back(
00740                         pointInWorldFrame(prev->feature_locations_3d_[earlier_id], earlier_v->estimate()));
00741             }
00742         }
00743 
00744         for (unsigned int i=0;i<last_inlier_matches_.size(); i++){
00745             int newer_id = last_inlier_matches_.at(i).queryIdx; //feature id in newer node
00746             int earlier_id = last_inlier_matches_.at(i).trainIdx; //feature id in earlier node
00747 
00748             earlier_v = dynamic_cast<g2o::VertexSE3*>(optimizer_->vertex(last_matching_node_));
00749             newer_v = dynamic_cast<g2o::VertexSE3*>(optimizer_->vertex(graph_.size()-1));
00750 
00751 
00752             //inliers are green (newer) to blue (older)
00753             marker_lines.colors.push_back(color_green);
00754             marker_lines.colors.push_back(color_blue);
00755 
00756             Node* last = graph_.find(graph_.size()-1)->second;
00757             marker_lines.points.push_back(
00758                     pointInWorldFrame(last->feature_locations_3d_[newer_id], newer_v->estimate()));
00759             Node* prev = graph_.find(last_matching_node_)->second;
00760             marker_lines.points.push_back(
00761                     pointInWorldFrame(prev->feature_locations_3d_[earlier_id], earlier_v->estimate()));
00762         }
00763 
00764         ransac_marker_pub_.publish(marker_lines);
00765         ROS_DEBUG_STREAM("Published  " << marker_lines.points.size()/2 << " lines");
00766     }
00767     clock_gettime(CLOCK_MONOTONIC, &finish); elapsed = (finish.tv_sec - starttime.tv_sec); elapsed += (finish.tv_nsec - starttime.tv_nsec) / 1000000000.0; ROS_INFO_STREAM_COND_NAMED(elapsed > ParameterServer::instance()->get<double>("min_time_reported"), "timings", __FUNCTION__ << " runtime: "<< elapsed <<" s");
00768 }
00769 
00770 
00771 void GraphManager::visualizeGraphEdges() const {
00772     struct timespec starttime, finish; double elapsed; clock_gettime(CLOCK_MONOTONIC, &starttime);
00773 
00774     if (marker_pub_.getNumSubscribers() > 0){ //no visualization for nobody
00775         visualization_msgs::Marker edges_marker;
00776         edges_marker.header.frame_id = "/camera_rgb_optical_frame"; //TODO: Should be a meaningfull fixed frame with known relative pose to the camera
00777         edges_marker.header.stamp = ros::Time::now();
00778         edges_marker.ns = "camera_pose_graph"; // Set the namespace and id for this marker.  This serves to create a unique ID
00779         edges_marker.id = 0;    // Any marker sent with the same namespace and id will overwrite the old one
00780 
00781         edges_marker.type = visualization_msgs::Marker::LINE_LIST;
00782         edges_marker.action = visualization_msgs::Marker::ADD; // Set the marker action.  Options are ADD and DELETE
00783         edges_marker.frame_locked = true; //rviz automatically retransforms the markers into the frame every update cycle
00784         // Set the scale of the marker -- 1x1x1 here means 1m on a side
00785         edges_marker.scale.x = 0.005; //line width
00786         //Global pose (used to transform all points)
00787         edges_marker.pose.position.x = 0;
00788         edges_marker.pose.position.y = 0;
00789         edges_marker.pose.position.z = 0;
00790         edges_marker.pose.orientation.x = 0.0;
00791         edges_marker.pose.orientation.y = 0.0;
00792         edges_marker.pose.orientation.z = 0.0;
00793         edges_marker.pose.orientation.w = 1.0;
00794         // Set the color -- be sure to set alpha to something non-zero!
00795         edges_marker.color.r = 1.0f;
00796         edges_marker.color.g = 1.0f;
00797         edges_marker.color.b = 1.0f;
00798         edges_marker.color.a = 0.5f;//looks smoother
00799         geometry_msgs::Point point; //start and endpoint for each line segment
00800         g2o::VertexSE3* v1,* v2; //used in loop
00801         EdgeSet::iterator edge_iter = optimizer_->edges().begin();
00802         int counter = 0;
00803         for(;edge_iter != optimizer_->edges().end(); edge_iter++, counter++) {
00804             g2o::EdgeSE3* myedge = dynamic_cast<g2o::EdgeSE3*>(*edge_iter);
00805             std::vector<g2o::HyperGraph::Vertex*>& myvertices = myedge->vertices();
00806             v1 = dynamic_cast<g2o::VertexSE3*>(myvertices.at(1));
00807             v2 = dynamic_cast<g2o::VertexSE3*>(myvertices.at(0));
00808 
00809             point.x = v1->estimate().translation().x();
00810             point.y = v1->estimate().translation().y();
00811             point.z = v1->estimate().translation().z();
00812             edges_marker.points.push_back(point);
00813             
00814             point.x = v2->estimate().translation().x();
00815             point.y = v2->estimate().translation().y();
00816             point.z = v2->estimate().translation().z();
00817             edges_marker.points.push_back(point);
00818         }
00819 
00820         marker_pub_.publish (edges_marker);
00821         ROS_INFO("published %d graph edges", counter);
00822     }
00823 
00824     clock_gettime(CLOCK_MONOTONIC, &finish); elapsed = (finish.tv_sec - starttime.tv_sec); elapsed += (finish.tv_nsec - starttime.tv_nsec) / 1000000000.0; ROS_INFO_STREAM_COND_NAMED(elapsed > ParameterServer::instance()->get<double>("min_time_reported"), "timings", __FUNCTION__ << " runtime: "<< elapsed <<" s");
00825 }
00826 
00827 void GraphManager::visualizeGraphNodes() const {
00828     struct timespec starttime, finish; double elapsed; clock_gettime(CLOCK_MONOTONIC, &starttime);
00829 
00830     if (marker_pub_.getNumSubscribers() > 0){ //don't visualize, if nobody's looking
00831         visualization_msgs::Marker nodes_marker;
00832         nodes_marker.header.frame_id = "/camera_rgb_optical_frame"; //TODO: Should be a meaningfull fixed frame with known relative pose to the camera
00833         nodes_marker.header.stamp = ros::Time::now();
00834         nodes_marker.ns = "camera_pose_graph"; // Set the namespace and id for this marker.  This serves to create a unique ID
00835         nodes_marker.id = 1;    // Any marker sent with the same namespace and id will overwrite the old one
00836 
00837 
00838         nodes_marker.type = visualization_msgs::Marker::LINE_LIST;
00839         nodes_marker.action = visualization_msgs::Marker::ADD; // Set the marker action.  Options are ADD and DELETE
00840         nodes_marker.frame_locked = true; //rviz automatically retransforms the markers into the frame every update cycle
00841         // Set the scale of the marker -- 1x1x1 here means 1m on a side
00842         nodes_marker.scale.x = 0.002;
00843         //Global pose (used to transform all points) //TODO: is this the default pose anyway?
00844         nodes_marker.pose.position.x = 0;
00845         nodes_marker.pose.position.y = 0;
00846         nodes_marker.pose.position.z = 0;
00847         nodes_marker.pose.orientation.x = 0.0;
00848         nodes_marker.pose.orientation.y = 0.0;
00849         nodes_marker.pose.orientation.z = 0.0;
00850         nodes_marker.pose.orientation.w = 1.0;
00851         // Set the color -- be sure to set alpha to something non-zero!
00852         nodes_marker.color.r = 1.0f;
00853         nodes_marker.color.g = 0.0f;
00854         nodes_marker.color.b = 0.0f;
00855         nodes_marker.color.a = 1.0f;
00856 
00857 
00858         geometry_msgs::Point tail; //same startpoint for each line segment
00859         geometry_msgs::Point tip;  //different endpoint for each line segment
00860         std_msgs::ColorRGBA arrow_color_red  ;  //red x axis
00861         arrow_color_red.r = 1.0;
00862         arrow_color_red.a = 1.0;
00863         std_msgs::ColorRGBA arrow_color_green;  //green y axis
00864         arrow_color_green.g = 1.0;
00865         arrow_color_green.a = 1.0;
00866         std_msgs::ColorRGBA arrow_color_blue ;  //blue z axis
00867         arrow_color_blue.b = 1.0;
00868         arrow_color_blue.a = 1.0;
00869         Eigen::Vector3d origin(0.0,0.0,0.0);
00870         Eigen::Vector3d x_axis(0.2,0.0,0.0); //20cm long axis for the first (almost fixed) node
00871         Eigen::Vector3d y_axis(0.0,0.2,0.0);
00872         Eigen::Vector3d z_axis(0.0,0.0,0.2);
00873         Eigen::Vector3d tmp; //the transformed endpoints
00874         int counter = 0;
00875         g2o::VertexSE3* v; //used in loop
00876         VertexIDMap::iterator vertex_iter = optimizer_->vertices().begin();
00877         for(/*see above*/; vertex_iter != optimizer_->vertices().end(); vertex_iter++, counter++) {
00878             v = dynamic_cast<g2o::VertexSE3* >((*vertex_iter).second);
00879             //v->estimate().rotation().x()+ v->estimate().rotation().y()+ v->estimate().rotation().z()+ v->estimate().rotation().w();
00880             tmp = v->estimate() * origin;
00881             tail.x = tmp.x();
00882             tail.y = tmp.y();
00883             tail.z = tmp.z();
00884             //Endpoints X-Axis
00885             nodes_marker.points.push_back(tail);
00886             nodes_marker.colors.push_back(arrow_color_red);
00887             tmp = v->estimate() * x_axis;
00888             tip.x  = tmp.x();
00889             tip.y  = tmp.y();
00890             tip.z  = tmp.z();
00891             nodes_marker.points.push_back(tip);
00892             nodes_marker.colors.push_back(arrow_color_red);
00893             //Endpoints Y-Axis
00894             nodes_marker.points.push_back(tail);
00895             nodes_marker.colors.push_back(arrow_color_green);
00896             tmp = v->estimate() * y_axis;
00897             tip.x  = tmp.x();
00898             tip.y  = tmp.y();
00899             tip.z  = tmp.z();
00900             nodes_marker.points.push_back(tip);
00901             nodes_marker.colors.push_back(arrow_color_green);
00902             //Endpoints Z-Axis
00903             nodes_marker.points.push_back(tail);
00904             nodes_marker.colors.push_back(arrow_color_blue);
00905             tmp = v->estimate() * z_axis;
00906             tip.x  = tmp.x();
00907             tip.y  = tmp.y();
00908             tip.z  = tmp.z();
00909             nodes_marker.points.push_back(tip);
00910             nodes_marker.colors.push_back(arrow_color_blue);
00911             //shorten all nodes after the first one
00912             x_axis.x() = 0.1;
00913             y_axis.y() = 0.1;
00914             z_axis.z() = 0.1;
00915         }
00916 
00917         marker_pub_.publish (nodes_marker);
00918         ROS_INFO("published %d graph nodes", counter);
00919     }
00920 
00921     clock_gettime(CLOCK_MONOTONIC, &finish); elapsed = (finish.tv_sec - starttime.tv_sec); elapsed += (finish.tv_nsec - starttime.tv_nsec) / 1000000000.0; ROS_INFO_STREAM_COND_NAMED(elapsed > ParameterServer::instance()->get<double>("min_time_reported"), "timings", __FUNCTION__ << " runtime: "<< elapsed <<" s");
00922 }
00923 
00924 bool GraphManager::addEdgeToG2O(const LoadedEdge3D& edge, bool largeEdge, bool set_estimate) {
00925     struct timespec starttime, finish; double elapsed; clock_gettime(CLOCK_MONOTONIC, &starttime);
00926 
00927     QMutexLocker locker(&optimizer_mutex);
00928     g2o::VertexSE3* v1 = dynamic_cast<g2o::VertexSE3*>(optimizer_->vertex(edge.id1));
00929     g2o::VertexSE3* v2 = dynamic_cast<g2o::VertexSE3*>(optimizer_->vertex(edge.id2));
00930 
00931     // at least one vertex has to be created, assert that the transformation
00932     // is large enough to avoid to many vertices on the same spot
00933     if (!v1 || !v2){
00934         if (!largeEdge) {
00935             ROS_INFO("Edge to new vertex is to short, vertex will not be inserted");
00936             return false; 
00937         }
00938     }
00939 
00940     if(!v1 && !v2){
00941       ROS_ERROR("Missing both vertices: %i, %i, cannot create edge", edge.id1, edge.id2);
00942       return false;
00943     }
00944     else if (!v1 && v2) {
00945         v1 = new g2o::VertexSE3;
00946         assert(v1);
00947         v1->setId(edge.id1);
00948         v1->setEstimate(v2->estimate() * edge.mean.inverse());
00949         optimizer_->addVertex(v1); 
00950         latest_transform_ = g2o2QMatrix(v1->estimate()); 
00951     }
00952     else if (!v2 && v1) {
00953         v2 = new g2o::VertexSE3;
00954         assert(v2);
00955         v2->setId(edge.id2);
00956         v2->setEstimate(v1->estimate() * edge.mean);
00957         optimizer_->addVertex(v2); 
00958         latest_transform_ = g2o2QMatrix(v2->estimate()); 
00959     }
00960     else if(set_estimate){
00961         v2->setEstimate(v1->estimate() * edge.mean);
00962     }
00963     g2o::EdgeSE3* g2o_edge = new g2o::EdgeSE3;
00964     g2o_edge->vertices()[0] = v1;
00965     g2o_edge->vertices()[1] = v2;
00966     g2o_edge->setMeasurement(edge.mean);
00967     g2o_edge->setInverseMeasurement(edge.mean.inverse());
00968     g2o_edge->setInformation(edge.informationMatrix);
00969     optimizer_->addEdge(g2o_edge);
00970 
00971     if(abs(edge.id1 - edge.id2) > ParameterServer::instance()->get<int>("predecessor_candidates")){
00972       loop_closures_edges++;
00973     } else {
00974       sequential_edges++;
00975     }
00976 
00977     clock_gettime(CLOCK_MONOTONIC, &finish); elapsed = (finish.tv_sec - starttime.tv_sec); elapsed += (finish.tv_nsec - starttime.tv_nsec) / 1000000000.0; ROS_INFO_STREAM_COND_NAMED(elapsed > ParameterServer::instance()->get<double>("min_time_reported"), "timings", __FUNCTION__ << " runtime: "<< elapsed <<" s");
00978     return true;
00979 }
00980 
00981 void GraphManager::optimizeGraph(int max_iter, bool nonthreaded){
00982   if(ParameterServer::instance()->get<bool>("concurrent_optimization") && !nonthreaded) {
00983     ROS_DEBUG("Optimization done in Thread");
00984     QtConcurrent::run(this, &GraphManager::optimizeGraphImpl, max_iter); 
00985   }
00986   else { //Non-concurrent
00987     optimizeGraphImpl(max_iter);//regular function call
00988   }
00989 }
00990 
00991 void GraphManager::optimizeGraphImpl(int max_iter)
00992 {
00993   struct timespec starttime, finish; double elapsed; clock_gettime(CLOCK_MONOTONIC, &starttime);
00994   int iterations = max_iter >= 0 ? max_iter : ParameterServer::instance()->get<int>("optimizer_iterations");
00995   QMutexLocker locker(&optimizer_mutex);
00996 
00997   ROS_WARN_NAMED("eval", "Loop Closures: %u, Sequential Edges: %u", loop_closures_edges, sequential_edges);
00998   if(ParameterServer::instance()->get<std::string>("backend_solver") == "auto"){
00999     if(static_cast<float>(sequential_edges)/static_cast<float>(loop_closures_edges) < 1.0){
01000       ROS_WARN_NAMED("eval", "Using PCG as solver");
01001       if(current_backend_ != "pcg") createOptimizer("pcg", optimizer_);
01002     } else {
01003       ROS_WARN_NAMED("eval", "Using CHOLMOD as solver");
01004       if(current_backend_ != "cholmod") createOptimizer("cholmod", optimizer_);
01005     }
01006   }
01007 
01008   ROS_WARN("Starting Optimization");
01009   std::string bagfile_name = ParameterServer::instance()->get<std::string>("bagfile_name");
01010   optimizer_->save((bagfile_name + "_g2o-optimizer-save-file-before").c_str());
01011   optimizer_->initializeOptimization();
01012   double prev_chi2 = std::numeric_limits<double>::max();
01013   int i = 0;
01014   for(; i <  iterations; i++){
01015     int currentIt = optimizer_->optimize(1);
01016     optimizer_->computeActiveErrors();
01017     ROS_WARN_STREAM_NAMED("eval", "G2O Statistics: " << std::setprecision(15) << optimizer_->vertices().size() << " nodes, " 
01018                     << optimizer_->edges().size() << " edges. "
01019                     << optimizer_->chi2() << " ; chi2 "<< ", Iterations: " << currentIt);
01020     if(!(prev_chi2 - optimizer_->chi2() > optimizer_->chi2()*0.0001)){ // improvement less than 0.01 Percent
01021       ROS_INFO("G2O Converged after %d iterations", i);
01022       break;
01023     }
01024     prev_chi2 = optimizer_->chi2();
01025   }
01026   ROS_INFO("Finished G2O optimization after %d iterations", i);
01027   optimizer_->save((bagfile_name + "_g2o-optimizer-save-file-after").c_str());
01028 
01029   g2o::VertexSE3* v = dynamic_cast<g2o::VertexSE3*>(optimizer_->vertex(optimizer_->vertices().size()-1));
01030 
01031   computed_motion_ =  g2o2TF(v->estimate());
01032   latest_transform_ = g2o2QMatrix(v->estimate()); 
01033   Q_EMIT setGraphEdges(getGraphEdges());
01034   Q_EMIT updateTransforms(getAllPosesAsMatrixList());
01035 
01036   clock_gettime(CLOCK_MONOTONIC, &finish); elapsed = (finish.tv_sec - starttime.tv_sec); elapsed += (finish.tv_nsec - starttime.tv_nsec) / 1000000000.0; ROS_INFO_STREAM_COND_NAMED(elapsed > ParameterServer::instance()->get<double>("min_time_reported"), "timings", __FUNCTION__ << " runtime: "<< elapsed <<" s");
01037   ROS_WARN_STREAM_NAMED("eval", "Optimizer Runtime; "<< elapsed <<" s");
01038 }
01039 
01040 
01063 void GraphManager::reset(){
01064     reset_request_ = true;
01065 }
01066 
01067 void GraphManager::deleteLastFrame(){
01068     if(graph_.size() <= 1) {
01069       ROS_INFO("Resetting, as the only node is to be deleted");
01070       reset_request_ = true;
01071       Q_EMIT deleteLastNode();
01072       return;
01073     }
01074     optimizer_mutex.lock();
01075     g2o::VertexSE3* v_to_del = dynamic_cast<g2o::VertexSE3*>(optimizer_->vertex(optimizer_->vertices().size()-1));//last vertex
01076     g2o::VertexSE3 *v1, *v2; //used in loop as temporaries
01077     EdgeSet::iterator edge_iter = optimizer_->edges().begin();
01078     for(;edge_iter != optimizer_->edges().end(); edge_iter++) {
01079         g2o::EdgeSE3* myedge = dynamic_cast<g2o::EdgeSE3*>(*edge_iter);
01080         std::vector<g2o::HyperGraph::Vertex*>& myvertices = myedge->vertices();
01081         v1 = dynamic_cast<g2o::VertexSE3*>(myvertices.at(1));
01082         v2 = dynamic_cast<g2o::VertexSE3*>(myvertices.at(0));
01083         if(v1->id() == v_to_del->id() || v2->id() == v_to_del->id()) 
01084           optimizer_->removeEdge((*edge_iter));
01085     }
01086 
01087     optimizer_->removeVertex(v_to_del);
01088     graph_.erase(graph_.size()-1);
01089     optimizer_mutex.unlock();
01090     Q_EMIT deleteLastNode();
01091     optimizeGraph();//s.t. the effect of the removed edge transforms are removed to
01092     ROS_INFO("Removed most recent node");
01093     Q_EMIT setGUIInfo("Removed most recent node");
01094     //Q_EMIT setGraphEdges(getGraphEdges());
01095     //updateTransforms needs to be last, as it triggers a redraw
01096     //Q_EMIT updateTransforms(getAllPosesAsMatrixList());
01097 }
01098 
01099 QList<QPair<int, int> >* GraphManager::getGraphEdges()
01100 {
01101     struct timespec starttime, finish; double elapsed; clock_gettime(CLOCK_MONOTONIC, &starttime);
01102     //QList<QPair<int, int> >* edge_list = new QList<QPair<int, int> >();
01103     current_edges_.clear();
01104     g2o::VertexSE3 *v1, *v2; //used in loop
01105     EdgeSet::iterator edge_iter = optimizer_->edges().begin();
01106     for(;edge_iter != optimizer_->edges().end(); edge_iter++) {
01107         g2o::EdgeSE3* myedge = dynamic_cast<g2o::EdgeSE3*>(*edge_iter);
01108         std::vector<g2o::HyperGraph::Vertex*>& myvertices = myedge->vertices();
01109         v1 = dynamic_cast<g2o::VertexSE3*>(myvertices.at(1));
01110         v2 = dynamic_cast<g2o::VertexSE3*>(myvertices.at(0));
01111         current_edges_.append( qMakePair(v1->id(), v2->id()));
01112     }
01113     clock_gettime(CLOCK_MONOTONIC, &finish); elapsed = (finish.tv_sec - starttime.tv_sec); elapsed += (finish.tv_nsec - starttime.tv_nsec) / 1000000000.0; ROS_INFO_STREAM_COND_NAMED(elapsed > ParameterServer::instance()->get<double>("min_time_reported"), "timings", __FUNCTION__ << " runtime: "<< elapsed <<" s");
01114     return new QList<QPair<int, int> >(current_edges_);
01115 }
01116 
01117 QList<QMatrix4x4>* GraphManager::getAllPosesAsMatrixList(){
01118     struct timespec starttime, finish; double elapsed; clock_gettime(CLOCK_MONOTONIC, &starttime);
01119     ROS_DEBUG("Retrieving all transformations from optimizer");
01120     //QList<QMatrix4x4>* result = new QList<QMatrix4x4>();
01121     current_poses_.clear();
01122 #if defined(QT_VERSION) && QT_VERSION >= 0x040700
01123     current_poses_.reserve(optimizer_->vertices().size());//only allocates the internal pointer array
01124 #endif
01125 
01126     for (unsigned int i = 0; i < optimizer_->vertices().size(); ++i) {
01127         g2o::VertexSE3* v = dynamic_cast<g2o::VertexSE3*>(optimizer_->vertex(i));
01128         if(v){ 
01129             current_poses_.push_back(g2o2QMatrix(v->estimate())); 
01130         } else {
01131             ROS_ERROR("Nullpointer in graph at position %i!", i);
01132         }
01133     }
01134     clock_gettime(CLOCK_MONOTONIC, &finish); elapsed = (finish.tv_sec - starttime.tv_sec); elapsed += (finish.tv_nsec - starttime.tv_nsec) / 1000000000.0; ROS_INFO_STREAM_COND_NAMED(elapsed > ParameterServer::instance()->get<double>("min_time_reported"), "timings", __FUNCTION__ << " runtime: "<< elapsed <<" s");
01135     return new QList<QMatrix4x4>(current_poses_); //pointer to a copy
01136 }
01137 // If QT Concurrent is available, run the saving in a seperate thread
01138 void GraphManager::saveAllClouds(QString filename, bool threaded){
01139     if (ParameterServer::instance()->get<bool>("concurrent_edge_construction") && threaded) {
01140         QFuture<void> f1 = QtConcurrent::run(this, &GraphManager::saveAllCloudsToFile, filename);
01141         //f1.waitForFinished();
01142     }
01143     else {// Otherwise just call it without threading
01144         saveAllCloudsToFile(filename);
01145     }
01146 }
01147 
01148 void GraphManager::saveIndividualClouds(QString filename, bool threaded){
01149   if (ParameterServer::instance()->get<bool>("concurrent_edge_construction") && threaded) {
01150     QFuture<void> f1 = QtConcurrent::run(this, &GraphManager::saveIndividualCloudsToFile, filename);
01151     //f1.waitForFinished();
01152   }
01153   else {
01154     saveIndividualCloudsToFile(filename);
01155   }
01156 }
01157 
01158 void GraphManager::saveIndividualCloudsToFile(QString file_basename)
01159 {
01160     struct timespec starttime, finish; double elapsed; clock_gettime(CLOCK_MONOTONIC, &starttime);
01161     ROS_INFO("Saving all clouds to %sxxxx.pcd", qPrintable(file_basename));
01162     std::string gt = ParameterServer::instance()->get<std::string>("ground_truth_frame_name");
01163     ROS_INFO_COND(!gt.empty(), "Saving all clouds with ground truth sensor position to gt_%sxxxx.pcd", qPrintable(file_basename));
01164 
01165     batch_processing_runs_ = true;
01166     tf::Transform  world2base;
01167     QString message, filename;
01168     std::string fixed_frame_id = ParameterServer::instance()->get<std::string>("fixed_frame_name");
01169     for (unsigned int i = 0; i < optimizer_->vertices().size(); ++i) {
01170         g2o::VertexSE3* v = dynamic_cast<g2o::VertexSE3*>(optimizer_->vertex(i));
01171         if(!v){ 
01172             ROS_ERROR("Nullpointer in graph at position %i!", i);
01173             continue;
01174         }
01175         if(graph_[i]->pc_col->size() == 0){
01176             ROS_INFO("Skipping Node %i, point cloud data is empty!", i);
01177             continue;
01178         }
01179         /*/TODO: is all this correct?
01180         tf::Transform transform = g2o2TF(v->estimate());
01181         tf::Transform cam2rgb;
01182         cam2rgb.setRotation(tf::createQuaternionFromRPY(-1.57,0,-1.57));
01183         cam2rgb.setOrigin(tf::Point(0,-0.04,0));
01184         world2base = cam2rgb*transform;
01185         */
01186         tf::Transform pose = g2o2TF(v->estimate());
01187         tf::StampedTransform base2points = graph_[i]->getBase2PointsTransform();//get pose of base w.r.t current pc at capture time
01188         world2base = init_base_pose_*base2points*pose*base2points.inverse();
01189 
01190         Eigen::Vector4f sensor_origin(world2base.getOrigin().x(),world2base.getOrigin().y(),world2base.getOrigin().z(),world2base.getOrigin().w());
01191         Eigen::Quaternionf sensor_orientation(world2base.getRotation().w(),world2base.getRotation().x(),world2base.getRotation().y(),world2base.getRotation().z());
01192 
01193         graph_[i]->pc_col->sensor_origin_ = sensor_origin;
01194         graph_[i]->pc_col->sensor_orientation_ = sensor_orientation;
01195         graph_[i]->pc_col->header.frame_id = fixed_frame_id;
01196 
01197         filename.sprintf("%s_%04d.pcd", qPrintable(file_basename), i);
01198         Q_EMIT setGUIStatus(message.sprintf("Saving to %s: Transformed Node %i/%i", qPrintable(filename), i, (int)optimizer_->vertices().size()));
01199         pcl::io::savePCDFile(qPrintable(filename), *(graph_[i]->pc_col), true); //Last arg: true is binary mode. ASCII mode drops color bits
01200         
01201         if(!gt.empty()){
01202           tf::StampedTransform gt_world2base = graph_[i  ]->getGroundTruthTransform();//get mocap pose of base in map
01203           if( gt_world2base.frame_id_   == "/missing_ground_truth" ){ 
01204             ROS_WARN_STREAM("Skipping ground truth: " << gt_world2base.child_frame_id_ << " child/parent " << gt_world2base.frame_id_);
01205             continue;
01206           }
01207           Eigen::Vector4f sensor_origin(gt_world2base.getOrigin().x(),gt_world2base.getOrigin().y(),gt_world2base.getOrigin().z(),gt_world2base.getOrigin().w());
01208           Eigen::Quaternionf sensor_orientation(gt_world2base.getRotation().w(),gt_world2base.getRotation().x(),gt_world2base.getRotation().y(),gt_world2base.getRotation().z());
01209 
01210           graph_[i]->pc_col->sensor_origin_ = sensor_origin;
01211           graph_[i]->pc_col->sensor_orientation_ = sensor_orientation;
01212           graph_[i]->pc_col->header.frame_id = fixed_frame_id;
01213 
01214           filename.sprintf("%s_%04d_gt.pcd", qPrintable(file_basename), i);
01215           Q_EMIT setGUIStatus(message.sprintf("Saving to %s: Transformed Node %i/%i", qPrintable(filename), i, (int)optimizer_->vertices().size()));
01216           pcl::io::savePCDFile(qPrintable(filename), *(graph_[i]->pc_col), true); //Last arg: true is binary mode. ASCII mode drops color bits
01217         }
01218   
01219     }
01220     Q_EMIT setGUIStatus("Saved all point clouds");
01221     ROS_INFO ("Saved all points clouds to %sxxxx.pcd", qPrintable(file_basename));
01222     batch_processing_runs_ = false;
01223     clock_gettime(CLOCK_MONOTONIC, &finish); elapsed = (finish.tv_sec - starttime.tv_sec); elapsed += (finish.tv_nsec - starttime.tv_nsec) / 1000000000.0; ROS_INFO_STREAM_COND_NAMED(elapsed > ParameterServer::instance()->get<double>("min_time_reported"), "timings", __FUNCTION__ << " runtime: "<< elapsed <<" s");
01224 }
01225 
01226 void GraphManager::saveAllFeatures(QString filename, bool threaded)
01227 {
01228     if (ParameterServer::instance()->get<bool>("concurrent_edge_construction") && threaded) {
01229         QFuture<void> f1 = QtConcurrent::run(this, &GraphManager::saveAllFeaturesToFile, filename);
01230         //f1.waitForFinished();
01231     }
01232     else {// Otherwise just call it without threading
01233         saveAllFeaturesToFile(filename);
01234     }
01235 }
01236 void GraphManager::saveAllFeaturesToFile(QString filename)
01237 {
01238     struct timespec starttime, finish; double elapsed; clock_gettime(CLOCK_MONOTONIC, &starttime);
01239     cv::FileStorage fs(qPrintable(filename), cv::FileStorage::WRITE);
01240     fs << "Feature_Locations" << "[";
01241 
01242     ROS_INFO("Saving all features to %s transformed to a common coordinate frame.", qPrintable(filename));
01243     batch_processing_runs_ = true;
01244     tf::Transform  world2rgb, cam2rgb;
01245     QString message;
01246     cam2rgb.setRotation(tf::createQuaternionFromRPY(-1.57,0,-1.57));
01247     cam2rgb.setOrigin(tf::Point(0,-0.04,0));
01248     int feat_count = 0;
01249     for (unsigned int i = 0; i < optimizer_->vertices().size(); ++i) {
01250         g2o::VertexSE3* v = dynamic_cast<g2o::VertexSE3*>(optimizer_->vertex(i));
01251         tf::Transform world2cam = g2o2TF(v->estimate());
01252         world2rgb = cam2rgb*world2cam;
01253         Eigen::Matrix4f world2rgbMat;
01254         pcl_ros::transformAsMatrix(world2rgb, world2rgbMat);
01255         BOOST_FOREACH(Eigen::Vector4f loc, graph_[i]->feature_locations_3d_)
01256         {
01257           loc.w() = 1.0;
01258           Eigen::Vector4f new_loc = world2rgbMat * loc;
01259           fs << "{:" << "x" << new_loc.x() << "y" << new_loc.y() << "z" << new_loc.z() << "}";
01260           feat_count++;
01261         }
01262         Q_EMIT setGUIStatus(message.sprintf("Saving to %s: Features of Node %i/%i", qPrintable(filename), i, (int)optimizer_->vertices().size()));
01263     }
01264     fs << "]";
01265     //Assemble all descriptors into a big matrix
01266     int descriptor_size = graph_[0]->feature_descriptors_.cols;
01267     int descriptor_type = graph_[0]->feature_descriptors_.type();
01268     cv::Mat alldescriptors(0, descriptor_size,  descriptor_type);
01269     alldescriptors.reserve(feat_count);
01270     for (unsigned int i = 0; i < graph_.size(); ++i) {
01271       alldescriptors.push_back(graph_[i]->feature_descriptors_);
01272     }
01273     fs << "Feature_Descriptors" << alldescriptors;
01274     fs.release();
01275 
01276     Q_EMIT setGUIStatus(message.sprintf("Saved %d features points to %s", feat_count, qPrintable(filename)));
01277     ROS_INFO ("Saved %d feature points to %s", feat_count, qPrintable(filename));
01278     batch_processing_runs_ = false;
01279     clock_gettime(CLOCK_MONOTONIC, &finish); elapsed = (finish.tv_sec - starttime.tv_sec); elapsed += (finish.tv_nsec - starttime.tv_nsec) / 1000000000.0; ROS_INFO_STREAM_COND_NAMED(elapsed > ParameterServer::instance()->get<double>("min_time_reported"), "timings", __FUNCTION__ << " runtime: "<< elapsed <<" s");
01280 }
01281 
01282 
01283 
01284 
01285 void GraphManager::saveAllCloudsToFile(QString filename){
01286     struct timespec starttime, finish; double elapsed; clock_gettime(CLOCK_MONOTONIC, &starttime);
01287     pointcloud_type aggregate_cloud; 
01288     ROS_INFO("Saving all clouds to %s, this may take a while as they need to be transformed to a common coordinate frame.", qPrintable(filename));
01289     batch_processing_runs_ = true;
01290     tf::Transform  world2cam;
01291     //fill message
01292     //rgbdslam::CloudTransforms msg;
01293     QString message;
01294     //As everything is relative to the first frame: Just use its transform to get things to base frame
01295     tf::StampedTransform base2points = graph_[0]->getBase2PointsTransform();
01296     for (unsigned int i = 0; i < optimizer_->vertices().size(); ++i) {
01297         g2o::VertexSE3* v = dynamic_cast<g2o::VertexSE3*>(optimizer_->vertex(i));
01298         if(!v){ 
01299             ROS_ERROR("Nullpointer in graph at position %i!", i);
01300             continue;
01301         }
01302         tf::Transform transform = g2o2TF(v->estimate());
01303         world2cam = base2points*transform;
01304         transformAndAppendPointCloud (*(graph_[i]->pc_col), aggregate_cloud, world2cam, Max_Depth);
01305 
01306         if(ParameterServer::instance()->get<bool>("batch_processing"))
01307           graph_[i]->clearPointCloud(); //saving all is the last thing to do, so these are not required anymore
01308         Q_EMIT setGUIStatus(message.sprintf("Saving to %s: Transformed Node %i/%i", qPrintable(filename), i, (int)optimizer_->vertices().size()));
01309     }
01310     //Set base frame id
01311     aggregate_cloud.header.frame_id = base2points.frame_id_;
01312 
01313     if(filename.endsWith(".ply", Qt::CaseInsensitive))
01314       pointCloud2MeshFile(filename, aggregate_cloud);
01315     if(filename.endsWith(".pcd", Qt::CaseInsensitive))
01316       pcl::io::savePCDFile(qPrintable(filename), aggregate_cloud, true); //Last arg is binary mode
01317     else {
01318       ROS_WARN("Filename misses correct extension (.pcd or .ply) using .pcd");
01319       filename.append(".pcd");
01320       pcl::io::savePCDFile(qPrintable(filename), aggregate_cloud, true); //Last arg is binary mode
01321     }
01322     Q_EMIT setGUIStatus(message.sprintf("Saved %d data points to %s", (int)aggregate_cloud.points.size(), qPrintable(filename)));
01323     ROS_INFO ("Saved %d data points to %s", (int)aggregate_cloud.points.size(), qPrintable(filename));
01324 
01325     if (whole_cloud_pub_.getNumSubscribers() > 0){ //if it should also be send out
01326         aggregate_cloud.header.stamp = ros::Time::now();//Avoid problems with tf cache size if mapping started long ago. Assumption: Base frame hasn't moved
01327         whole_cloud_pub_.publish(aggregate_cloud);
01328         ROS_INFO("Aggregate pointcloud sent");
01329     }
01330     batch_processing_runs_ = false;
01331     clock_gettime(CLOCK_MONOTONIC, &finish); elapsed = (finish.tv_sec - starttime.tv_sec); elapsed += (finish.tv_nsec - starttime.tv_nsec) / 1000000000.0; ROS_INFO_STREAM_COND_NAMED(elapsed > ParameterServer::instance()->get<double>("min_time_reported"), "timings", __FUNCTION__ << " runtime: "<< elapsed <<" s");
01332 }
01333 
01334 void GraphManager::pointCloud2MeshFile(QString filename, pointcloud_type full_cloud){
01335   QFile file(filename);//file is closed on destruction
01336   if(!file.open(QIODevice::WriteOnly|QIODevice::Text)){
01337     ROS_ERROR("Could not open file %s", qPrintable(filename));
01338     return; 
01339   }
01340   QTextStream out(&file);
01341         out << "ply\n";
01342         out << "format ascii 1.0\n";
01343         out << "element vertex " << (int)full_cloud.points.size() << "\n"; 
01344         out << "property float x\n";
01345         out << "property float y\n";
01346         out << "property float z\n";
01347         out << "property uchar red\n";
01348         out << "property uchar green\n";
01349         out << "property uchar blue\n";
01350         out << "end_header\n";
01351   unsigned char r,g,b;
01352   float x, y, z ;
01353   for(unsigned int i = 0; i < full_cloud.points.size() ; i++){
01354     b = *(  (unsigned char*)&(full_cloud.points[i].rgb));
01355     g = *(1+(unsigned char*)&(full_cloud.points[i].rgb));
01356     r = *(2+(unsigned char*)&(full_cloud.points[i].rgb));
01357     x = full_cloud.points[i].x;
01358     y = full_cloud.points[i].y;
01359     z = full_cloud.points[i].z;
01360     out << qSetFieldWidth(8) << x << " " << y << " " << z << " ";
01361     out << qSetFieldWidth(3) << r << " " << g << " " << b << "\n";
01362   }
01363 }
01364   
01365 
01366 void GraphManager::saveTrajectory(QString filebasename, bool with_ground_truth){
01367     struct timespec starttime, finish; double elapsed; clock_gettime(CLOCK_MONOTONIC, &starttime);
01368     if(graph_.size() == 0){
01369       ROS_ERROR("Graph is empty, no trajectory can be saved");
01370       return;
01371     }
01372     ROS_INFO("Logging Trajectory");
01373     QMutexLocker locker(&optimizer_mutex);
01374 
01375     //FIXME: DO this block only if with_ground_truth is true and !gt.empty()
01376     std::string gt = ParameterServer::instance()->get<std::string>("ground_truth_frame_name");
01377 
01378     ROS_INFO("Comparison of relative motion with ground truth");
01379     QString gtt_fname("_ground_truth.txt");
01380     QFile gtt_file(gtt_fname.prepend(filebasename));//file is closed on destruction
01381     if(!gtt_file.open(QIODevice::WriteOnly|QIODevice::Text)) return; //TODO: Errormessage
01382     QTextStream gtt_out(&gtt_file);
01383     tf::StampedTransform b2p = graph_[0]->getGroundTruthTransform();
01384     gtt_out.setRealNumberNotation(QTextStream::FixedNotation);
01385     gtt_out << "# TF Coordinate Frame ID: " << b2p.frame_id_.c_str() << "(data: " << b2p.child_frame_id_.c_str() << ")\n";
01386 
01387      
01388     QString et_fname("_estimate.txt");
01389     QFile et_file (et_fname.prepend(filebasename));//file is closed on destruction
01390     if(!et_file.open(QIODevice::WriteOnly|QIODevice::Text)) return; //TODO: Errormessage
01391     QTextStream et_out(&et_file);
01392     et_out.setRealNumberNotation(QTextStream::FixedNotation);
01393     b2p = graph_[0]->getBase2PointsTransform();
01394     et_out << "# TF Coordinate Frame ID: " << b2p.frame_id_.c_str() << "(data: " << b2p.child_frame_id_.c_str() << ")\n";
01395 
01396     for (unsigned int i = 0; i < optimizer_->vertices().size(); ++i) {
01397         g2o::VertexSE3* v = dynamic_cast<g2o::VertexSE3*>(optimizer_->vertex(i));
01398         ROS_ERROR_COND(!v, "Nullpointer in graph at position %i!", i);
01399 
01400         tf::Transform pose = g2o2TF(v->estimate());
01401 
01402         tf::StampedTransform base2points = graph_[i]->getBase2PointsTransform();//get pose of base w.r.t current pc at capture time
01403         tf::Transform world2base = init_base_pose_*base2points*pose*base2points.inverse();
01404 
01405         logTransform(et_out, world2base, graph_[i]->pc_col->header.stamp.toSec()); 
01406         //Eigen::Matrix<double, 6,6> uncertainty = v->uncertainty();
01407         //et_out << uncertainty(0,0) << "\t" << uncertainty(1,1) << "\t" << uncertainty(2,2) << "\t" << uncertainty(3,3) << "\t" << uncertainty(4,4) << "\t" << uncertainty(5,5) <<"\n" ;
01408         if(with_ground_truth && !gt.empty()){
01409           tf::StampedTransform gt_world2base = graph_[i  ]->getGroundTruthTransform();//get mocap pose of base in map
01410           if( gt_world2base.frame_id_   == "/missing_ground_truth" ){ 
01411             ROS_WARN_STREAM("Skipping ground truth: " << gt_world2base.child_frame_id_ << " child/parent " << gt_world2base.frame_id_);
01412             continue;
01413           }
01414           logTransform(gtt_out, gt_world2base, gt_world2base.stamp_.toSec()); 
01415           //logTransform(et_out, world2base, gt_world2base.stamp_.toSec()); 
01416         } 
01417     }
01418     ROS_INFO_COND(!gt.empty() && with_ground_truth, "Written logfiles ground_truth_trajectory.txt and estimated_trajectory.txt");
01419     ROS_INFO_COND(gt.empty(),  "Written logfile estimated_trajectory.txt");
01420     clock_gettime(CLOCK_MONOTONIC, &finish); elapsed = (finish.tv_sec - starttime.tv_sec); elapsed += (finish.tv_nsec - starttime.tv_nsec) / 1000000000.0; ROS_INFO_STREAM_COND_NAMED(elapsed > ParameterServer::instance()->get<double>("min_time_reported"), "timings", __FUNCTION__ << " runtime: "<< elapsed <<" s");
01421 }
01422 
01423 void GraphManager::broadcastTransform(Node* node, tf::Transform& computed_motion){
01424     std::string fixed_frame = ParameterServer::instance()->get<std::string>("fixed_frame_name");
01425     std::string base_frame  = ParameterServer::instance()->get<std::string>("base_frame_name");
01426     
01427     /*
01428     if(graph_.size() == 0){
01429       ROS_WARN("Cannot broadcast transform while graph is empty sending identity");
01430       br_.sendTransform(tf::StampedTransform(tf::Transform::getIdentity(), ros::Time::now(), fixed_frame, base_frame));
01431       return;
01432     }
01433     */
01434     const tf::StampedTransform& base2points = node->getBase2PointsTransform();//get pose of base w.r.t current pc at capture time
01435 
01436     //Assumption: computed_motion_ contains last pose
01437     tf::Transform world2base = init_base_pose_*base2points*computed_motion*base2points.inverse();
01438     printTransform("World->Base", world2base);
01439 
01440     ROS_DEBUG("Broadcasting transform");
01441     
01442     br_.sendTransform(tf::StampedTransform(world2base.inverse(), base2points.stamp_, base_frame, fixed_frame));
01443 }
01444 
01445 // If QT Concurrent is available, run the saving in a seperate thread
01446 void GraphManager::sendAllClouds(bool threaded){
01447     if (ParameterServer::instance()->get<bool>("concurrent_edge_construction") && threaded) {
01448         QFuture<void> f1 = QtConcurrent::run(this, &GraphManager::sendAllCloudsImpl);
01449         //f1.waitForFinished();
01450     }
01451     else {// Otherwise just call it without threading
01452         sendAllCloudsImpl();
01453     }
01454 }
01455 
01456 
01457 void GraphManager::sendAllCloudsImpl(){
01458     struct timespec starttime, finish; double elapsed; clock_gettime(CLOCK_MONOTONIC, &starttime);
01459     if (batch_cloud_pub_.getNumSubscribers() == 0){
01460         ROS_WARN("No Subscribers: Sending of clouds cancelled");
01461         return;
01462     }
01463 
01464     ROS_INFO("Sending out all clouds");
01465     batch_processing_runs_ = true;
01466     ros::Rate r(ParameterServer::instance()->get<double>("send_clouds_rate")); //slow down a bit, to allow for transmitting to and processing in other nodes
01467 
01468     for (unsigned int i = 0; i < optimizer_->vertices().size(); ++i) {
01469         g2o::VertexSE3* v = dynamic_cast<g2o::VertexSE3*>(optimizer_->vertex(i));
01470         if(!v){ 
01471             ROS_ERROR("Nullpointer in graph at position %i!", i);
01472             continue;
01473         }
01474 
01475         tf::StampedTransform base2points = graph_[i]->getBase2PointsTransform();//get pose of base w.r.t current pc at capture time
01476         printTransform("base2points", base2points);
01477         tf::Transform computed_motion = g2o2TF(v->estimate());//get pose of point cloud w.r.t. first frame's pc
01478         printTransform("computed_motion", computed_motion);
01479         printTransform("init_base_pose_", init_base_pose_);
01480 
01481         tf::Transform world2base = init_base_pose_*base2points*computed_motion*base2points.inverse();
01482         tf::Transform gt_world2base = graph_[i]->getGroundTruthTransform();//get mocap pose of base in map
01483         tf::Transform err = gt_world2base.inverseTimes(world2base);
01484         //TODO: Compute err from relative transformations betw. time steps
01485 
01486         ros::Time now = ros::Time::now(); //makes sure things have a corresponding timestamp
01487         ROS_DEBUG("Sending out transform %i", i);
01488         printTransform("World->Base", world2base);
01489         std::string fixed_frame = ParameterServer::instance()->get<std::string>("fixed_frame_name");
01490         br_.sendTransform(tf::StampedTransform(world2base, now, fixed_frame, base2points.frame_id_));
01491         br_.sendTransform(tf::StampedTransform(err, now, fixed_frame, "/where_mocap_should_be"));
01492         ROS_DEBUG("Sending out cloud %i", i);
01493         //graph_[i]->publish("/batch_transform", now, batch_cloud_pub_);
01494         graph_[i]->publish(now, batch_cloud_pub_);
01495         //tf::Transform ground_truth_tf = graph_[i]->getGroundTruthTransform();
01496         QString message;
01497         Q_EMIT setGUIInfo(message.sprintf("Sending pointcloud and map transform (%i/%i) on topics %s and /tf", (int)i+1, (int)optimizer_->vertices().size(), ParameterServer::instance()->get<std::string>("individual_cloud_out_topic").c_str()) );
01498         r.sleep();
01499     }
01500 
01501     batch_processing_runs_ = false;
01502     Q_EMIT sendFinished();
01503     clock_gettime(CLOCK_MONOTONIC, &finish); elapsed = (finish.tv_sec - starttime.tv_sec); elapsed += (finish.tv_nsec - starttime.tv_nsec) / 1000000000.0; ROS_INFO_STREAM_COND_NAMED(elapsed > ParameterServer::instance()->get<double>("min_time_reported"), "timings", __FUNCTION__ << " runtime: "<< elapsed <<" s");
01504 }
01505 
01506 void GraphManager::setMaxDepth(float max_depth){
01507         Max_Depth = max_depth;
01508         ROS_INFO("Max Depth set to: %f", max_depth);
01509 }
01510 
01511 void GraphManager::cloudRendered(pointcloud_type const * pc) {
01512   BOOST_REVERSE_FOREACH(GraphNodeType entry, graph_){
01513     if(entry.second->pc_col.get() == pc){
01514       entry.second->clearPointCloud();
01515       ROS_WARN("Cleared PointCloud after rendering to openGL list. It will not be available for save/send.");
01516       return;
01517     }
01518   }
01519 }
01520 
01521 
01522 void GraphManager::sanityCheck(float thresh){ 
01523   thresh *=thresh; //squaredNorm
01524   QMutexLocker locker(&optimizer_mutex);
01525   EdgeSet::iterator edge_iter = optimizer_->edges().begin();
01526   for(int i =0;edge_iter != optimizer_->edges().end(); edge_iter++, i++) {
01527       g2o::EdgeSE3* myedge = dynamic_cast<g2o::EdgeSE3*>(*edge_iter);
01528       Eigen::Vector3d ev = myedge->measurement().translation();
01529       if(ev.squaredNorm() > thresh){
01530         optimizer_->removeEdge(myedge); 
01531       }
01532   }
01533 }
01534 void GraphManager::pruneEdgesWithErrorAbove(float thresh){
01535   QMutexLocker locker(&optimizer_mutex);
01536   EdgeSet::iterator edge_iter = optimizer_->edges().begin();
01537   for(int i =0;edge_iter != optimizer_->edges().end(); edge_iter++, i++) {
01538       g2o::EdgeSE3* myedge = dynamic_cast<g2o::EdgeSE3*>(*edge_iter);
01539       g2o::EdgeSE3::ErrorVector ev = myedge->error();
01540       if(ev.squaredNorm() > thresh){
01541         optimizer_->removeEdge(myedge); 
01542       }
01543   }
01544 }
01545 void GraphManager::printEdgeErrors(QString filename){
01546   QMutexLocker locker(&optimizer_mutex);
01547   std::fstream filestr;
01548   filestr.open (qPrintable(filename),  std::fstream::out );
01549 
01550   EdgeSet::iterator edge_iter = optimizer_->edges().begin();
01551   for(int i =0;edge_iter != optimizer_->edges().end(); edge_iter++, i++) {
01552       g2o::EdgeSE3* myedge = dynamic_cast<g2o::EdgeSE3*>(*edge_iter);
01553       g2o::EdgeSE3::ErrorVector ev = myedge->error();
01554       ROS_INFO_STREAM("Error Norm for edge " << i << ": " << ev.squaredNorm());
01555       filestr << "Error for edge " << i << ": " << ev.squaredNorm() << std::endl;
01556   }
01557   filestr.close();
01558 }
01559 
01560 void GraphManager::finishUp(){
01561   someone_is_waiting_for_me_ = true;
01562 }
01563 bool GraphManager::isBusy(){
01564   return (batch_processing_runs_ || process_node_runs_ );
01565 }
01566 
01567 double GraphManager::geodesicDiscount(g2o::HyperDijkstra& hypdij, const MatchingResult& mr){
01568     //Discount by geodesic distance to root node
01569     const g2o::HyperDijkstra::AdjacencyMap am = hypdij.adjacencyMap();
01570     g2o::VertexSE3* older_vertex = dynamic_cast<g2o::VertexSE3*>(optimizer_->vertex(mr.edge.id1));
01571     double discount_factor = am.at(older_vertex).distance();
01572     discount_factor = discount_factor > 0.0? 1.0/discount_factor : 1.0;//avoid inf
01573     ROS_INFO("Discount weight for connection to Node %i = %f", mr.edge.id1, discount_factor);
01574     return discount_factor;
01575 }
01576 
01577 void GraphManager::toggleMapping(bool mappingOn){
01578   QMutexLocker locker(&optimizer_mutex);
01579   ROS_INFO_COND(mappingOn, "Switching mapping back on");
01580   ROS_INFO_COND(!mappingOn, "Switching mapping off: Localization continues");
01581   localization_only_ = !mappingOn;
01582   BOOST_FOREACH(VertexIDPair pair, optimizer_->vertices()) {
01583       g2o::VertexSE3* v = dynamic_cast<g2o::VertexSE3* >(pair.second);
01584       v->setFixed(localization_only_);
01585   }
01586 }
01587 
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Properties Friends Defines


rgbdslam
Author(s): Felix Endres, Juergen Hess, Nikolas Engelhard
autogenerated on Wed Dec 26 2012 15:53:08