graph_mgr_io.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 
00020 #include <sys/time.h>
00021 #include "scoped_timer.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 "pcl/io/ply_io.h"
00027 //#include <sensor_msgs/PointCloud2.h>
00028 #include <opencv2/features2d/features2d.hpp>
00029 #include <qtconcurrentrun.h>
00030 #include <QFile>
00031 #include <utility>
00032 #include <fstream>
00033 #include <boost/foreach.hpp>
00034 #include <rosbag/bag.h>
00035 
00036 #include "g2o/types/slam3d/se3quat.h"
00037 //#include "g2o/types/slam3d/edge_se3_quat.h"
00038 #include "g2o/types/slam3d/edge_se3.h"
00039 #include "g2o/core/optimizable_graph.h"
00040 
00041 #include <pcl_ros/point_cloud.h>
00042 #include <pcl_ros/transforms.h>
00043 
00044 // If QT Concurrent is available, run the saving in a seperate thread
00045 void GraphManager::sendAllClouds(bool threaded){
00046     if (ParameterServer::instance()->get<bool>("concurrent_io") && threaded) {
00047         QFuture<void> f1 = QtConcurrent::run(this, &GraphManager::sendAllCloudsImpl);
00048         //f1.waitForFinished();
00049     }
00050     else {// Otherwise just call it without threading
00051         sendAllCloudsImpl();
00052     }
00053 }
00054 
00055 tf::StampedTransform GraphManager::computeFixedToBaseTransform(Node* node, bool invert)
00056 {
00057     g2o::VertexSE3* v = dynamic_cast<g2o::VertexSE3*>(optimizer_->vertex(node->vertex_id_));
00058     if(!v){ 
00059       ROS_FATAL("Nullpointer in graph at position %i!", node->vertex_id_);
00060       throw std::exception();
00061     }
00062 
00063     tf::Transform computed_motion = eigenTransf2TF(v->estimate());//get pose of point cloud w.r.t. first frame's pc
00064     tf::Transform base2points = node->getBase2PointsTransform();//get pose of base w.r.t current pc at capture time
00065     printTransform("base2points", base2points);
00066     printTransform("computed_motion", computed_motion);
00067     printTransform("init_base_pose_", init_base_pose_);
00068 
00069     tf::Transform world2base = init_base_pose_*base2points*computed_motion*base2points.inverse();
00070     tf::Transform gt_world2base = node->getGroundTruthTransform();//get mocap pose of base in map
00071     tf::Transform err = gt_world2base.inverseTimes(world2base);
00072 
00073     //makes sure things have a corresponding timestamp
00074     //also avoids problems with tflistener cache size if mapping took long. Must be synchronized with tf broadcasting
00075     ros::Time now = ros::Time::now(); 
00076 
00077     printTransform("World->Base", world2base);
00078     std::string fixed_frame = ParameterServer::instance()->get<std::string>("fixed_frame_name");
00079     std::string base_frame  = ParameterServer::instance()->get<std::string>("base_frame_name");
00080     if(base_frame.empty())
00081     { //if there is no base frame defined, use frame of sensor data
00082       base_frame = graph_.begin()->second->header_.frame_id;
00083     }
00084     if(invert){
00085       return tf::StampedTransform(world2base.inverse(), now, base_frame, fixed_frame);
00086     } else {
00087       return tf::StampedTransform(world2base, now, fixed_frame, base_frame);
00088     }
00089 }
00090 
00091 // If QT Concurrent is available, run the saving in a seperate thread
00092 void GraphManager::saveBagfileAsync(QString filename){
00093     if (ParameterServer::instance()->get<bool>("concurrent_io")) {
00094         QFuture<void> f1 = QtConcurrent::run(this, &GraphManager::saveBagfile, filename);
00095         //f1.waitForFinished();
00096     }
00097     else {// Otherwise just call it without threading
00098         saveBagfile(filename);
00099     }
00100 }
00101 void GraphManager::saveBagfile(QString filename)
00102 {
00103   ScopedTimer s(__FUNCTION__);
00104   if(filename.isEmpty()){
00105     ROS_ERROR("Filename empty. Cannot save to bagfile.");
00106     return;
00107   }
00108 
00109   Q_EMIT iamBusy(0, "Saving Bagfile", graph_.size());
00110   rosbag::Bag bag;
00111   bag.open(qPrintable(filename), rosbag::bagmode::Write);
00112   if(ParameterServer::instance()->get<bool>("compress_output_bagfile"))
00113   {
00114     bag.setCompression(rosbag::compression::BZ2); 
00115   }
00116   geometry_msgs::TransformStamped geom_msg;
00117 
00118   for (graph_it it = graph_.begin(); it != graph_.end(); ++it)
00119   {
00120     Node* node = it->second;
00121     if(!node->valid_tf_estimate_) {
00122       ROS_INFO("Skipping node %i: No valid estimate", node->id_);
00123       continue;
00124     }
00125 
00126     tf::tfMessage tfmsg;
00127 
00128     tf::StampedTransform base_to_fixed = this->computeFixedToBaseTransform(node, true);
00129     base_to_fixed.stamp_ = node->header_.stamp;
00130     tf::transformStampedTFToMsg(base_to_fixed, geom_msg);
00131     tfmsg.transforms.push_back(geom_msg);
00132 
00133     tf::StampedTransform base_to_points = node->getBase2PointsTransform();
00134     base_to_points.stamp_ = node->header_.stamp;
00135     tf::transformStampedTFToMsg(base_to_points, geom_msg);
00136     tfmsg.transforms.push_back(geom_msg);
00137 
00138     //tfmsg.set_transforms_size(2);
00139 
00140     bag.write("/tf", node->header_.stamp, tfmsg);
00141     bag.write("/rgbdslam/batch_clouds", node->header_.stamp, node->pc_col);
00142 
00143     Q_EMIT progress(0, "Saving Bagfile", it->first+1);
00144     QString message;
00145     Q_EMIT setGUIInfo(message.sprintf("Writing pointcloud and map transform (%i/%i) to bagfile %s", it->first, (int)graph_.size(), qPrintable(filename)));
00146   }
00147   Q_EMIT progress(0, "Finished Saving Bagfile", 1e6);
00148   bag.close();
00149 }
00150 
00151 
00152 void GraphManager::sendAllCloudsImpl()
00153 {
00154   ScopedTimer s(__FUNCTION__);
00155 
00156   if (batch_cloud_pub_.getNumSubscribers() == 0){
00157     ROS_WARN("No Subscribers: Sending of clouds cancelled");
00158     return;
00159   }
00160 
00161   ROS_INFO("Sending out all clouds");
00162   batch_processing_runs_ = true;
00163   ros::Rate r(ParameterServer::instance()->get<double>("send_clouds_rate")); //slow down a bit, to allow for transmitting to and processing in other nodes
00164   double delay_seconds = ParameterServer::instance()->get<double>("send_clouds_delay");
00165 
00166   for (graph_it it = graph_.begin(); it != graph_.end(); ++it)
00167   {
00168 
00169     Node* node = it->second;
00170     if(!node->valid_tf_estimate_) {
00171       ROS_INFO("Skipping node %i: No valid estimate", node->id_);
00172       continue;
00173     }
00174     while(node->header_.stamp.toSec() > (ros::Time::now().toSec() - delay_seconds)){
00175       ROS_INFO("Waiting for node becoming %f seconds old", delay_seconds);
00176       r.sleep();
00177     }
00178     tf::StampedTransform base_to_fixed = this->computeFixedToBaseTransform(node, true);
00179     br_.sendTransform(base_to_fixed);
00180     publishCloud(node, base_to_fixed.stamp_, batch_cloud_pub_);
00181 
00182     QString message;
00183     Q_EMIT setGUIInfo(message.sprintf("Sending pointcloud and map transform (%i/%i) on topics %s and /tf", it->first, (int)graph_.size(), ParameterServer::instance()->get<std::string>("individual_cloud_out_topic").c_str()) );
00184     r.sleep();
00185   }
00186 
00187   batch_processing_runs_ = false;
00188   Q_EMIT sendFinished();
00189 }
00190 
00191 // If QT Concurrent is available, run the saving in a seperate thread
00192 void GraphManager::saveAllClouds(QString filename, bool threaded){
00193     if (ParameterServer::instance()->get<bool>("concurrent_io") && threaded) {
00194         QFuture<void> f1 = QtConcurrent::run(this, &GraphManager::saveAllCloudsToFile, filename);
00195         //f1.waitForFinished();
00196     }
00197     else {// Otherwise just call it without threading
00198         saveAllCloudsToFile(filename);
00199     }
00200 }
00201 
00202 void GraphManager::saveIndividualClouds(QString filename, bool threaded){
00203   if (ParameterServer::instance()->get<bool>("concurrent_io") && threaded) {
00204     QFuture<void> f1 = QtConcurrent::run(this, &GraphManager::saveIndividualCloudsToFile, filename);
00205     //f1.waitForFinished();
00206   }
00207   else {
00208     saveIndividualCloudsToFile(filename);
00209   }
00210 }
00211 
00212 
00215 bool GraphManager::updateCloudOrigin(Node* node)
00216 {
00217     if(!node->valid_tf_estimate_) {
00218       ROS_INFO("Skipping node %i: No valid estimate", node->id_);
00219       return false;
00220     }
00221     g2o::VertexSE3* v = dynamic_cast<g2o::VertexSE3*>(optimizer_->vertex(node->vertex_id_));
00222     if(!v){
00223       ROS_ERROR("Nullpointer in graph at position %i!", node->id_);
00224       return false;
00225     }
00226     if(node->pc_col->size() == 0){
00227       ROS_INFO("Skipping Node %i, point cloud data is empty!", node->id_);
00228       return false;
00229     }
00230     // Update the sensor pose stored in the point clouds
00231     node->pc_col->sensor_origin_.head<3>() = v->estimate().translation().cast<float>();
00232     node->pc_col->sensor_orientation_ =  v->estimate().rotation().cast<float>();
00233     //node->header_.frame_id = ParameterServer::instance()->get<std::string>("fixed_frame_name");
00234 }
00235 
00236 void GraphManager::saveOctomap(QString filename, bool threaded){
00237   if (ParameterServer::instance()->get<bool>("octomap_online_creation")) {
00238     this->writeOctomap(filename);
00239   } 
00240   else{ //if not online creation, create now
00241     if (ParameterServer::instance()->get<bool>("concurrent_io") && threaded) {
00242       //saveOctomapImpl(filename);
00243       QFuture<void> f1 = QtConcurrent::run(this, &GraphManager::saveOctomapImpl, filename);
00244       //f1.waitForFinished();
00245     }
00246     else {
00247       saveOctomapImpl(filename);
00248     }
00249   }
00250 }
00251 
00252 void GraphManager::saveOctomapImpl(QString filename)
00253 {
00254   ScopedTimer s(__FUNCTION__);
00255 
00256   batch_processing_runs_ = true;
00257   Q_EMIT iamBusy(1, "Saving Octomap", 0);
00258   std::list<Node*> nodes_for_octomapping;
00259   unsigned int points_to_render = 0;
00260   { //Get the transformations from the optimizer and store them in the node's point cloud header
00261     QMutexLocker locker(&optimizer_mutex_);
00262     for (graph_it it = graph_.begin(); it != graph_.end(); ++it){
00263       Node* node = it->second;
00264       if(this->updateCloudOrigin(node)){
00265         nodes_for_octomapping.push_back(node);
00266         points_to_render += node->pc_col->size();
00267       }
00268     }
00269   } 
00270   // Now (this takes long) render the clouds into the octomap
00271   int counter = 0;
00272   co_server_.reset();
00273   Q_EMIT iamBusy(1, "Saving Octomap", nodes_for_octomapping.size());
00274   unsigned int rendered_points = 0;
00275   double delay_seconds = ParameterServer::instance()->get<double>("save_octomap_delay");
00276   BOOST_FOREACH(Node* node, nodes_for_octomapping)
00277   {
00278     /*
00279       double data_stamp = node->pc_col->header.stamp.toSec();
00280       while(data_stamp > ros::Time::now().toSec() - delay_seconds){
00281         ROS_INFO("Waiting for node becoming %f seconds old before rendering to octomap", delay_seconds);
00282         ros::Duration((ros::Time::now().toSec() - delay_seconds) - data_stamp).sleep();
00283       }
00284       */
00285       QString message;
00286       Q_EMIT setGUIStatus(message.sprintf("Inserting Node %i/%i into octomap", ++counter, (int)nodes_for_octomapping.size()));
00287       this->renderToOctomap(node);
00288       rendered_points += node->pc_col->size();
00289       ROS_INFO("Rendered %u points of %u", rendered_points, points_to_render);
00290       Q_EMIT progress(1, "Saving Octomap", counter);
00291       if(counter % ParameterServer::instance()->get<int>("octomap_autosave_step") == 0){
00292         Q_EMIT setGUIStatus(QString("Autosaving preliminary octomap to ") + filename);
00293         this->writeOctomap(filename);
00294       }
00295   }
00296 
00297 
00298   Q_EMIT setGUIStatus(QString("Saving final octomap to ") + filename);
00299   co_server_.save(qPrintable(filename));
00300   Q_EMIT progress(1, "Finished Saving Octomap", 1e6);
00301   ROS_INFO ("Saved Octomap to %s", qPrintable(filename));
00302   if(ParameterServer::instance()->get<bool>("octomap_clear_after_save")){
00303     co_server_.reset();
00304     ROS_INFO ("Reset Octomap to free memory");
00305   }
00306 
00307   Q_EMIT renderableOctomap(&co_server_);
00308   batch_processing_runs_ = false;
00309 }
00310 
00311 void GraphManager::writeOctomap(QString filename) const
00312 {
00313     ScopedTimer s(__FUNCTION__);
00314     co_server_.save(qPrintable(filename));
00315 }
00316 
00317 void GraphManager::renderToOctomap(Node* node)
00318 {
00319     ScopedTimer s(__FUNCTION__);
00320     ROS_INFO("Rendering Node %i with frame %s", node->id_, node->header_.frame_id.c_str());
00321     if(updateCloudOrigin(node)){//Only render if transformation estimate is valid
00322     co_server_.insertCloudCallback(node->pc_col, ParameterServer::instance()->get<double>("maximum_depth")); // Will be transformed according to sensor pose set previously
00323     }
00324     if(ParameterServer::instance()->get<bool>("octomap_clear_raycasted_clouds")){
00325       node->clearPointCloud();
00326       ROS_INFO("Cleared pointcloud of Node %i", node->id_);
00327     }
00328 }
00329 void GraphManager::saveIndividualCloudsToFile(QString file_basename)
00330 {
00331   ScopedTimer s(__FUNCTION__);
00332 
00333   ROS_INFO("Saving all clouds to %s_xxxx.pcd", qPrintable(file_basename));
00334   std::string gt = ParameterServer::instance()->get<std::string>("ground_truth_frame_name");
00335   ROS_INFO_COND(!gt.empty(), "Saving all clouds with ground truth sensor position to gt_%sxxxx.pcd", qPrintable(file_basename));
00336 
00337   batch_processing_runs_ = true;
00338   tf::Transform  world2base;
00339   QString message, filename;
00340   std::string fixed_frame_id = ParameterServer::instance()->get<std::string>("fixed_frame_name");
00341   for (graph_it it = graph_.begin(); it != graph_.end(); ++it){
00342 
00343     Node* node = it->second;
00344     if(!node->valid_tf_estimate_) {
00345       ROS_INFO("Skipping node %i: No valid estimate", node->id_);
00346       continue;
00347     }
00348 
00349     g2o::VertexSE3* v = dynamic_cast<g2o::VertexSE3*>(optimizer_->vertex(node->vertex_id_));
00350     if(!v){ 
00351       ROS_ERROR("Nullpointer in graph at position %i!", it->first);
00352       continue;
00353     }
00354     if(node->pc_col->size() == 0){
00355       ROS_INFO("Skipping Node %i, point cloud data is empty!", it->first);
00356       continue;
00357     }
00358     /*/TODO: is all this correct?
00359       tf::Transform transform = eigenTransf2TF(v->estimate());
00360       tf::Transform cam2rgb;
00361       cam2rgb.setRotation(tf::createQuaternionFromRPY(-1.57,0,-1.57));
00362       cam2rgb.setOrigin(tf::Point(0,-0.04,0));
00363       world2base = cam2rgb*transform;
00364       */
00365     //for cpu_tsdf
00366     Eigen::Matrix3f export_rot;
00367     Eigen::Vector3f export_origin;
00368     QString transform_filename;
00369     if(ParameterServer::instance()->get<bool>("transform_individual_clouds")){
00370 
00371       Eigen::Matrix4f m = v->estimate().matrix().cast<float>();
00372       pcl::transformPointCloud(*(node->pc_col), *(node->pc_col), m);
00373       Eigen::Vector4f sensor_origin(0,0,0,0);
00374       Eigen::Quaternionf sensor_orientation(0,0,0,1);
00375 
00376       export_rot= sensor_orientation.toRotationMatrix();
00377       export_origin= sensor_origin.head<3>();
00378       node->pc_col->sensor_origin_ = sensor_origin;
00379       node->pc_col->sensor_orientation_ = sensor_orientation;
00380       node->header_.frame_id = fixed_frame_id;
00381     }
00382     else {
00383       tf::Transform pose = eigenTransf2TF(v->estimate());
00384       tf::StampedTransform base2points =  node->getBase2PointsTransform();//get pose of base w.r.t current pc at capture time
00385       world2base = this->computeFixedToBaseTransform(node, false);
00386       tf::Transform world2points = world2base*base2points;
00387 
00388       Eigen::Vector4f sensor_origin(world2points.getOrigin().x(),world2points.getOrigin().y(),world2points.getOrigin().z(),world2points.getOrigin().w());
00389       Eigen::Quaternionf sensor_orientation(world2points.getRotation().w(),world2points.getRotation().x(),world2points.getRotation().y(),world2points.getRotation().z());
00390 
00391       export_rot= sensor_orientation.toRotationMatrix();
00392       export_origin= sensor_origin.head<3>();
00393       node->pc_col->sensor_origin_ = sensor_origin;
00394       node->pc_col->sensor_orientation_ = sensor_orientation;
00395       node->header_.frame_id = fixed_frame_id;
00396     }
00397 
00398     filename.sprintf("%s_%04d.pcd", qPrintable(file_basename), it->first);
00399     transform_filename.sprintf("%s_%04d.txt", qPrintable(file_basename), it->first);
00400     ROS_INFO("Saving %s", qPrintable(filename));
00401     Q_EMIT setGUIStatus(message.sprintf("Saving to %s: Transformed Node %i/%i", qPrintable(filename), it->first, (int)camera_vertices.size()));
00402     pcl::io::savePCDFile(qPrintable(filename), *(node->pc_col), true); //Last arg: true is binary mode. ASCII mode drops color bits
00403 
00404     std::ofstream trans_file;
00405     trans_file.open(qPrintable(transform_filename), std::ios::out);
00406     for (int i = 0; i < 3; ++i) {
00407         trans_file << export_rot(i,0) << " " << export_rot(i,1) << " " << export_rot(i,2) << " " << export_origin(i) << " ";
00408         }
00409     trans_file << 0 << " " << 0 << " " << 0 << " " << 1 << std::endl;
00410     if(!gt.empty()){
00411       tf::StampedTransform gt_world2base = node->getGroundTruthTransform();//get mocap pose of base in map
00412       if( gt_world2base.frame_id_   == "/missing_ground_truth" ){ 
00413         ROS_WARN_STREAM("Skipping ground truth: " << gt_world2base.child_frame_id_ << " child/parent " << gt_world2base.frame_id_);
00414         continue;
00415       }
00416       Eigen::Vector4f sensor_origin(gt_world2base.getOrigin().x(),gt_world2base.getOrigin().y(),gt_world2base.getOrigin().z(),gt_world2base.getOrigin().w());
00417       Eigen::Quaternionf sensor_orientation(gt_world2base.getRotation().w(),gt_world2base.getRotation().x(),gt_world2base.getRotation().y(),gt_world2base.getRotation().z());
00418 
00419       node->pc_col->sensor_origin_ = sensor_origin;
00420       node->pc_col->sensor_orientation_ = sensor_orientation;
00421       node->header_.frame_id = fixed_frame_id;
00422 
00423       filename.sprintf("%s_%04d_gt.pcd", qPrintable(file_basename), it->first);
00424       Q_EMIT setGUIStatus(message.sprintf("Saving to %s: Transformed Node %i/%i", qPrintable(filename), it->first, (int)camera_vertices.size()));
00425       pcl::io::savePCDFile(qPrintable(filename), *(node->pc_col), true); //Last arg: true is binary mode. ASCII mode drops color bits
00426     }
00427 
00428   }
00429   Q_EMIT setGUIStatus("Saved all point clouds");
00430   ROS_INFO ("Saved all points clouds to %sxxxx.pcd", qPrintable(file_basename));
00431   batch_processing_runs_ = false;
00432 }
00433 
00434 void GraphManager::saveAllFeatures(QString filename, bool threaded)
00435 {
00436     if (ParameterServer::instance()->get<bool>("concurrent_io") && threaded) {
00437         QFuture<void> f1 = QtConcurrent::run(this, &GraphManager::saveAllFeaturesToFile, filename);
00438         //f1.waitForFinished();
00439     }
00440     else {// Otherwise just call it without threading
00441         saveAllFeaturesToFile(filename);
00442     }
00443 }
00444 void GraphManager::saveAllFeaturesToFile(QString filename)
00445 {
00446     ScopedTimer s(__FUNCTION__);
00447 
00448     cv::FileStorage fs(qPrintable(filename), cv::FileStorage::WRITE);
00449     fs << "Feature_Locations" << "[";
00450 
00451     ROS_INFO("Saving all features to %s transformed to a common coordinate frame.", qPrintable(filename));
00452     batch_processing_runs_ = true;
00453     tf::Transform  world2rgb, cam2rgb;
00454     QString message;
00455     cam2rgb.setRotation(tf::createQuaternionFromRPY(-1.57,0,-1.57));
00456     cam2rgb.setOrigin(tf::Point(0,-0.04,0));
00457     int feat_count = 0;
00458     for (graph_it it = graph_.begin(); it != graph_.end(); ++it){
00459 
00460       Node* node = it->second;
00461       if(!node->valid_tf_estimate_) {
00462         ROS_INFO("Skipping node %i: No valid estimate", node->id_);
00463         continue;
00464       }
00465 
00466       g2o::VertexSE3* v = dynamic_cast<g2o::VertexSE3*>(optimizer_->vertex(node->vertex_id_));
00467             tf::Transform world2cam = eigenTransf2TF(v->estimate());
00468             world2rgb = cam2rgb*world2cam;
00469             Eigen::Matrix4f world2rgbMat;
00470             pcl_ros::transformAsMatrix(world2rgb, world2rgbMat);
00471       BOOST_FOREACH(Eigen::Vector4f loc, node->feature_locations_3d_)
00472             {
00473               loc.w() = 1.0;
00474               Eigen::Vector4f new_loc = world2rgbMat * loc;
00475               fs << "{:" << "x" << new_loc.x() << "y" << new_loc.y() << "z" << new_loc.z() << "}";
00476               feat_count++;
00477             }
00478       Q_EMIT setGUIStatus(message.sprintf("Saving to %s: Features of Node %i/%i", qPrintable(filename), it->first, (int)camera_vertices.size()));
00479     }
00480     fs << "]";
00481     //Assemble all descriptors into a big matrix
00482  assert(graph_.size()>0);
00483     int descriptor_size = graph_[0]->feature_descriptors_.cols;
00484     int descriptor_type = graph_[0]->feature_descriptors_.type();
00485     cv::Mat alldescriptors(0, descriptor_size,  descriptor_type);
00486     alldescriptors.reserve(feat_count);
00487     for (unsigned int i = 0; i < graph_.size(); ++i) {
00488       alldescriptors.push_back(graph_[i]->feature_descriptors_);
00489     }
00490     fs << "Feature_Descriptors" << alldescriptors;
00491     fs.release();
00492 
00493     Q_EMIT setGUIStatus(message.sprintf("Saved %d features points to %s", feat_count, qPrintable(filename)));
00494     ROS_INFO ("Saved %d feature points to %s", feat_count, qPrintable(filename));
00495     batch_processing_runs_ = false;
00496 }
00497 
00498 
00499 
00500 
00501 void GraphManager::saveAllCloudsToFile(QString filename){
00502     ScopedTimer s(__FUNCTION__);
00503     if(graph_.size() < 1){
00504       ROS_WARN("Cannot save empty graph. Aborted");
00505       return;
00506     }
00507     bool export_normals =false;//FIXME: Not implemented (see below)
00508     pcl::PointCloud<point_type> aggregate_cloud; 
00509     pcl::PointCloud<pcl::PointXYZRGBNormal> aggregate_normal_cloud; 
00510     //Make big enough to hold all other clouds. This might be too much if NaNs are filtered. They are filtered according to the parameter preserve_raster_on_save
00511     aggregate_cloud.reserve(graph_.size() * graph_.begin()->second->pc_col->size()); 
00512     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));
00513     batch_processing_runs_ = true;
00514 
00515     std::string base_frame  = ParameterServer::instance()->get<std::string>("base_frame_name");
00516     if(base_frame.empty()){ //if there is no base frame defined, use frame of sensor data
00517       base_frame = graph_.begin()->second->header_.frame_id;
00518     }
00519 
00520     tf::Transform  world2cam;
00521     //fill message
00522     //rgbdslam::CloudTransforms msg;
00523     QString message;
00525     tf::Transform cam2rgb;
00526     cam2rgb.setRotation(tf::createQuaternionFromRPY(-1.57,0,-1.57));
00527     cam2rgb.setOrigin(tf::Point(0,-0.04,0));
00528     for (graph_it it = graph_.begin(); it != graph_.end(); ++it){
00529       Node* node = it->second;
00530       if(!node->valid_tf_estimate_) {
00531         ROS_INFO("Skipping node %i: No valid estimate", node->id_);
00532         continue;
00533       }
00534       g2o::VertexSE3* v = dynamic_cast<g2o::VertexSE3*>(optimizer_->vertex(node->vertex_id_));
00535       if(!v){ 
00536         ROS_ERROR("Nullpointer in graph at position %i when saving clouds!", it->first);
00537         continue;
00538       }
00539       tf::Transform transform = eigenTransf2TF(v->estimate());
00540       world2cam = cam2rgb*transform;
00541       if (!export_normals){
00542         transformAndAppendPointCloud (*(node->pc_col), aggregate_cloud, world2cam, ParameterServer::instance()->get<double>("maximum_depth"), node->id_);
00543       } else { 
00544         ROS_ERROR("Exporting normals is not implemented");
00545         //transformAndAppendPointCloud (*(node->pc_col), aggregate_normal_cloud, world2cam, ParameterServer::instance()->get<double>("maximum_depth"));
00546       }
00547       if(ParameterServer::instance()->get<bool>("batch_processing")){
00548         node->clearPointCloud(); //saving all is the last thing to do, so these are not required anymore
00549       }
00550       Q_EMIT setGUIStatus(message.sprintf("Saving to %s: Transformed Node %i/%i", qPrintable(filename), it->first, (int)camera_vertices.size()));
00551     }
00552     aggregate_cloud.header.frame_id = base_frame;
00553     aggregate_normal_cloud.header.frame_id = base_frame;
00554     if (filename.endsWith(".ply", Qt::CaseInsensitive) ){
00555                 ROS_INFO("ply saving");
00556           //savePlyFile(qPrintable(filename), aggregate_normal_cloud); //Last arg is binary mode
00557           pcl::io::savePLYFileBinary(qPrintable(filename), aggregate_cloud);
00558     } else {
00559       ROS_INFO("pcd saving");
00560       if(!filename.endsWith(".pcd", Qt::CaseInsensitive)){
00561         filename.append(".pcd");
00562       }
00563       pcl::io::savePCDFile(qPrintable(filename), aggregate_cloud, true); //Last arg is binary mode
00564     }
00565     
00566     Q_EMIT setGUIStatus(message.sprintf("Saved %d data points to %s", (int)aggregate_cloud.points.size(), qPrintable(filename)));
00567     ROS_INFO ("Saved %d data points to %s", (int)aggregate_cloud.points.size(), qPrintable(filename));
00568 
00569     if (whole_cloud_pub_.getNumSubscribers() > 0){ //if it should also be send out
00570       /*
00571       sensor_msgs::PointCloud2 cloudMessage_; //this will be send out in batch mode
00572       pcl::toROSMsg(aggregate_cloud,cloudMessage_);
00573       cloudMessage_.header.frame_id = base_frame;
00574       cloudMessage_.header.stamp = ros::Time::now();
00575       whole_cloud_pub_.publish(cloudMessage_);
00576       */
00577       whole_cloud_pub_.publish(aggregate_cloud.makeShared());
00578       ROS_INFO("Aggregate pointcloud sent");
00579     }
00580     batch_processing_runs_ = false;
00581 }
00582 
00583 void GraphManager::pointCloud2MeshFile(QString filename, pointcloud_type full_cloud)
00584 {
00585   QFile file(filename);//file is closed on destruction
00586   if(!file.open(QIODevice::WriteOnly|QIODevice::Text)){
00587     ROS_ERROR("Could not open file %s", qPrintable(filename));
00588     return; 
00589   }
00590   QTextStream out(&file);
00591         out << "ply\n";
00592         out << "format ascii 1.0\n";
00593         out << "element vertex " << (int)full_cloud.points.size() << "\n"; 
00594         out << "property float x\n";
00595         out << "property float y\n";
00596         out << "property float z\n";
00597         out << "property uchar red\n";
00598         out << "property uchar green\n";
00599         out << "property uchar blue\n";
00600         out << "end_header\n";
00601   unsigned char r,g,b;
00602   float x, y, z ;
00603   for(unsigned int i = 0; i < full_cloud.points.size() ; i++){
00604     getColor(full_cloud.points[i], r,g,b);
00605     x = full_cloud.points[i].x;
00606     y = full_cloud.points[i].y;
00607     z = full_cloud.points[i].z;
00608     out << qSetFieldWidth(8) << x << " " << y << " " << z << " ";
00609     out << qSetFieldWidth(3) << r << " " << g << " " << b << "\n";
00610   }
00611 }
00612   
00613 
00614 void GraphManager::saveTrajectory(QString filebasename, bool with_ground_truth)
00615 {
00616     ScopedTimer s(__FUNCTION__);
00617 
00618     if(graph_.size() == 0){
00619       ROS_ERROR("Graph is empty, no trajectory can be saved");
00620       return;
00621     }
00622     QMutexLocker locker(&optimizer_mutex_);
00623 
00624     //FIXME: DO this block only if with_ground_truth is true and !gt.empty()
00625     std::string gt = ParameterServer::instance()->get<std::string>("ground_truth_frame_name");
00626 
00627     ROS_INFO("Comparison of relative motion with ground truth");
00628     QString gtt_fname("_ground_truth.txt");
00629     QFile gtt_file(gtt_fname.prepend(filebasename));//file is closed on destruction
00630     if(!gtt_file.open(QIODevice::WriteOnly|QIODevice::Text)) return; //TODO: Errormessage
00631     QTextStream gtt_out(&gtt_file);
00632     tf::StampedTransform b2p = graph_[0]->getGroundTruthTransform();
00633     gtt_out.setRealNumberNotation(QTextStream::FixedNotation);
00634     gtt_out << "# TF Coordinate Frame ID: " << b2p.frame_id_.c_str() << "(data: " << b2p.child_frame_id_.c_str() << ")\n";
00635 
00636      
00637     QString et_fname("_estimate.txt");
00638     QFile et_file (et_fname.prepend(filebasename));//file is closed on destruction
00639     if(!et_file.open(QIODevice::WriteOnly|QIODevice::Text)) return; //TODO: Errormessage
00640     ROS_INFO("Logging Trajectory to %s", qPrintable(et_fname));
00641     QTextStream et_out(&et_file);
00642     et_out.setRealNumberNotation(QTextStream::FixedNotation);
00643     b2p = graph_[0]->getBase2PointsTransform();
00644     et_out << "# TF Coordinate Frame ID: " << b2p.frame_id_.c_str() << "(data: " << b2p.child_frame_id_.c_str() << ")\n";
00645 
00646     for (graph_it it = graph_.begin(); it != graph_.end(); ++it){
00647       Node* node = it->second;
00648       if(!node->valid_tf_estimate_) {
00649         ROS_INFO("Skipping node %i: No valid estimate", node->id_);
00650         continue;
00651       }
00652       g2o::VertexSE3* v = dynamic_cast<g2o::VertexSE3*>(optimizer_->vertex(node->vertex_id_));
00653 
00654       ROS_ERROR_COND(!v, "Nullpointer in graph at position %i when saving trajectory!", it->first);
00655 
00656       tf::Transform pose = eigenTransf2TF(v->estimate());
00657 
00658       tf::StampedTransform base2points = node->getBase2PointsTransform();//get pose of base w.r.t current pc at capture time
00659       tf::Transform world2base = init_base_pose_*base2points*pose*base2points.inverse();
00660 
00661       logTransform(et_out, world2base, node->header_.stamp.toSec());
00662       //Eigen::Matrix<double, 6,6> uncertainty = v->uncertainty();
00663       //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" ;
00664       if(with_ground_truth && !gt.empty()){
00665         tf::StampedTransform gt_world2base = node->getGroundTruthTransform();//get mocap pose of base in map
00666         if( gt_world2base.frame_id_   == "/missing_ground_truth" ){ 
00667           ROS_WARN_STREAM("Skipping ground truth: " << gt_world2base.child_frame_id_ << " child/parent " << gt_world2base.frame_id_);
00668           continue;
00669         }
00670         logTransform(gtt_out, gt_world2base, gt_world2base.stamp_.toSec()); 
00671         //logTransform(et_out, world2base, gt_world2base.stamp_.toSec()); 
00672       } 
00673     }
00674     ROS_INFO_COND(!gt.empty() && with_ground_truth, "Written logfiles ground_truth_trajectory.txt and estimated_trajectory.txt");
00675     ROS_INFO_COND(gt.empty(),  "Written logfile estimated_trajectory.txt");
00676 }
00677 
00682 #include <visualization_msgs/Marker.h>
00683 #include <geometry_msgs/Point.h>
00684 typedef std::set<g2o::HyperGraph::Edge*> EdgeSet;
00685 
00686 void GraphManager::visualizeFeatureFlow3D(unsigned int marker_id, bool draw_outlier)
00687 {
00688     ScopedTimer s(__FUNCTION__);
00689 
00690     if (ransac_marker_pub_.getNumSubscribers() > 0){ //don't visualize, if nobody's looking
00691         if(curr_best_result_.edge.id1 < 0 || curr_best_result_.edge.id2 < 0) {
00692           ROS_WARN("Attempted to visualize invalid pairwise result");
00693           return;
00694         }
00695         visualization_msgs::Marker marker_lines;
00696 
00697         marker_lines.header.frame_id = "/openni_rgb_optical_frame";
00698         marker_lines.ns = "ransac_markers";
00699         marker_lines.header.stamp = ros::Time::now();
00700         marker_lines.action = visualization_msgs::Marker::ADD;
00701         marker_lines.pose.orientation.w = 1.0;
00702         marker_lines.id = marker_id;
00703         marker_lines.type = visualization_msgs::Marker::LINE_LIST;
00704         marker_lines.scale.x = 0.002;
00705         
00706         std_msgs::ColorRGBA color_red  ;  //red outlier
00707         color_red.r = 1.0;
00708         color_red.a = 1.0;
00709         std_msgs::ColorRGBA color_green;  //green inlier, newer endpoint
00710         color_green.g = 1.0;
00711         color_green.a = 1.0;
00712         std_msgs::ColorRGBA color_yellow;  //yellow inlier, earlier endpoint
00713         color_yellow.r = 1.0;
00714         color_yellow.g = 1.0;
00715         color_yellow.a = 1.0;
00716         std_msgs::ColorRGBA color_blue  ;  //red-blue outlier
00717         color_blue.b = 1.0;
00718         color_blue.a = 1.0;
00719 
00720         marker_lines.color = color_green; //just to set the alpha channel to non-zero
00721         const g2o::VertexSE3* earlier_v; //used to get the transform
00722         const g2o::VertexSE3* newer_v; //used to get the transform
00723         // end of initialization
00724         ROS_DEBUG("Matches Visualization start: %zu Matches, %zu Inliers", 
00725                   curr_best_result_.all_matches.size(), 
00726                   curr_best_result_.inlier_matches.size());
00727 
00728         // write all inital matches to the line_list
00729         marker_lines.points.clear();//necessary?
00730 
00731         earlier_v = dynamic_cast<g2o::VertexSE3*>(optimizer_->vertex(nodeId2VertexId(curr_best_result_.edge.id1)));
00732         newer_v   = dynamic_cast<g2o::VertexSE3*>(optimizer_->vertex(nodeId2VertexId(curr_best_result_.edge.id2)));
00733         Node* last = graph_.find(curr_best_result_.edge.id2)->second;
00734         Node* prev = graph_.find(curr_best_result_.edge.id1)->second;
00735 
00736         if (draw_outlier)
00737         {
00738           BOOST_FOREACH(cv::DMatch match, curr_best_result_.all_matches)
00739           {
00740           //for (unsigned int i=0;i<curr_best_result_.all_matches.size(); i++){
00741             int newer_id = match.queryIdx; //feature id in newer node
00742             int earlier_id = match.trainIdx; //feature id in earlier node
00743 
00744             //Outliers are red (newer) to blue (older)
00745             marker_lines.colors.push_back(color_red);
00746             marker_lines.colors.push_back(color_blue);
00747 
00748             marker_lines.points.push_back(
00749                     pointInWorldFrame(last->feature_locations_3d_[newer_id], newer_v->estimate()));
00750             marker_lines.points.push_back(
00751                     pointInWorldFrame(prev->feature_locations_3d_[earlier_id], earlier_v->estimate()));
00752           }
00753         }
00754 
00755         BOOST_FOREACH(cv::DMatch match, curr_best_result_.inlier_matches)
00756         {
00757         //for (unsigned int i=0;i<last_inlier_matches_.size(); i++){
00758           int newer_id = match.queryIdx; //feature id in newer node
00759           int earlier_id = match.trainIdx; //feature id in earlier node
00760 
00761           //inliers are green (newer) to blue (older)
00762           marker_lines.colors.push_back(color_green);
00763           marker_lines.colors.push_back(color_blue);
00764 
00765           marker_lines.points.push_back(
00766                   pointInWorldFrame(last->feature_locations_3d_[newer_id], newer_v->estimate()));
00767           marker_lines.points.push_back(
00768                   pointInWorldFrame(prev->feature_locations_3d_[earlier_id], earlier_v->estimate()));
00769         }
00770 
00771         ransac_marker_pub_.publish(marker_lines);
00772         ROS_DEBUG_STREAM("Published  " << marker_lines.points.size()/2 << " lines");
00773     }
00774 }
00775 
00776 
00777 void GraphManager::visualizeGraphEdges() const {
00778     ScopedTimer s(__FUNCTION__);
00779 
00780     if (marker_pub_.getNumSubscribers() > 0){ //no visualization for nobody
00781         ROS_WARN("Sending RVIZ Marker");
00782         visualization_msgs::Marker edges_marker;
00783         edges_marker.header.frame_id = "/openni_rgb_optical_frame"; //TODO: Should be a meaningfull fixed frame with known relative pose to the camera
00784         edges_marker.header.stamp = ros::Time::now();
00785         edges_marker.ns = "camera_pose_graph"; // Set the namespace and id for this marker.  This serves to create a unique ID
00786         edges_marker.id = 0;    // Any marker sent with the same namespace and id will overwrite the old one
00787 
00788         edges_marker.type = visualization_msgs::Marker::LINE_LIST;
00789         edges_marker.action = visualization_msgs::Marker::ADD; // Set the marker action.  Options are ADD and DELETE
00790         edges_marker.frame_locked = true; //rviz automatically retransforms the markers into the frame every update cycle
00791         // Set the scale of the marker -- 1x1x1 here means 1m on a side
00792         edges_marker.scale.x = 0.005; //line width
00793         //Global pose (used to transform all points)
00794         edges_marker.pose.position.x = 0;
00795         edges_marker.pose.position.y = 0;
00796         edges_marker.pose.position.z = 0;
00797         edges_marker.pose.orientation.x = 0.0;
00798         edges_marker.pose.orientation.y = 0.0;
00799         edges_marker.pose.orientation.z = 0.0;
00800         edges_marker.pose.orientation.w = 1.0;
00801         // Set the color -- be sure to set alpha to something non-zero!
00802         edges_marker.color.r = 1.0f;
00803         edges_marker.color.g = 1.0f;
00804         edges_marker.color.b = 1.0f;
00805         edges_marker.color.a = 0.5f;//looks smoother
00806         geometry_msgs::Point point; //start and endpoint for each line segment
00807         g2o::VertexSE3* v1,* v2; //used in loop
00808         EdgeSet::iterator edge_iter = cam_cam_edges_.begin();
00809         int counter = 0;
00810         for(;edge_iter != cam_cam_edges_.end(); edge_iter++, counter++) {
00811             g2o::EdgeSE3* myedge = dynamic_cast<g2o::EdgeSE3*>(*edge_iter);
00812             std::vector<g2o::HyperGraph::Vertex*>& myvertices = myedge->vertices();
00813             v1 = dynamic_cast<g2o::VertexSE3*>(myvertices.at(1));
00814             v2 = dynamic_cast<g2o::VertexSE3*>(myvertices.at(0));
00815 
00816             point.x = v1->estimate().translation().x();
00817             point.y = v1->estimate().translation().y();
00818             point.z = v1->estimate().translation().z();
00819             edges_marker.points.push_back(point);
00820             
00821             point.x = v2->estimate().translation().x();
00822             point.y = v2->estimate().translation().y();
00823             point.z = v2->estimate().translation().z();
00824             edges_marker.points.push_back(point);
00825         }
00826 
00827         marker_pub_.publish (edges_marker);
00828         ROS_INFO("published %d graph edges", counter);
00829     }
00830 
00831 }
00832 
00833 void GraphManager::visualizeGraphNodes() const {
00834     ScopedTimer s(__FUNCTION__);
00835 
00836     if (marker_pub_.getNumSubscribers() > 0){ //don't visualize, if nobody's looking
00837         visualization_msgs::Marker nodes_marker;
00838         nodes_marker.header.frame_id = "/openni_rgb_optical_frame"; //TODO: Should be a meaningfull fixed frame with known relative pose to the camera
00839         nodes_marker.header.stamp = ros::Time::now();
00840         nodes_marker.ns = "camera_pose_graph"; // Set the namespace and id for this marker.  This serves to create a unique ID
00841         nodes_marker.id = 1;    // Any marker sent with the same namespace and id will overwrite the old one
00842 
00843 
00844         nodes_marker.type = visualization_msgs::Marker::LINE_LIST;
00845         nodes_marker.action = visualization_msgs::Marker::ADD; // Set the marker action.  Options are ADD and DELETE
00846         nodes_marker.frame_locked = true; //rviz automatically retransforms the markers into the frame every update cycle
00847         // Set the scale of the marker -- 1x1x1 here means 1m on a side
00848         nodes_marker.scale.x = 0.002;
00849         //Global pose (used to transform all points) //TODO: is this the default pose anyway?
00850         nodes_marker.pose.position.x = 0;
00851         nodes_marker.pose.position.y = 0;
00852         nodes_marker.pose.position.z = 0;
00853         nodes_marker.pose.orientation.x = 0.0;
00854         nodes_marker.pose.orientation.y = 0.0;
00855         nodes_marker.pose.orientation.z = 0.0;
00856         nodes_marker.pose.orientation.w = 1.0;
00857         // Set the color -- be sure to set alpha to something non-zero!
00858         nodes_marker.color.r = 1.0f;
00859         nodes_marker.color.g = 0.0f;
00860         nodes_marker.color.b = 0.0f;
00861         nodes_marker.color.a = 1.0f;
00862 
00863 
00864         geometry_msgs::Point tail; //same startpoint for each line segment
00865         geometry_msgs::Point tip;  //different endpoint for each line segment
00866         std_msgs::ColorRGBA arrow_color_red  ;  //red x axis
00867         arrow_color_red.r = 1.0;
00868         arrow_color_red.a = 1.0;
00869         std_msgs::ColorRGBA arrow_color_green;  //green y axis
00870         arrow_color_green.g = 1.0;
00871         arrow_color_green.a = 1.0;
00872         std_msgs::ColorRGBA arrow_color_blue ;  //blue z axis
00873         arrow_color_blue.b = 1.0;
00874         arrow_color_blue.a = 1.0;
00875         Eigen::Vector3d origin(0.0,0.0,0.0);
00876         Eigen::Vector3d x_axis(0.2,0.0,0.0); //20cm long axis for the first (almost fixed) node
00877         Eigen::Vector3d y_axis(0.0,0.2,0.0);
00878         Eigen::Vector3d z_axis(0.0,0.0,0.2);
00879         Eigen::Vector3d tmp; //the transformed endpoints
00880         int counter = 0;
00881         g2o::VertexSE3* v; //used in loop
00882   for (g2o::HyperGraph::VertexSet::iterator it = camera_vertices.begin(); it != camera_vertices.end(); ++it){
00883 
00884    // VertexIDMap::iterator vertex_iter = optimizer_->vertices().begin();
00885    // for(/*see above*/; vertex_iter != optimizer_->vertices().end(); vertex_iter++, counter++) {
00886 
00887    v = dynamic_cast<g2o::VertexSE3* >(*it);
00888             //v->estimate().rotation().x()+ v->estimate().rotation().y()+ v->estimate().rotation().z()+ v->estimate().rotation().w();
00889             tmp = v->estimate() * origin;
00890             tail.x = tmp.x();
00891             tail.y = tmp.y();
00892             tail.z = tmp.z();
00893             //Endpoints X-Axis
00894             nodes_marker.points.push_back(tail);
00895             nodes_marker.colors.push_back(arrow_color_red);
00896             tmp = v->estimate() * x_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_red);
00902             //Endpoints Y-Axis
00903             nodes_marker.points.push_back(tail);
00904             nodes_marker.colors.push_back(arrow_color_green);
00905             tmp = v->estimate() * y_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_green);
00911             //Endpoints Z-Axis
00912             nodes_marker.points.push_back(tail);
00913             nodes_marker.colors.push_back(arrow_color_blue);
00914             tmp = v->estimate() * z_axis;
00915             tip.x  = tmp.x();
00916             tip.y  = tmp.y();
00917             tip.z  = tmp.z();
00918             nodes_marker.points.push_back(tip);
00919             nodes_marker.colors.push_back(arrow_color_blue);
00920             //shorten all nodes after the first one
00921             x_axis.x() = 0.1;
00922             y_axis.y() = 0.1;
00923             z_axis.z() = 0.1;
00924         }
00925 
00926         marker_pub_.publish (nodes_marker);
00927         ROS_INFO("published %d graph nodes", counter);
00928     }
00929 
00930 }
00931 
00932 void GraphManager::saveG2OGraph(QString filename)
00933 {
00934   ROS_INFO("Saving G2O graph to %s", qPrintable(filename));
00935   optimizer_->save(qPrintable(filename));
00936 }
00937 
00938 tf::StampedTransform GraphManager::stampedTransformInWorldFrame(const Node* node, const tf::Transform& computed_motion) const 
00939 {
00940     std::string fixed_frame = ParameterServer::instance()->get<std::string>("fixed_frame_name");
00941     std::string base_frame  = ParameterServer::instance()->get<std::string>("base_frame_name");
00942     if(base_frame.empty()){ //if there is no base frame defined, use frame of sensor data
00943       base_frame = node->header_.frame_id;
00944     }
00945     const tf::StampedTransform& base2points = node->getBase2PointsTransform();//get pose of base w.r.t current pc at capture time
00946 
00947     tf::Transform world2base = init_base_pose_*base2points*computed_motion*base2points.inverse();
00948     //printTransform("World->Base", world2base);
00949 
00950     return tf::StampedTransform(world2base.inverse(), base2points.stamp_, base_frame, fixed_frame);
00951 }
00952 
00953 void GraphManager::broadcastLatestTransform(const ros::TimerEvent& event) const
00954 {
00955     //printTransform("Broadcasting cached transform", latest_transform_cache_);
00956   /*
00957     tf::StampedTransform tmp(latest_transform_cache_, 
00958                              ros::Time::now(),
00959                              latest_transform_cache_.frame_id_,
00960                              latest_transform_cache_.child_frame_id_);
00961     broadcastTransform(tmp);
00962     */
00963 }
00964 
00965 void GraphManager::broadcastTransform(const tf::StampedTransform& stamped_transform) 
00966 {
00967     br_.sendTransform(stamped_transform);
00968     if(graph_.size() > 0){
00969       Node* current_node = graph_.at(graph_.size() - 1);
00970       if(current_node && current_node->header_.stamp.toSec() == stamped_transform.stamp_.toSec()){
00971         publishCloud(current_node, current_node->header_.stamp, online_cloud_pub_);
00972       } else {
00973         ROS_WARN("Timestamp of transform does not match node");
00974       }
00975     }
00976 }
00977 
00978 /*
00979 void GraphManager::broadcastTransform(Node* node, tf::Transform& computed_motion){
00980     std::string fixed_frame = ParameterServer::instance()->get<std::string>("fixed_frame_name");
00981     std::string base_frame  = ParameterServer::instance()->get<std::string>("base_frame_name");
00982     if(base_frame.empty()){ //if there is no base frame defined, use frame of sensor data
00983       base_frame = node->header_.frame_id;
00984     }
00985     
00986     /*
00987     if(graph_.size() == 0){
00988       ROS_WARN("Cannot broadcast transform while graph is empty sending identity");
00989       br_.sendTransform(tf::StampedTransform(tf::Transform::getIdentity(), ros::Time::now(), fixed_frame, base_frame));
00990       return;
00991     }
00992     * /
00993     const tf::StampedTransform& base2points = node->getBase2PointsTransform();//get pose of base w.r.t current pc at capture time
00994 
00995     //Assumption: computed_motion_ contains last pose
00996     tf::Transform world2base = init_base_pose_*base2points*computed_motion*base2points.inverse();
00997     printTransform("World->Base", world2base);
00998 
00999     ROS_DEBUG("Broadcasting transform");
01000     
01001     br_.sendTransform(tf::StampedTransform(world2base.inverse(), base2points.stamp_, base_frame, fixed_frame));
01002 }
01003 */
01004 
01006 void publishCloud(Node* node, ros::Time timestamp, ros::Publisher pub){
01007   if (pub.getNumSubscribers() != 0){
01008     myHeader backup_h(node->pc_col->header);
01009     myHeader newtime_h(node->pc_col->header);
01010     newtime_h.stamp = timestamp;
01011     node->pc_col->header = newtime_h;
01012     pub.publish(node->pc_col);
01013     ROS_INFO("Pointcloud with id %i sent with frame %s", node->id_, node->pc_col->header.frame_id.c_str());
01014     node->pc_col->header = backup_h;
01015   }
01016 }
01017 
01018 void drawFeatureConnectors(cv::Mat& canvas, cv::Scalar line_color, 
01019                            const std::vector<cv::DMatch> matches,
01020                            const std::vector<cv::KeyPoint>& newer_keypoints,
01021                            const std::vector<cv::KeyPoint>& older_keypoints,
01022                            int line_thickness = 2,
01023                            int line_type = 16)
01024 {
01025     const double pi_fourth = 3.14159265358979323846 / 4.0;
01026     const int circle_radius = 6;
01027     //const int cv_aa = 16;
01028     for(unsigned int mtch = 0; mtch < matches.size(); mtch++) {
01029         cv::Point2f p,q; //TODO: Use sub-pixel-accuracy
01030         unsigned int newer_idx = matches[mtch].queryIdx;
01031         unsigned int earlier_idx = matches[mtch].trainIdx;
01032         if(newer_idx > newer_keypoints.size()) break;//Added in case the feature info has been cleared
01033         q = newer_keypoints[newer_idx].pt;
01034         if(earlier_idx > older_keypoints.size()) break;//Added in case the feature info has been cleared
01035         p = older_keypoints[earlier_idx].pt;
01036 
01037         double angle;    angle = atan2( (double) p.y - q.y, (double) p.x - q.x );
01038         double hypotenuse = cv::norm(p-q);
01039         if(hypotenuse > 0.1){  //only larger motions larger than one pix get an arrow line
01040             cv::line( canvas, p, q, line_color, line_thickness, line_type );
01041         } else { //draw a smaller circle into the bigger one 
01042             cv::circle(canvas, p, 1, line_color, line_thickness, line_type);
01043         }
01044         if(hypotenuse > 3.0){  //only larger motions larger than this get an arrow tip
01045             /* Now draw the tips of the arrow.  */
01046             p.x =  (q.x + 4 * cos(angle + pi_fourth));
01047             p.y =  (q.y + 4 * sin(angle + pi_fourth));
01048             cv::line( canvas, p, q, line_color, line_thickness, line_type );
01049             p.x =  (q.x + 4 * cos(angle - pi_fourth));
01050             p.y =  (q.y + 4 * sin(angle - pi_fourth));
01051             cv::line( canvas, p, q, line_color, line_thickness, line_type );
01052         } 
01053     }
01054 }
01055 void GraphManager::drawFeatureFlow(cv::Mat& canvas, cv::Scalar line_color,
01056                                    cv::Scalar circle_color)
01057 {
01058     if(!ParameterServer::instance()->get<bool>("use_gui")){ return; }
01059     ROS_DEBUG("Number of features to draw: %d", (int)curr_best_result_.inlier_matches.size());
01060 
01061     if(graph_.empty()) {
01062       ROS_WARN("Feature Flow for empty graph requested. Bug?");
01063       return;
01064     } else if(graph_.size() == 1 || curr_best_result_.edge.id1 == -1 ) {//feature flow is only available between at least two nodes
01065       Node* newernode = graph_[graph_.size()-1];
01066       cv::drawKeypoints(canvas, newernode->feature_locations_2d_, canvas, cv::Scalar(255), 5);
01067       return;
01068     } 
01069 
01070     Node* earliernode = graph_[curr_best_result_.edge.id1];//graph_.size()-2; //compare current to previous
01071     Node* newernode = graph_[curr_best_result_.edge.id2];
01072     if(earliernode == NULL){
01073       if(newernode == NULL ){ ROS_ERROR("Nullpointer for Node %u", (unsigned int)graph_.size()-1); }
01074       ROS_ERROR("Nullpointer for Node %d", curr_best_result_.edge.id1);
01075       curr_best_result_.edge.id1 = 0;
01076       return;
01077     } else if(newernode == NULL ){
01078       ROS_ERROR("Nullpointer for Node %u", (unsigned int)graph_.size()-1);
01079       return;
01080     }
01081 
01082     cv::Mat tmpimage = cv::Mat::zeros(canvas.rows, canvas.cols, canvas.type());
01083     if(canvas.type() == CV_8UC1) circle_color = cv::Scalar(255);
01084 
01085     //Generate different keypoint sets for those with depth and those without
01086     std::vector<cv::KeyPoint> with_depth, without_depth;
01087     for(int i = 0; i < newernode->feature_locations_2d_.size(); i++){
01088       if(isnan(newernode->feature_locations_3d_[i](2))){
01089           without_depth.push_back(newernode->feature_locations_2d_[i]);
01090       } else {
01091           with_depth.push_back(newernode->feature_locations_2d_[i]);
01092       }
01093     }
01094 
01095     //Juergen: commented out to draw key points seperately
01096     //Draw normal keypoints in given color 
01097     //cv::drawKeypoints(canvas, with_depth, tmpimage, circle_color, 5);
01098     //Draw depthless keypoints in orange 
01099     //cv::drawKeypoints(canvas, without_depth, tmpimage, cv::Scalar(0,128,255,0), 5);
01100     //canvas+=tmpimage;
01101 
01102 
01103     //Outliers
01104     drawFeatureConnectors(canvas, cv::Scalar(100.0), curr_best_result_.all_matches, newernode->feature_locations_2d_, earliernode->feature_locations_2d_, 1);
01105     //Inliers
01106     drawFeatureConnectors(canvas, line_color, curr_best_result_.inlier_matches, newernode->feature_locations_2d_, earliernode->feature_locations_2d_, 2);
01107 
01108 }
01109 
01110 void GraphManager::drawFeatureFlow(cv::Mat& canvas, cv::Mat& canvas_features, cv::Scalar line_color,
01111                                    cv::Scalar circle_color)
01112 {
01113     if(!ParameterServer::instance()->get<bool>("use_gui")){ return; }
01114     ROS_DEBUG("Number of features to draw: %d", (int)curr_best_result_.inlier_matches.size());
01115 
01116     if(graph_.empty()) {
01117       ROS_WARN("Feature Flow for empty graph requested. Bug?");
01118       return;
01119     } else if(graph_.size() == 1 || curr_best_result_.edge.id1 == -1 ) {//feature flow is only available between at least two nodes
01120       Node* newernode = graph_[graph_.size()-1];
01121       cv::drawKeypoints(canvas, newernode->feature_locations_2d_, canvas, cv::Scalar(255), 5);
01122       return;
01123     } 
01124 
01125     Node* earliernode = graph_[curr_best_result_.edge.id1];//graph_.size()-2; //compare current to previous
01126     Node* newernode = graph_[curr_best_result_.edge.id2];
01127     if(earliernode == NULL){
01128       if(newernode == NULL ){ ROS_ERROR("Nullpointer for Node %u", (unsigned int)graph_.size()-1); }
01129       ROS_ERROR("Nullpointer for Node %d", curr_best_result_.edge.id1);
01130       curr_best_result_.edge.id1 = 0;
01131       return;
01132     } else if(newernode == NULL ){
01133       ROS_ERROR("Nullpointer for Node %u", (unsigned int)graph_.size()-1);
01134       return;
01135     }
01136 
01137     cv::Mat tmpimage = cv::Mat::zeros(canvas.rows, canvas.cols, canvas.type());
01138     if(canvas.type() == CV_8UC1) circle_color = cv::Scalar(255);
01139 
01140     //Generate different keypoint sets for those with depth and those without
01141     std::vector<cv::KeyPoint> with_depth, without_depth;
01142     for(int i = 0; i < newernode->feature_locations_2d_.size(); i++){
01143       if(isnan(newernode->feature_locations_3d_[i](2))){
01144           without_depth.push_back(newernode->feature_locations_2d_[i]);
01145       } else {
01146           with_depth.push_back(newernode->feature_locations_2d_[i]);
01147       }
01148     }
01149 
01150     //Juergen: commented out to draw key points seperately
01151     //Draw normal keypoints in given color 
01152     //cv::drawKeypoints(canvas, with_depth, tmpimage, circle_color, 5);
01153     //Draw depthless keypoints in orange 
01154     //cv::drawKeypoints(canvas, without_depth, tmpimage, cv::Scalar(0,128,255,0), 5);
01155     //canvas+=tmpimage;
01156 
01157     //drawFeatureConnectors(canvas, cv::Scalar(150.0), curr_best_result_.all_matches, newernode->feature_locations_2d_, earliernode->feature_locations_2d_);
01158     drawFeatureConnectors(canvas, line_color, curr_best_result_.inlier_matches, newernode->feature_locations_2d_, earliernode->feature_locations_2d_);
01159 
01160 }
01161 void GraphManager::savePlyFile(QString filename, pointcloud_normal_type& full_cloud){
01162     
01163   
01164     std::fstream file;
01165     file.open(filename.toStdString().c_str(), std::fstream::out);
01166 
01167 
01168     if (!file.is_open()) {
01169       std::cerr << "Could not write to file " << filename.toStdString() << std::endl;
01170       return;
01171     }
01172 
01173         float x,y,z,nx,ny,nz;
01174 
01175         unsigned int r,g,b;
01176         int n=0;
01177         std::stringstream data;
01178         data << std::setprecision(5);
01179 
01180         for (int i = 0; i < full_cloud.points.size(); ++i) {
01181                 point_normal_type p = full_cloud.points[i];
01182                 
01183                 x=full_cloud.points[i].x;
01184                 y=full_cloud.points[i].y;
01185                 z=full_cloud.points[i].z;
01186                 nx=full_cloud.points[i].normal_x;
01187                 ny=full_cloud.points[i].normal_y;
01188                 nz=full_cloud.points[i].normal_z;
01189                 if (std::isnan(x)||std::isnan(y)||std::isnan(z)||std::isnan(nx)||std::isnan(ny)||std::isnan(nz)){
01190                         continue;
01191                 }
01192                 data << x  << " " << y << " " << z << " " << nx << " " << ny << " " << nz << " "; //std::endl;
01193 
01194                 
01195                 #ifdef RGB_IS_4TH_DIM
01196                 b = *(  (unsigned char*)(&p.data[3]));
01197                 g = *(1+(unsigned char*)(&p.data[3]));
01198                 r = *(2+(unsigned char*)(&p.data[3]));
01199                 #else
01200                 b = *(  (unsigned char*)(&p.rgb));
01201                 g = *(1+(unsigned char*)(&p.rgb));
01202                 r = *(2+(unsigned char*)(&p.rgb));
01203                 #endif
01204                 
01205                 if (r>255) r=255; 
01206                 if (g>255) g=255; 
01207                 if (b>255) b=255; 
01208                 data << r << " " << g << " " << b << " " << r << " " << g << " " << b << std::endl;
01209                 n++;
01210         }
01211 
01212     file << "ply\n";
01213     file << "format ascii 1.0\n";
01214     file << "element vertex " << n << "\n";
01215         file << "property float x\n";
01216         file << "property float y\n";
01217         file << "property float z\n";
01218         file << "property float nx\n";
01219         file << "property float ny\n";
01220         file << "property float nz\n";
01221         file << "property uchar red\n";
01222         file << "property uchar green\n";
01223         file << "property uchar blue\n";
01224 //      file << "property uchar alpha\n";
01225         file << "property uchar diffuse_red\n";
01226         file << "property uchar diffuse_green\n";
01227         file << "property uchar diffuse_blue\n";
01228         file << "end_header\n";
01229 
01230         file << data.str();
01231         file.close();
01232 }


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