/*
* Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke
*
* This file is part of RTAB-Map.
*
* RTAB-Map is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* RTAB-Map is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with RTAB-Map. If not, see .
*/
#include "rtabmap/gui/DatabaseViewer.h"
#include "rtabmap/gui/CloudViewer.h"
#include "ui_DatabaseViewer.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "rtabmap/core/Memory.h"
#include "rtabmap/core/DBDriver.h"
#include "rtabmap/gui/KeypointItem.h"
#include "rtabmap/gui/UCv2Qt.h"
#include "rtabmap/core/util3d.h"
#include "rtabmap/core/Signature.h"
#include "rtabmap/gui/DataRecorder.h"
#include "rtabmap/core/Image.h"
#include "ExportDialog.h"
#include "DetailedProgressDialog.h"
#include
#include
#include
DatabaseViewer::DatabaseViewer(QWidget * parent) :
QMainWindow(parent),
memory_(0)
{
pathDatabase_ = QDir::homePath()+"/Documents/RTAB-Map"; //use home directory by default
if(!UDirectory::exists(pathDatabase_.toStdString()))
{
pathDatabase_ = QDir::homePath();
}
ui_ = new Ui_DatabaseViewer();
ui_->setupUi(this);
ui_->dockWidget_constraints->setVisible(false);
ui_->menuView->addAction(ui_->dockWidget_constraints->toggleViewAction());
ui_->dockWidget_graphView->setVisible(false);
ui_->menuView->addAction(ui_->dockWidget_graphView->toggleViewAction());
connect(ui_->dockWidget_graphView->toggleViewAction(), SIGNAL(triggered()), this, SLOT(updateGraphView()));
connect(ui_->actionQuit, SIGNAL(triggered()), this, SLOT(close()));
connect(ui_->buttonBox, SIGNAL(rejected()), this, SLOT(close()));
// connect actions with custom slots
connect(ui_->actionOpen_database, SIGNAL(triggered()), this, SLOT(openDatabase()));
connect(ui_->actionExport, SIGNAL(triggered()), this, SLOT(exportDatabase()));
connect(ui_->actionExtract_images, SIGNAL(triggered()), this, SLOT(extractImages()));
connect(ui_->actionGenerate_graph_dot, SIGNAL(triggered()), this, SLOT(generateGraph()));
connect(ui_->actionGenerate_local_graph_dot, SIGNAL(triggered()), this, SLOT(generateLocalGraph()));
connect(ui_->actionGenerate_TORO_graph_graph, SIGNAL(triggered()), this, SLOT(generateTOROGraph()));
connect(ui_->actionView_3D_map, SIGNAL(triggered()), this, SLOT(view3DMap()));
connect(ui_->actionGenerate_3D_map_pcd, SIGNAL(triggered()), this, SLOT(generate3DMap()));
ui_->actionGenerate_TORO_graph_graph->setEnabled(false);
ui_->horizontalSlider_A->setTracking(false);
ui_->horizontalSlider_B->setTracking(false);
ui_->horizontalSlider_A->setEnabled(false);
ui_->horizontalSlider_B->setEnabled(false);
connect(ui_->horizontalSlider_A, SIGNAL(valueChanged(int)), this, SLOT(sliderAValueChanged(int)));
connect(ui_->horizontalSlider_B, SIGNAL(valueChanged(int)), this, SLOT(sliderBValueChanged(int)));
connect(ui_->horizontalSlider_A, SIGNAL(sliderMoved(int)), this, SLOT(sliderAMoved(int)));
connect(ui_->horizontalSlider_B, SIGNAL(sliderMoved(int)), this, SLOT(sliderBMoved(int)));
ui_->horizontalSlider_neighbors->setTracking(false);
ui_->horizontalSlider_loops->setTracking(false);
ui_->horizontalSlider_neighbors->setEnabled(false);
ui_->horizontalSlider_loops->setEnabled(false);
connect(ui_->horizontalSlider_neighbors, SIGNAL(valueChanged(int)), this, SLOT(sliderNeighborValueChanged(int)));
connect(ui_->horizontalSlider_loops, SIGNAL(valueChanged(int)), this, SLOT(sliderLoopValueChanged(int)));
connect(ui_->horizontalSlider_neighbors, SIGNAL(sliderMoved(int)), this, SLOT(sliderNeighborValueChanged(int)));
connect(ui_->horizontalSlider_loops, SIGNAL(sliderMoved(int)), this, SLOT(sliderLoopValueChanged(int)));
ui_->horizontalSlider_iterations->setTracking(false);
ui_->dockWidget_graphView->setEnabled(false);
connect(ui_->horizontalSlider_iterations, SIGNAL(valueChanged(int)), this, SLOT(sliderIterationsValueChanged(int)));
connect(ui_->horizontalSlider_iterations, SIGNAL(sliderMoved(int)), this, SLOT(sliderIterationsValueChanged(int)));
connect(ui_->spinBox_iterations, SIGNAL(editingFinished()), this, SLOT(updateGraphView()));
connect(ui_->spinBox_optimizationsFrom, SIGNAL(editingFinished()), this, SLOT(updateGraphView()));
connect(ui_->checkBox_initGuess, SIGNAL(stateChanged(int)), this, SLOT(updateGraphView()));
}
DatabaseViewer::~DatabaseViewer()
{
delete ui_;
if(memory_)
{
delete memory_;
}
}
void DatabaseViewer::openDatabase()
{
QString path = QFileDialog::getOpenFileName(this, tr("Select file"), pathDatabase_, tr("Databases (*.db)"));
if(!path.isEmpty())
{
openDatabase(path);
}
}
bool DatabaseViewer::openDatabase(const QString & path)
{
UDEBUG("Open database \"%s\"", path.toStdString().c_str());
if(QFile::exists(path))
{
if(memory_)
{
delete memory_;
memory_ = 0;
ids_.clear();
idToIndex_.clear();
neighborLinks_.clear();
loopLinks_.clear();
graphes_.clear();
poses_.clear();
links_.clear();
scans_.clear();
ui_->actionGenerate_TORO_graph_graph->setEnabled(false);
}
std::string driverType = "sqlite3";
rtabmap::ParametersMap parameters;
parameters.insert(rtabmap::ParametersPair(rtabmap::Parameters::kDbSqlite3InMemory(), "false"));
parameters.insert(rtabmap::ParametersPair(rtabmap::Parameters::kMemIncrementalMemory(), "false"));
memory_ = new rtabmap::Memory();
if(!memory_->init(path.toStdString(), false, parameters))
{
QMessageBox::warning(this, "Database error", tr("Can't open database \"%1\"").arg(path));
}
else
{
pathDatabase_ = UDirectory::getDir(path.toStdString()).c_str();
updateIds();
return true;
}
}
else
{
QMessageBox::warning(this, "Database error", tr("Database \"%1\" does not exist.").arg(path));
}
return false;
}
void DatabaseViewer::exportDatabase()
{
if(!memory_ || ids_.size() == 0)
{
return;
}
rtabmap::ExportDialog dialog;
if(dialog.exec())
{
if(!dialog.outputPath().isEmpty())
{
int framesIgnored = dialog.framesIgnored();
QString path = dialog.outputPath();
rtabmap::DataRecorder recorder;
if(recorder.init(path, false))
{
rtabmap::DetailedProgressDialog progressDialog(this);
progressDialog.setMaximumSteps(ids_.size() / (1+framesIgnored) + 1);
progressDialog.show();
for(int i=0; i compressedRgb, compressedDepth, compressedDepth2d;
float tmpDepthConstant;
rtabmap::Transform tmpLocalTransform, pose;
memory_->getImageDepth(id, compressedRgb, compressedDepth, compressedDepth2d, tmpDepthConstant, tmpLocalTransform);
if(dialog.isOdomExported())
{
memory_->getPose(id, pose, true);
}
cv::Mat rgb, depth, depth2d;
float depthConstant = 0;
rtabmap::Transform localTransform;
if(dialog.isRgbExported())
{
rgb = rtabmap::util3d::uncompressImage(compressedRgb);
}
if(dialog.isDepthExported())
{
depth = rtabmap::util3d::uncompressImage(compressedDepth);
depthConstant = tmpDepthConstant;
localTransform = tmpLocalTransform;
}
if(dialog.isDepth2dExported())
{
depth2d = rtabmap::util3d::uncompressData(compressedDepth2d);
}
rtabmap::Image data(rgb, depth, depth2d, depthConstant, pose, localTransform, id);
recorder.addData(data);
progressDialog.appendText(tr("Exported node %1").arg(id));
progressDialog.incrementStep();
QApplication::processEvents();
}
progressDialog.setValue(progressDialog.maximumSteps());
progressDialog.appendText("Export finished!");
}
else
{
UERROR("DataRecorder init failed?!");
}
}
else
{
QMessageBox::warning(this, tr("Cannot export database"), tr("An output path must be set!"));
}
}
}
void DatabaseViewer::extractImages()
{
if(!memory_ || ids_.size() == 0)
{
return;
}
QString path = QFileDialog::getExistingDirectory(this, tr("Select directory where to save images..."), QDir::homePath());
if(!path.isNull())
{
for(int i=0; i compressedRgb = memory_->getImage(id);
if(compressedRgb.size())
{
cv::Mat imageMat = rtabmap::util3d::uncompressImage(compressedRgb);
cv::imwrite(QString("%1/%2.png").arg(path).arg(id).toStdString(), imageMat);
UINFO(QString("Saved %1/%2.png").arg(path).arg(id).toStdString().c_str());
}
}
}
}
void DatabaseViewer::updateIds()
{
if(!memory_)
{
return;
}
std::set ids = memory_->getAllSignatureIds();
ids_ = QList::fromStdList(std::list(ids.begin(), ids.end()));
idToIndex_.clear();
for(int i=0; igetLastWorkingSignature())
{
std::map nids = memory_->getNeighborsId(memory_->getLastWorkingSignature()->id(), 0, -1, true);
memory_->getMetricConstraints(uKeys(nids), poses_, links_, true);
ui_->spinBox_optimizationsFrom->setRange(1, memory_->getLastWorkingSignature()->id());
ui_->spinBox_optimizationsFrom->setValue(memory_->getLastWorkingSignature()->id());
}
ui_->actionGenerate_TORO_graph_graph->setEnabled(false);
graphes_.clear();
neighborLinks_.clear();
loopLinks_.clear();
for(std::multimap::iterator iter = links_.begin(); iter!=links_.end(); ++iter)
{
if(!iter->second.transform().isNull())
{
if(iter->second.type() == rtabmap::Link::kNeighbor)
{
neighborLinks_.append(iter->second);
}
else
{
loopLinks_.append(iter->second);
}
}
else
{
UERROR("Transform null for link from %d to %d", iter->first, iter->second.to());
}
}
UINFO("Loaded %d ids", ids_.size());
if(ids_.size())
{
ui_->horizontalSlider_A->setMinimum(0);
ui_->horizontalSlider_B->setMinimum(0);
ui_->horizontalSlider_A->setMaximum(ids_.size()-1);
ui_->horizontalSlider_B->setMaximum(ids_.size()-1);
ui_->horizontalSlider_A->setEnabled(true);
ui_->horizontalSlider_B->setEnabled(true);
ui_->horizontalSlider_A->setSliderPosition(0);
ui_->horizontalSlider_B->setSliderPosition(0);
sliderAValueChanged(0);
sliderBValueChanged(0);
}
else
{
ui_->horizontalSlider_A->setEnabled(false);
ui_->horizontalSlider_B->setEnabled(false);
ui_->label_idA->setText("NaN");
ui_->label_idB->setText("NaN");
}
if(neighborLinks_.size())
{
ui_->horizontalSlider_neighbors->setMinimum(0);
ui_->horizontalSlider_neighbors->setMaximum(neighborLinks_.size()-1);
ui_->horizontalSlider_neighbors->setEnabled(true);
ui_->horizontalSlider_neighbors->setSliderPosition(0);
}
else
{
ui_->horizontalSlider_neighbors->setEnabled(false);
}
if(loopLinks_.size())
{
ui_->horizontalSlider_loops->setMinimum(0);
ui_->horizontalSlider_loops->setMaximum(loopLinks_.size()-1);
ui_->horizontalSlider_loops->setEnabled(true);
ui_->horizontalSlider_loops->setSliderPosition(0);
}
else
{
ui_->horizontalSlider_loops->setEnabled(false);
}
updateGraphView();
}
void DatabaseViewer::generateGraph()
{
if(!memory_)
{
QMessageBox::warning(this, tr("Cannot generate a graph"), tr("A database must must loaded first...\nUse File->Open database."));
return;
}
QString path = QFileDialog::getSaveFileName(this, tr("Save File"), pathDatabase_+"/Graph.dot", tr("Graphiz file (*.dot)"));
if(!path.isEmpty())
{
memory_->generateGraph(path.toStdString());
}
}
void DatabaseViewer::generateLocalGraph()
{
if(!ids_.size() || !memory_)
{
QMessageBox::warning(this, tr("Cannot generate a graph"), tr("The database is empty..."));
return;
}
bool ok = false;
int id = QInputDialog::getInt(this, tr("Around which location?"), tr("Location ID"), ids_.first(), ids_.first(), ids_.last(), 1, &ok);
if(ok)
{
int margin = QInputDialog::getInt(this, tr("Depth around the location?"), tr("Margin"), 4, 1, 100, 1, &ok);
if(ok)
{
QString path = QFileDialog::getSaveFileName(this, tr("Save File"), pathDatabase_+"/Graph" + QString::number(id) + ".dot", tr("Graphiz file (*.dot)"));
if(!path.isEmpty())
{
std::map ids = memory_->getNeighborsId(id, margin, -1, false);
if(ids.size() > 0)
{
ids.insert(std::pair(id, 0));
std::set idsSet;
for(std::map::iterator iter = ids.begin(); iter!=ids.end(); ++iter)
{
idsSet.insert(idsSet.end(), iter->first);
UINFO("Node %d", iter->first);
}
UINFO("idsSet=%d", idsSet.size());
memory_->generateGraph(path.toStdString(), idsSet);
}
else
{
QMessageBox::critical(this, tr("Error"), tr("No neighbors found for signature %1.").arg(id));
}
}
}
}
}
void DatabaseViewer::generateTOROGraph()
{
if(!graphes_.size() || !links_.size())
{
QMessageBox::warning(this, tr("Cannot generate a TORO graph"), tr("No poses or no links..."));
return;
}
bool ok = false;
int id = QInputDialog::getInt(this, tr("Which iteration?"), tr("Iteration (0 -> %1)").arg(graphes_.size()-1), graphes_.size()-1, 0, graphes_.size()-1, 1, &ok);
if(ok)
{
QString path = QFileDialog::getSaveFileName(this, tr("Save File"), pathDatabase_+"/constraints" + QString::number(id) + ".graph", tr("TORO file (*.graph)"));
if(!path.isEmpty())
{
rtabmap::util3d::saveTOROGraph(path.toStdString(), uValueAt(graphes_, id), links_);
}
}
}
void DatabaseViewer::view3DMap()
{
if(!ids_.size() || !memory_)
{
QMessageBox::warning(this, tr("Cannot generate a graph"), tr("The database is empty..."));
return;
}
bool ok = false;
int id = QInputDialog::getInt(this, tr("Around which location?"), tr("Location ID"), ids_.first(), ids_.first(), ids_.last(), 1, &ok);
if(ok)
{
int margin = QInputDialog::getInt(this, tr("Depth around the location?"), tr("Margin (0=no limit)"), 0, 0, 100, 1, &ok);
if(ok)
{
QStringList items;
items.append("1");
items.append("2");
items.append("4");
items.append("8");
items.append("16");
QString item = QInputDialog::getItem(this, tr("Decimation?"), tr("Image decimation"), items, 2, false, &ok);
if(ok)
{
int decimation = item.toInt();
double maxDepth = QInputDialog::getDouble(this, tr("Camera depth?"), tr("Maximum depth (m, 0=no max):"), 4.0, 0, 10, 2, &ok);
if(ok)
{
std::map ids = memory_->getNeighborsId(id, margin, -1, false);
if(ids.size() > 0)
{
rtabmap::DetailedProgressDialog progressDialog(this);
progressDialog.setMaximumSteps(ids.size()+2);
progressDialog.show();
progressDialog.appendText("Graph generation...");
std::map poses, optimizedPoses;
std::multimap edgeConstraints;
memory_->getMetricConstraints(uKeys(ids), poses, edgeConstraints, true);
progressDialog.appendText("Graph generation... done!");
progressDialog.incrementStep();
progressDialog.appendText("Graph optimization...");
rtabmap::Transform mapCorrection;
rtabmap::util3d::optimizeTOROGraph(poses, edgeConstraints, optimizedPoses, mapCorrection, 100, true);
progressDialog.appendText("Graph optimization... done!");
progressDialog.incrementStep();
// create a window
QWidget * window = new QWidget(this, Qt::Popup);
window->setAttribute(Qt::WA_DeleteOnClose);
window->setWindowFlags(Qt::Dialog);
window->setWindowTitle(tr("3D Map"));
window->setMinimumWidth(800);
window->setMinimumHeight(600);
rtabmap::CloudViewer * viewer = new rtabmap::CloudViewer(window);
QVBoxLayout *layout = new QVBoxLayout();
layout->addWidget(viewer);
window->setLayout(layout);
window->showNormal();
for(std::map::iterator iter = ids.begin(); iter!=ids.end(); ++iter)
{
rtabmap::Transform pose = uValue(optimizedPoses, iter->first, rtabmap::Transform());
if(!pose.isNull())
{
std::vector image, depth, depth2d;
float depthConstant;
rtabmap::Transform localTransform;
memory_->getImageDepth(iter->first, image, depth, depth2d, depthConstant, localTransform);
pcl::PointCloud::Ptr cloud;
cv::Mat imageMat = rtabmap::util3d::uncompressImage(image);
cv::Mat depthMat = rtabmap::util3d::uncompressImage(depth);
cloud = rtabmap::util3d::cloudFromDepthRGB(
imageMat,
depthMat,
depthMat.cols/2, depthMat.rows/2,
1.0f/depthConstant, 1.0f/depthConstant,
decimation);
if(maxDepth)
{
cloud = rtabmap::util3d::passThrough(cloud, "z", 0, maxDepth);
}
cloud = rtabmap::util3d::transformPointCloud(cloud, localTransform);
viewer->addCloud(uFormat("cloud%d", iter->first), cloud, pose);
UINFO("Generated %d (%d points)", iter->first, cloud->size());
progressDialog.appendText(QString("Generated %1 (%2 points)").arg(iter->first).arg(cloud->size()));
progressDialog.incrementStep();
QApplication::processEvents();
}
}
progressDialog.setValue(progressDialog.maximumSteps());
}
else
{
QMessageBox::critical(this, tr("Error"), tr("No neighbors found for node %1.").arg(id));
}
}
}
}
}
}
void DatabaseViewer::generate3DMap()
{
if(!ids_.size() || !memory_)
{
QMessageBox::warning(this, tr("Cannot generate a graph"), tr("The database is empty..."));
return;
}
bool ok = false;
int id = QInputDialog::getInt(this, tr("Around which location?"), tr("Location ID"), ids_.first(), ids_.first(), ids_.last(), 1, &ok);
if(ok)
{
int margin = QInputDialog::getInt(this, tr("Depth around the location?"), tr("Margin (0=no limit)"), 0, 0, 100, 1, &ok);
if(ok)
{
QStringList items;
items.append("1");
items.append("2");
items.append("4");
items.append("8");
items.append("16");
QString item = QInputDialog::getItem(this, tr("Decimation?"), tr("Image decimation"), items, 2, false, &ok);
if(ok)
{
int decimation = item.toInt();
double maxDepth = QInputDialog::getDouble(this, tr("Camera depth?"), tr("Maximum depth (m, 0=no max):"), 4.0, 0, 10, 2, &ok);
if(ok)
{
QString path = QFileDialog::getExistingDirectory(this, tr("Save directory"), pathDatabase_);
if(!path.isEmpty())
{
std::map ids = memory_->getNeighborsId(id, margin, -1, false);
if(ids.size() > 0)
{
rtabmap::DetailedProgressDialog progressDialog;
progressDialog.setMaximumSteps(ids.size()+2);
progressDialog.show();
progressDialog.appendText("Graph generation...");
std::map poses, optimizedPoses;
std::multimap edgeConstraints;
memory_->getMetricConstraints(uKeys(ids), poses, edgeConstraints, true);
progressDialog.appendText("Graph generation... done!");
progressDialog.incrementStep();
progressDialog.appendText("Graph optimization...");
rtabmap::Transform mapCorrection;
rtabmap::util3d::optimizeTOROGraph(poses, edgeConstraints, optimizedPoses, mapCorrection, 100, true);
progressDialog.appendText("Graph optimization... done!");
progressDialog.incrementStep();
for(std::map::iterator iter = ids.begin(); iter!=ids.end(); ++iter)
{
rtabmap::Transform pose = uValue(optimizedPoses, iter->first, rtabmap::Transform());
if(!pose.isNull())
{
std::vector image, depth, depth2d;
float depthConstant;
rtabmap::Transform localTransform;
memory_->getImageDepth(iter->first, image, depth, depth2d, depthConstant, localTransform);
pcl::PointCloud::Ptr cloud;
cv::Mat imageMat = rtabmap::util3d::uncompressImage(image);
cv::Mat depthMat = rtabmap::util3d::uncompressImage(depth);
cloud = rtabmap::util3d::cloudFromDepthRGB(
imageMat,
depthMat,
depthMat.cols/2, depthMat.rows/2,
1.0f/depthConstant, 1.0f/depthConstant,
decimation);
if(maxDepth)
{
cloud = rtabmap::util3d::passThrough(cloud, "z", 0, maxDepth);
}
cloud = rtabmap::util3d::transformPointCloud(cloud, pose*localTransform);
std::string name = uFormat("%s/node%d.pcd", path.toStdString().c_str(), iter->first);
pcl::io::savePCDFile(name, *cloud);
UINFO("Saved %s (%d points)", name.c_str(), cloud->size());
progressDialog.appendText(QString("Saved %1 (%2 points)").arg(name.c_str()).arg(cloud->size()));
progressDialog.incrementStep();
QApplication::processEvents();
}
}
progressDialog.setValue(progressDialog.maximumSteps());
QMessageBox::information(this, tr("Finished"), tr("%1 clouds generated to %2.").arg(ids.size()).arg(path));
}
else
{
QMessageBox::critical(this, tr("Error"), tr("No neighbors found for node %1.").arg(id));
}
}
}
}
}
}
}
void DatabaseViewer::sliderAValueChanged(int value)
{
this->update(value,
ui_->label_indexA,
ui_->label_parentsA,
ui_->label_childrenA,
ui_->graphicsView_A,
ui_->label_idA);
}
void DatabaseViewer::sliderBValueChanged(int value)
{
this->update(value,
ui_->label_indexB,
ui_->label_parentsB,
ui_->label_childrenB,
ui_->graphicsView_B,
ui_->label_idB);
}
void DatabaseViewer::update(int value,
QLabel * labelIndex,
QLabel * labelParents,
QLabel * labelChildren,
rtabmap::ImageView * view,
QLabel * labelId)
{
UTimer timer;
labelIndex->setText(QString::number(value));
labelParents->clear();
labelChildren->clear();
if(value >= 0 && value < ids_.size())
{
view->clear();
int id = ids_.at(value);
labelId->setText(QString::number(id));
if(id>0)
{
//image
QImage img;
QImage imgDepth;
if(memory_)
{
std::vector image, depth, depth2d;
float depthConstant;
rtabmap::Transform localTransform;
memory_->getImageDepth(id, image, depth, depth2d, depthConstant, localTransform);
cv::Mat imageMat = rtabmap::util3d::uncompressImage(image);
cv::Mat depthMat = rtabmap::util3d::uncompressImage(depth);
if(!image.empty())
{
img = uCvMat2QImage(imageMat);
}
if(!depth.empty())
{
imgDepth = uCvMat2QImage(depthMat);
}
}
if(memory_)
{
std::multimap words = memory_->getWords(id);
if(words.size())
{
view->setFeatures(words);
}
}
if(!img.isNull())
{
view->setImage(img);
}
else
{
ULOGGER_DEBUG("Image is empty");
}
if(!imgDepth.isNull())
{
view->setImageDepth(imgDepth);
}
else
{
ULOGGER_DEBUG("Image depth is empty");
}
view->fitInView(view->sceneRect(), Qt::KeepAspectRatio);
// loops
std::map parents;
std::map children;
memory_->getLoopClosureIds(id, parents, children, true);
if(parents.size())
{
QString str;
for(std::map::iterator iter=parents.begin(); iter!=parents.end(); ++iter)
{
str.append(QString("%1 ").arg(iter->first));
}
labelParents->setText(str);
}
if(children.size())
{
QString str;
for(std::map::iterator iter=children.begin(); iter!=children.end(); ++iter)
{
str.append(QString("%1 ").arg(iter->first));
}
labelChildren->setText(str);
}
}
labelId->setText(QString::number(id));
view->fitInView(view->scene()->itemsBoundingRect(), Qt::KeepAspectRatio);
}
else
{
ULOGGER_ERROR("Slider index out of range ?");
}
}
void DatabaseViewer::sliderAMoved(int value)
{
ui_->label_indexA->setText(QString::number(value));
if(value>=0 && value < ids_.size())
{
ui_->label_idA->setText(QString::number(ids_.at(value)));
}
else
{
ULOGGER_ERROR("Slider index out of range ?");
}
}
void DatabaseViewer::sliderBMoved(int value)
{
ui_->label_indexB->setText(QString::number(value));
if(value>=0 && value < ids_.size())
{
ui_->label_idB->setText(QString::number(ids_.at(value)));
}
else
{
ULOGGER_ERROR("Slider index out of range ?");
}
}
void DatabaseViewer::sliderNeighborValueChanged(int value)
{
this->updateConstraintView(neighborLinks_.at(value));
}
void DatabaseViewer::sliderLoopValueChanged(int value)
{
this->updateConstraintView(loopLinks_.at(value));
}
void DatabaseViewer::updateConstraintView(const rtabmap::Link & link)
{
rtabmap::Transform t = link.transform();
ui_->label_constraint->clear();
if(!t.isNull() && memory_)
{
ui_->label_constraint->setText(t.prettyPrint().c_str());
ui_->horizontalSlider_A->setValue(idToIndex_.value(link.from()));
ui_->horizontalSlider_B->setValue(idToIndex_.value(link.to()));
float depthConstantA, depthConstantB;
rtabmap::Transform localTransformA, localTransformB;
std::vector imageBytesA, depthBytesA, depth2dBytesA;
memory_->getImageDepth(link.from(), imageBytesA, depthBytesA, depth2dBytesA, depthConstantA, localTransformA);
cv::Mat imageA = rtabmap::util3d::uncompressImage(imageBytesA);
cv::Mat depthA = rtabmap::util3d::uncompressImage(depthBytesA);
cv::Mat depth2dA = rtabmap::util3d::uncompressData(depth2dBytesA);
std::vector imageBytesB, depthBytesB, depth2dBytesB;
memory_->getImageDepth(link.to(), imageBytesB, depthBytesB, depth2dBytesB, depthConstantB, localTransformB);
cv::Mat imageB = rtabmap::util3d::uncompressImage(imageBytesB);
cv::Mat depthB = rtabmap::util3d::uncompressImage(depthBytesB);
cv::Mat depth2dB = rtabmap::util3d::uncompressData(depth2dBytesB);
//cloud 3d
pcl::PointCloud::Ptr cloudA;
cloudA = rtabmap::util3d::cloudFromDepthRGB(
imageA,
depthA,
depthA.cols/2,
depthA.rows/2,
1.0f/depthConstantA,
1.0f/depthConstantA,
1);
cloudA = rtabmap::util3d::removeNaNFromPointCloud(cloudA);
cloudA = rtabmap::util3d::transformPointCloud(cloudA, localTransformA);
pcl::PointCloud::Ptr cloudB;
cloudB = rtabmap::util3d::cloudFromDepthRGB(
imageB,
depthB,
depthB.cols/2,
depthB.rows/2,
1.0f/depthConstantB,
1.0f/depthConstantB,
1);
cloudB = rtabmap::util3d::removeNaNFromPointCloud(cloudB);
cloudB = rtabmap::util3d::transformPointCloud(cloudB, t*localTransformB);
//cloud 2d
pcl::PointCloud::Ptr scanA, scanB;
scanA = rtabmap::util3d::depth2DToPointCloud(depth2dA);
scanB = rtabmap::util3d::depth2DToPointCloud(depth2dB);
scanB = rtabmap::util3d::transformPointCloud(scanB, t);
if(cloudA->size())
{
ui_->constraintsViewer->addOrUpdateCloud("cloud0", cloudA);
}
if(cloudB->size())
{
ui_->constraintsViewer->addOrUpdateCloud("cloud1", cloudB);
}
if(scanA->size())
{
ui_->constraintsViewer->addOrUpdateCloud("scan0", scanA);
}
if(scanB->size())
{
ui_->constraintsViewer->addOrUpdateCloud("scan1", scanB);
}
}
ui_->constraintsViewer->render();
}
void DatabaseViewer::sliderIterationsValueChanged(int value)
{
if(memory_ && value >=0 && value < (int)graphes_.size())
{
if(scans_.size() == 0)
{
//update scans
UINFO("Update scans list...");
for(int i=0; i imageBytesA, depthBytesA, depth2dBytesA;
float depthConstantA;
rtabmap::Transform localTransformA;
memory_->getImageDepth(ids_.at(i), imageBytesA, depthBytesA, depth2dBytesA, depthConstantA, localTransformA);
if(depth2dBytesA.size())
{
scans_.insert(std::make_pair(ids_.at(i), depth2dBytesA));
}
}
UINFO("Update scans list... done");
}
std::map & graph = uValueAt(graphes_, value);
ui_->graphViewer->updateGraph(graph, links_, scans_);
ui_->label_iterations->setNum(value);
//compute total length (neighbor links)
float length = 0.0f;
for(std::multimap::const_iterator iter=links_.begin(); iter!=links_.end(); ++iter)
{
std::map::const_iterator jterA = graph.find(iter->first);
std::map::const_iterator jterB = graph.find(iter->second.to());
if(jterA != graph.end() && jterB != graph.end())
{
const rtabmap::Transform & poseA = jterA->second;
const rtabmap::Transform & poseB = jterB->second;
if(iter->second.type() == rtabmap::Link::kNeighbor)
{
Eigen::Vector3f vA, vB;
poseA.getTranslation(vA[0], vA[1], vA[2]);
poseB.getTranslation(vB[0], vB[1], vB[2]);
length += (vB - vA).norm();
}
}
}
ui_->label_pathLength->setNum(length);
}
}
void DatabaseViewer::updateGraphView()
{
if(ui_->dockWidget_graphView->isVisible() && poses_.size())
{
if(!uContains(poses_, ui_->spinBox_optimizationsFrom->value()))
{
QMessageBox::warning(this, tr(""), tr("Graph optimization from id (%1) for which node is not linked to graph.\n Minimum=%2, Maximum=%3")
.arg(ui_->spinBox_optimizationsFrom->value())
.arg(poses_.begin()->first)
.arg(poses_.rbegin()->first));
return;
}
graphes_.clear();
std::map finalPoses;
graphes_.push_back(poses_);
ui_->actionGenerate_TORO_graph_graph->setEnabled(true);
std::map ids = memory_->getNeighborsId(ui_->spinBox_optimizationsFrom->value(), 0, -1, true);
// Modify IDs using the margin from the current signature (TORO root will be the last signature)
int m = 0;
int toroId = 1;
std::map rtabmapToToro; //
std::map toroToRtabmap; //
while(ids.size())
{
for(std::map::iterator iter = ids.begin(); iter!=ids.end();)
{
if(m == iter->second)
{
rtabmapToToro.insert(std::make_pair(iter->first, toroId));
toroToRtabmap.insert(std::make_pair(toroId, iter->first));
++toroId;
ids.erase(iter++);
}
else
{
++iter;
}
}
++m;
}
std::map posesToro;
std::multimap edgeConstraintsToro;
for(std::map::iterator iter = poses_.begin(); iter!=poses_.end(); ++iter)
{
posesToro.insert(std::make_pair(rtabmapToToro.at(iter->first), iter->second));
}
for(std::multimap::iterator iter = links_.begin();
iter!=links_.end();
++iter)
{
edgeConstraintsToro.insert(std::make_pair(rtabmapToToro.at(iter->first), rtabmap::Link(rtabmapToToro.at(iter->first), rtabmapToToro.at(iter->second.to()), iter->second.transform(), iter->second.type())));
}
std::map optimizedPosesToro;
rtabmap::Transform mapCorrectionToro;
std::list > graphesToro;
// Optimize!
rtabmap::util3d::optimizeTOROGraph(posesToro, edgeConstraintsToro, optimizedPosesToro, mapCorrectionToro, ui_->spinBox_iterations->value(), ui_->checkBox_initGuess->isChecked(), &graphesToro);
for(std::list >::iterator iter = graphesToro.begin(); iter!=graphesToro.end(); ++iter)
{
std::map tmp;
for(std::map::iterator jter=iter->begin(); jter!=iter->end(); ++jter)
{
tmp.insert(std::make_pair(toroToRtabmap.at(jter->first), jter->second));
}
graphes_.push_back(tmp);
}
for(std::map::iterator iter=optimizedPosesToro.begin(); iter!=optimizedPosesToro.end(); ++iter)
{
finalPoses.insert(std::make_pair(toroToRtabmap.at(iter->first), iter->second));
}
graphes_.push_back(finalPoses);
}
if(graphes_.size())
{
ui_->horizontalSlider_iterations->setMaximum(graphes_.size()-1);
ui_->horizontalSlider_iterations->setValue(graphes_.size()-1);
ui_->dockWidget_graphView->setEnabled(true);
sliderIterationsValueChanged(graphes_.size()-1);
}
else
{
ui_->dockWidget_graphView->setEnabled(false);
}
}