From d4248385f0d422f2b121156f42c39609479d2472 Mon Sep 17 00:00:00 2001 From: matlabbe Date: Wed, 11 Oct 2017 19:36:00 -0400 Subject: [PATCH] Added rtabmap::databaseRecovery() function, added rtabmap-recovery tool, added "Database recovery" menu action in DbViewer --- corelib/include/rtabmap/core/ProgressState.h | 3 + corelib/include/rtabmap/core/Recovery.h | 56 ++++ corelib/src/CMakeLists.txt | 2 + corelib/src/Recovery.cpp | 208 +++++++++++++++ .../pcl18/surface/impl/texture_mapping.hpp | 2 +- corelib/src/pcl18/surface/texture_mapping.h | 2 +- guilib/include/rtabmap/gui/DatabaseViewer.h | 2 + guilib/include/rtabmap/gui/MainWindow.h | 1 + guilib/src/CMakeLists.txt | 1 + guilib/src/DatabaseViewer.cpp | 247 +++++++++++++----- guilib/src/MainWindow.cpp | 54 +++- guilib/src/RecoveryState.h | 103 ++++++++ guilib/src/StatsToolBox.cpp | 1 + guilib/src/ui/DatabaseViewer.ui | 138 ++++++++-- tools/CMakeLists.txt | 1 + tools/Recovery/CMakeLists.txt | 38 +++ tools/Recovery/main.cpp | 86 ++++++ 17 files changed, 848 insertions(+), 97 deletions(-) create mode 100644 corelib/include/rtabmap/core/Recovery.h create mode 100644 corelib/src/Recovery.cpp create mode 100644 guilib/src/RecoveryState.h create mode 100644 tools/Recovery/CMakeLists.txt create mode 100644 tools/Recovery/main.cpp diff --git a/corelib/include/rtabmap/core/ProgressState.h b/corelib/include/rtabmap/core/ProgressState.h index b08ba705..8133f4a6 100644 --- a/corelib/include/rtabmap/core/ProgressState.h +++ b/corelib/include/rtabmap/core/ProgressState.h @@ -30,6 +30,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include +namespace rtabmap { + class ProgressState { public: @@ -55,5 +57,6 @@ private: bool canceled_; }; +} #endif /* CORELIB_INCLUDE_RTABMAP_CORE_PROGRESSSTATE_H_ */ diff --git a/corelib/include/rtabmap/core/Recovery.h b/corelib/include/rtabmap/core/Recovery.h new file mode 100644 index 00000000..6300332d --- /dev/null +++ b/corelib/include/rtabmap/core/Recovery.h @@ -0,0 +1,56 @@ +/* +Copyright (c) 2010-2017, Mathieu Labbe - IntRoLab - Universite de Sherbrooke +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Universite de Sherbrooke nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef RECOVERY_H_ +#define RECOVERY_H_ + +#include "rtabmap/core/RtabmapExp.h" // DLL export/import defines + +#include + +namespace rtabmap { + +class ProgressState; + +/** + * Return true on success. The database is + * renamed to "*.backup.db" before recovering. + * @param corruptedDatabase database to recover + * @param keepCorruptedDatabase if false and on recovery success, the backup database is removed + * @param errorMsg error message if the function returns false + * @param progressState A ProgressState object used to get status of the recovery process + */ +bool RTABMAP_EXP databaseRecovery( + const std::string & corruptedDatabase, + bool keepCorruptedDatabase = true, + std::string * errorMsg = 0, + ProgressState * progressState = 0); + +} + + +#endif /* RECOVERY_H_ */ diff --git a/corelib/src/CMakeLists.txt b/corelib/src/CMakeLists.txt index 65badfa7..73ada3a8 100644 --- a/corelib/src/CMakeLists.txt +++ b/corelib/src/CMakeLists.txt @@ -11,6 +11,8 @@ SET(SRC_FILES DBDriverSqlite3.cpp DBReader.cpp + Recovery.cpp + Camera.cpp CameraThread.cpp CameraRGB.cpp diff --git a/corelib/src/Recovery.cpp b/corelib/src/Recovery.cpp new file mode 100644 index 00000000..d8357378 --- /dev/null +++ b/corelib/src/Recovery.cpp @@ -0,0 +1,208 @@ +/* +Copyright (c) 2010-2017, Mathieu Labbe - IntRoLab - Universite de Sherbrooke +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Universite de Sherbrooke nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace rtabmap { + +bool databaseRecovery( + const std::string & corruptedDatabase, + bool keepCorruptedDatabase, + std::string * errorMsg, + ProgressState * progressState) +{ + UDEBUG("Recovering \"%s\"", corruptedDatabase.c_str()); + + std::string databasePath = uReplaceChar(corruptedDatabase, '~', UDirectory::homeDir()); + if(!UFile::exists(databasePath)) + { + if(errorMsg) + *errorMsg = uFormat("File \"%s\" doesn't exist!", databasePath.c_str()); + return false; + } + + std::string backupPath; + if(UFile::getExtension(databasePath).compare("db") != 0) + { + if(errorMsg) + *errorMsg = uFormat("File \"%s\" is not a database (*.db)!", databasePath.c_str()); + return false; + } + std::list strList = uSplit(databasePath, '.'); + strList.pop_back(); + backupPath = uJoin(strList, ".") + ".backup.db"; + if(UFile::exists(backupPath)) + { + if(errorMsg) + *errorMsg = uFormat("Backup file \"%s\" already exists!", backupPath.c_str()); + return false; + } + + DBDriver * dbDriver = DBDriver::create(); + if(!dbDriver->openConnection(databasePath, false)) + { + if(errorMsg) + *errorMsg = uFormat("Failed opening database!"); + delete dbDriver; + return false; + } + + ParametersMap parameters = dbDriver->getLastParameters(); + if(parameters.empty()) + { + if(errorMsg) + *errorMsg = uFormat("Failed getting parameters from database, recovery cannot be done."); + dbDriver->closeConnection(false); + delete dbDriver; + return false; + } + std::set ids; + dbDriver->getAllNodeIds(ids); + if(ids.empty()) + { + if(errorMsg) + *errorMsg = uFormat("Input database doesn't have any nodes saved in it."); + dbDriver->closeConnection(false); + delete dbDriver; + return false; + } + if(progressState) + progressState->callback(uFormat("Found %d nodes to recover.", (int)ids.size())); + + //Detect if the database is corrupted + std::multimap links; + dbDriver->getAllLinks(links, true); + bool corrupted = false; + for(std::multimap::iterator iter=links.begin(); iter!=links.end(); ++iter) + { + if(iter->second.type() == Link::kNeighbor && + graph::findLink(links, iter->second.to(), iter->second.from(), false) == links.end()) + { + corrupted = true; + break; + } + } + + if(progressState) + { + if(corrupted) + progressState->callback("Database is indeed corrupted, found one or more neighbor links missing."); + else + progressState->callback("Database doesn't seem to be corrupted, still recovering it."); + } + + dbDriver->closeConnection(false); + delete dbDriver; + + if(progressState) + progressState->callback(uFormat("Renaming \"%s\" to \"%s\"...", UFile::getName(databasePath).c_str(), UFile::getName(backupPath).c_str())); + if(UFile::rename(databasePath, backupPath) != 0) + { + if(errorMsg) + *errorMsg = uFormat("Failed renaming database file from \"%s\" to \"%s\". Is it opened by another app?", UFile::getName(databasePath).c_str(), UFile::getName(backupPath).c_str()); + return false; + } + Rtabmap rtabmap; + rtabmap.init(parameters, databasePath); + + bool odometryIgnored = false; + Parameters::parse(parameters, Parameters::kRGBDEnabled(), odometryIgnored); + DBReader dbReader(backupPath, 0, !odometryIgnored); + dbReader.init(); + + CameraInfo info; + SensorData data = dbReader.takeImage(&info); + int processed = 0; + if(progressState) + progressState->callback(uFormat("Recovering data of \"%s\"...", backupPath.c_str())); + while(data.isValid() && (progressState==0 || !progressState->isCanceled())) + { + std::string status; + if(!odometryIgnored && info.odomPose.isNull()) + { + status = uFormat("Skipping node %d as it doesn't have odometry pose set.", data.id()); + } + else + { + if(!rtabmap.process(data, info.odomPose, info.odomCovariance)) + { + status = uFormat("Failed processing node %d.", data.id()); + } + } + if(status.empty()) + { + if(progressState) + progressState->callback(status); + } + + data = dbReader.takeImage(&info); + + if(progressState) + progressState->callback(uFormat("Processed %d/%d nodes...", ++processed, (int)ids.size())); + } + + if(progressState) + { + if(progressState->isCanceled()) + { + rtabmap.close(false); + if(errorMsg) + *errorMsg = "Recovery canceled"; + + // put back the file as before + UFile::erase(databasePath); + UFile::rename(backupPath, databasePath); + return false; + } + } + + if(progressState) + progressState->callback(uFormat("Closing database \"%s\"...", databasePath.c_str())); + rtabmap.close(true); + if(progressState) + progressState->callback(uFormat("Closing database \"%s\"... done!", databasePath.c_str())); + + if(!keepCorruptedDatabase) + { + UFile::erase(backupPath); + } + + return true; +} + +} + + + + diff --git a/corelib/src/pcl18/surface/impl/texture_mapping.hpp b/corelib/src/pcl18/surface/impl/texture_mapping.hpp index 785988fd..a30f5766 100644 --- a/corelib/src/pcl18/surface/impl/texture_mapping.hpp +++ b/corelib/src/pcl18/surface/impl/texture_mapping.hpp @@ -1053,7 +1053,7 @@ template bool pcl::TextureMapping::textureMeshwithMultipleCameras2 ( pcl::TextureMesh &mesh, const pcl::texture_mapping::CameraVector &cameras, - const ProgressState * state, + const rtabmap::ProgressState * state, std::vector > * vertexToPixels) { diff --git a/corelib/src/pcl18/surface/texture_mapping.h b/corelib/src/pcl18/surface/texture_mapping.h index bf6f943a..7cef3dc6 100644 --- a/corelib/src/pcl18/surface/texture_mapping.h +++ b/corelib/src/pcl18/surface/texture_mapping.h @@ -365,7 +365,7 @@ namespace pcl bool textureMeshwithMultipleCameras2 (pcl::TextureMesh &mesh, const pcl::texture_mapping::CameraVector &cameras, - const ProgressState * callback = 0, + const rtabmap::ProgressState * callback = 0, std::vector > * vertexToPixels = 0); protected: diff --git a/guilib/include/rtabmap/gui/DatabaseViewer.h b/guilib/include/rtabmap/gui/DatabaseViewer.h index f3988158..47feea57 100644 --- a/guilib/include/rtabmap/gui/DatabaseViewer.h +++ b/guilib/include/rtabmap/gui/DatabaseViewer.h @@ -85,6 +85,8 @@ private slots: void restoreDefaultSettings(); void configModified(); void openDatabase(); + bool closeDatabase(); + void recoverDatabase(); void updateStatistics(); void editDepthImage(); void generateGraph(); diff --git a/guilib/include/rtabmap/gui/MainWindow.h b/guilib/include/rtabmap/gui/MainWindow.h index be2a78d5..2276d406 100644 --- a/guilib/include/rtabmap/gui/MainWindow.h +++ b/guilib/include/rtabmap/gui/MainWindow.h @@ -277,6 +277,7 @@ private: double _firstStamp; bool _processingStatistics; bool _processingDownloadedMap; + bool _recovering; bool _odometryReceived; QString _newDatabasePath; QString _newDatabasePathOutput; diff --git a/guilib/src/CMakeLists.txt b/guilib/src/CMakeLists.txt index 693bb45f..a8128531 100644 --- a/guilib/src/CMakeLists.txt +++ b/guilib/src/CMakeLists.txt @@ -30,6 +30,7 @@ SET(headers_ui ./DepthCalibrationDialog.h ./3rdParty/QMultiComboBox.h ./TexturingState.h + ./RecoveryState.h ./EditDepthArea.h ) diff --git a/guilib/src/DatabaseViewer.cpp b/guilib/src/DatabaseViewer.cpp index 9ab2354c..9abaf63b 100644 --- a/guilib/src/DatabaseViewer.cpp +++ b/guilib/src/DatabaseViewer.cpp @@ -69,6 +69,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "rtabmap/core/RegistrationIcp.h" #include "rtabmap/core/OccupancyGrid.h" #include "rtabmap/core/GeodeticCoords.h" +#include "rtabmap/core/Recovery.h" #include "rtabmap/gui/DataRecorder.h" #include "ExportCloudsDialog.h" #include "EditDepthArea.h" @@ -77,6 +78,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "ExportDialog.h" #include "rtabmap/gui/ProgressDialog.h" #include "ParametersToolBox.h" +#include "RecoveryState.h" #include #include #include @@ -216,10 +218,15 @@ DatabaseViewer::DatabaseViewer(const QString & ini, QWidget * parent) : connect(ui_->actionQuit, SIGNAL(triggered()), this, SLOT(close())); + ui_->actionOpen_database->setEnabled(true); + ui_->actionClose_database->setEnabled(false); + // connect actions with custom slots ui_->actionSave_config->setShortcut(QKeySequence::Save); connect(ui_->actionSave_config, SIGNAL(triggered()), this, SLOT(writeSettings())); connect(ui_->actionOpen_database, SIGNAL(triggered()), this, SLOT(openDatabase())); + connect(ui_->actionClose_database, SIGNAL(triggered()), this, SLOT(closeDatabase())); + connect(ui_->actionDatabase_recovery, SIGNAL(triggered()), this, SLOT(recoverDatabase())); connect(ui_->actionExport, SIGNAL(triggered()), this, SLOT(exportDatabase())); connect(ui_->actionExtract_images, SIGNAL(triggered()), this, SLOT(extractImages())); connect(ui_->actionEdit_depth_image, SIGNAL(triggered()), this, SLOT(editDepthImage())); @@ -692,51 +699,23 @@ bool DatabaseViewer::openDatabase(const QString & path) UDEBUG("Open database \"%s\"", path.toStdString().c_str()); if(QFile::exists(path)) { - if(dbDriver_) - { - delete dbDriver_; - dbDriver_ = 0; - ids_.clear(); - idToIndex_.clear(); - neighborLinks_.clear(); - loopLinks_.clear(); - graphes_.clear(); - graphLinks_.clear(); - odomPoses_.clear(); - groundTruthPoses_.clear(); - gpsPoses_.clear(); - gpsValues_.clear(); - mapIds_.clear(); - links_.clear(); - linksAdded_.clear(); - linksRefined_.clear(); - linksRemoved_.clear(); - localMaps_.clear(); - localMapsInfo_.clear(); - generatedLocalMaps_.clear(); - generatedLocalMapsInfo_.clear(); - ui_->graphViewer->clearAll(); - occupancyGridViewer_->clear(); - ui_->menuExport_poses->setEnabled(false); - ui_->menuExport_GPS->setEnabled(false); - ui_->actionPoses_KML->setEnabled(false); - ui_->checkBox_showOptimized->setEnabled(false); - ui_->toolBox_statistics->clear(); - databaseFileName_.clear(); - ui_->checkBox_alignPosesWithGroundTruth->setVisible(false); - ui_->label_alignPosesWithGroundTruth->setVisible(false); - } - std::string driverType = "sqlite3"; dbDriver_ = DBDriver::create(); if(!dbDriver_->openConnection(path.toStdString())) { + ui_->actionClose_database->setEnabled(false); + ui_->actionOpen_database->setEnabled(true); + delete dbDriver_; + dbDriver_ = 0; QMessageBox::warning(this, "Database error", tr("Can't open database \"%1\"").arg(path)); } else { + ui_->actionClose_database->setEnabled(true); + ui_->actionOpen_database->setEnabled(false); + pathDatabase_ = UDirectory::getDir(path.toStdString()).c_str(); databaseFileName_ = UFile::getName(path.toStdString()); ui_->graphViewer->setWorkingDirectory(pathDatabase_); @@ -810,33 +789,8 @@ bool DatabaseViewer::openDatabase(const QString & path) return false; } -void DatabaseViewer::closeEvent(QCloseEvent* event) +bool DatabaseViewer::closeDatabase() { - //write settings before quit? - bool save = false; - if(this->isWindowModified()) - { - QMessageBox::Button b=QMessageBox::question(this, - tr("Database Viewer"), - tr("There are unsaved changed settings. Save them?"), - QMessageBox::Save | QMessageBox::Cancel | QMessageBox::Discard); - if(b == QMessageBox::Save) - { - save = true; - } - else if(b != QMessageBox::Discard) - { - event->ignore(); - return; - } - } - - if(save) - { - writeSettings(); - } - - event->accept(); if(dbDriver_) { if(linksAdded_.size() || linksRefined_.size() || linksRemoved_.size()) @@ -889,12 +843,11 @@ void DatabaseViewer::closeEvent(QCloseEvent* event) if(button != QMessageBox::Yes && button != QMessageBox::No) { - event->ignore(); + return false; } } - if(event->isAccepted() && - generatedLocalMaps_.size() && + if( generatedLocalMaps_.size() && uStrNumCmp(dbDriver_->getDatabaseVersion(), "0.11.10") >= 0) { QMessageBox::StandardButton button = QMessageBox::question(this, @@ -929,9 +882,161 @@ void DatabaseViewer::closeEvent(QCloseEvent* event) if(button != QMessageBox::Yes && button != QMessageBox::No) { - event->ignore(); + return false; } } + + delete dbDriver_; + dbDriver_ = 0; + ids_.clear(); + idToIndex_.clear(); + neighborLinks_.clear(); + loopLinks_.clear(); + graphes_.clear(); + graphLinks_.clear(); + odomPoses_.clear(); + groundTruthPoses_.clear(); + gpsPoses_.clear(); + gpsValues_.clear(); + mapIds_.clear(); + links_.clear(); + linksAdded_.clear(); + linksRefined_.clear(); + linksRemoved_.clear(); + localMaps_.clear(); + localMapsInfo_.clear(); + generatedLocalMaps_.clear(); + generatedLocalMapsInfo_.clear(); + ui_->graphViewer->clearAll(); + occupancyGridViewer_->clear(); + ui_->menuExport_poses->setEnabled(false); + ui_->menuExport_GPS->setEnabled(false); + ui_->actionPoses_KML->setEnabled(false); + ui_->checkBox_showOptimized->setEnabled(false); + ui_->toolBox_statistics->clear(); + databaseFileName_.clear(); + ui_->checkBox_alignPosesWithGroundTruth->setVisible(false); + ui_->label_alignPosesWithGroundTruth->setVisible(false); + ui_->label_optimizeFrom->setText(tr("Optimize from")); + ui_->textEdit_info->clear(); + + ui_->pushButton_refine->setEnabled(false); + ui_->pushButton_add->setEnabled(false); + ui_->pushButton_reset->setEnabled(false); + ui_->pushButton_reject->setEnabled(false); + + ui_->horizontalSlider_loops->setEnabled(false); + ui_->horizontalSlider_loops->setMaximum(0); + ui_->horizontalSlider_iterations->setEnabled(false); + ui_->horizontalSlider_iterations->setMaximum(0); + ui_->horizontalSlider_neighbors->setEnabled(false); + ui_->horizontalSlider_neighbors->setMaximum(0); + ui_->label_constraint->clear(); + ui_->label_constraint_opt->clear(); + ui_->label_variance->clear(); + + ui_->horizontalSlider_A->setEnabled(false); + ui_->horizontalSlider_A->setMaximum(0); + ui_->horizontalSlider_B->setEnabled(false); + ui_->horizontalSlider_B->setMaximum(0); + ui_->label_idA->setText("NaN"); + ui_->label_idB->setText("NaN"); + sliderAValueChanged(0); + sliderBValueChanged(0); + + constraintsViewer_->clear(); + constraintsViewer_->update(); + + cloudViewer_->clear(); + cloudViewer_->update(); + + occupancyGridViewer_->clear(); + occupancyGridViewer_->update(); + + ui_->graphViewer->clearAll(); + ui_->label_loopClosures->clear(); + ui_->label_timeOptimization->clear(); + ui_->label_pathLength->clear(); + ui_->label_poses->clear(); + ui_->spinBox_optimizationsFrom->setEnabled(false); + + ui_->graphicsView_A->clear(); + ui_->graphicsView_B->clear(); + + ui_->graphicsView_stereo->clear(); + stereoViewer_->clear(); + stereoViewer_->update(); + + ui_->toolBox_statistics->clear(); + } + + ui_->actionClose_database->setEnabled(dbDriver_ != 0); + ui_->actionOpen_database->setEnabled(dbDriver_ == 0); + + return dbDriver_ == 0; +} + + +void DatabaseViewer::recoverDatabase() +{ + QString path = QFileDialog::getOpenFileName(this, tr("Select file"), pathDatabase_, tr("Databases (*.db)")); + if(!path.isEmpty()) + { + if(path.compare(pathDatabase_+QDir::separator()+databaseFileName_.c_str()) == 0) + { + QMessageBox::information(this, "Database recovery", tr("The selected database is already opened, close it first.")); + return; + } + std::string errorMsg; + rtabmap::ProgressDialog * progressDialog = new rtabmap::ProgressDialog(this); + progressDialog->setAttribute(Qt::WA_DeleteOnClose); + progressDialog->setMaximumSteps(100); + progressDialog->show(); + progressDialog->setCancelButtonVisible(true); + RecoveryState state(progressDialog); + if(databaseRecovery(path.toStdString(), false, &errorMsg, &state)) + { + QMessageBox::information(this, "Database recovery", tr("Database \"%1\" recovered! Try opening it again.").arg(path)); + } + else + { + QMessageBox::warning(this, "Database recovery", tr("Database recovery failed: \"%1\".").arg(errorMsg.c_str())); + } + progressDialog->setValue(progressDialog->maximumSteps()); + } +} + +void DatabaseViewer::closeEvent(QCloseEvent* event) +{ + //write settings before quit? + bool save = false; + if(this->isWindowModified()) + { + QMessageBox::Button b=QMessageBox::question(this, + tr("Database Viewer"), + tr("There are unsaved changed settings. Save them?"), + QMessageBox::Save | QMessageBox::Cancel | QMessageBox::Discard); + if(b == QMessageBox::Save) + { + save = true; + } + else if(b != QMessageBox::Discard) + { + event->ignore(); + return; + } + } + + if(save) + { + writeSettings(); + } + + event->accept(); + + if(!closeDatabase()) + { + event->ignore(); } if(event->isAccepted()) @@ -3139,7 +3244,7 @@ void DatabaseViewer::update(int value, labelMapId->setText(QString::number(mapId)); } } - else + else if(value != 0) { ULOGGER_ERROR("Slider index out of range ?"); } @@ -3537,12 +3642,18 @@ void DatabaseViewer::update3dView() void DatabaseViewer::sliderNeighborValueChanged(int value) { - this->updateConstraintView(neighborLinks_.at(value)); + if(value < neighborLinks_.size()) + { + this->updateConstraintView(neighborLinks_.at(value)); + } } void DatabaseViewer::sliderLoopValueChanged(int value) { - this->updateConstraintView(loopLinks_.at(value)); + if(value < loopLinks_.size()) + { + this->updateConstraintView(loopLinks_.at(value)); + } } // only called when ui_->checkBox_showOptimized state changed diff --git a/guilib/src/MainWindow.cpp b/guilib/src/MainWindow.cpp index 11462857..58f63718 100644 --- a/guilib/src/MainWindow.cpp +++ b/guilib/src/MainWindow.cpp @@ -42,6 +42,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "rtabmap/core/RegistrationVis.h" #include "rtabmap/core/OccupancyGrid.h" #include "rtabmap/core/GainCompensator.h" +#include "rtabmap/core/Recovery.h" #include "rtabmap/gui/ImageView.h" #include "rtabmap/gui/KeypointItem.h" @@ -66,6 +67,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "AboutDialog.h" #include "PostProcessingDialog.h" #include "DepthCalibrationDialog.h" +#include "RecoveryState.h" #include #include @@ -143,6 +145,7 @@ MainWindow::MainWindow(PreferencesDialog * prefDialog, QWidget * parent) : _firstStamp(0.0f), _processingStatistics(false), _processingDownloadedMap(false), + _recovering(false), _odometryReceived(false), _newDatabasePath(""), _newDatabasePathOutput(""), @@ -776,8 +779,11 @@ bool MainWindow::handleEvent(UEvent* anEvent) } else if(anEvent->getClassName().compare("RtabmapEventInit") == 0) { - RtabmapEventInit * rtabmapEventInit = (RtabmapEventInit*)anEvent; - emit rtabmapEventInitReceived((int)rtabmapEventInit->getStatus(), rtabmapEventInit->getInfo().c_str()); + if(!_recovering) + { + RtabmapEventInit * rtabmapEventInit = (RtabmapEventInit*)anEvent; + emit rtabmapEventInitReceived((int)rtabmapEventInit->getStatus(), rtabmapEventInit->getInfo().c_str()); + } } else if(anEvent->getClassName().compare("RtabmapEvent3DMap") == 0) { @@ -4224,10 +4230,10 @@ void MainWindow::newDatabase() tr("Cannot create a new database because the temporary database \"%1\" already exists. " "There may be another instance of RTAB-Map running with the same Working Directory or " "the last time RTAB-Map was not closed correctly. " - "Do you want to continue (the database will be deleted to create the new one)?").arg(databasePath.c_str()), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); + "Do you want to recover the database (click Ignore to delete it and create a new one)?").arg(databasePath.c_str()), + QMessageBox::Yes | QMessageBox::No | QMessageBox::Ignore, QMessageBox::No); - if(r == QMessageBox::Yes) + if(r == QMessageBox::Ignore) { if(QFile::remove(databasePath.c_str())) { @@ -4239,6 +4245,44 @@ void MainWindow::newDatabase() return; } } + else if(r == QMessageBox::Yes) + { + std::string errorMsg; + rtabmap::ProgressDialog * progressDialog = new rtabmap::ProgressDialog(this); + progressDialog->setAttribute(Qt::WA_DeleteOnClose); + progressDialog->setMaximumSteps(100); + progressDialog->show(); + progressDialog->setCancelButtonVisible(true); + RecoveryState state(progressDialog); + _recovering = true; + if(databaseRecovery(databasePath, false, &errorMsg, &state)) + { + _recovering = false; + progressDialog->setValue(progressDialog->maximumSteps()); + QString newPath = QFileDialog::getSaveFileName(this, tr("Save recovered database"), _preferencesDialog->getWorkingDirectory()+QDir::separator()+QString("recovered.db"), tr("RTAB-Map database files (*.db)")); + if(newPath.isEmpty()) + { + return; + } + if(QFileInfo(newPath).suffix() == "") + { + newPath += ".db"; + } + if(QFile::exists(newPath)) + { + QFile::remove(newPath); + } + QFile::rename(databasePath.c_str(), newPath); + return; + } + else + { + _recovering = false; + progressDialog->setValue(progressDialog->maximumSteps()); + QMessageBox::warning(this, "Database recovery", tr("Database recovery failed: \"%1\".", errorMsg.c_str())); + return; + } + } else { return; diff --git a/guilib/src/RecoveryState.h b/guilib/src/RecoveryState.h new file mode 100644 index 00000000..77101445 --- /dev/null +++ b/guilib/src/RecoveryState.h @@ -0,0 +1,103 @@ +/* +Copyright (c) 2010-2017, Mathieu Labbe - IntRoLab - Universite de Sherbrooke +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Universite de Sherbrooke nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef RECOVERYSTATE_H_ +#define RECOVERYSTATE_H_ + +#include "rtabmap/gui/ProgressDialog.h" +#include "rtabmap/core/ProgressState.h" +#include "rtabmap/utilite/UStl.h" +#include "rtabmap/utilite/UConversion.h" +#include + +namespace rtabmap { + +class RecoveryState : public QObject, public ProgressState +{ + Q_OBJECT + +public: + RecoveryState(ProgressDialog * dialog): dialog_(dialog) + { + connect(dialog_, SIGNAL(canceled()), this, SLOT(cancel())); + } + virtual ~RecoveryState() {} + virtual bool callback(const std::string & msg) const + { + if(!msg.empty()) + { + QString msgQt = msg.c_str(); + if(msgQt.contains("Processed")) + { + dialog_->incrementStep(); + dialog_->appendText(msg.c_str()); + } + else if(msgQt.contains("Found")) + { + std::list strSplit = uSplitNumChar(msg); + if(strSplit.size() == 3) + { + int nodes = uStr2Int(*(++strSplit.begin())); + if(nodes > 0) + { + dialog_->setMaximumSteps(nodes); + } + } + dialog_->appendText(msg.c_str()); + } + else if(msgQt.contains("Skipping") || + msgQt.contains("Failed processing")) + { + dialog_->appendText(msg.c_str(), Qt::darkYellow); + } + else + { + dialog_->appendText(msg.c_str()); + } + } + QApplication::processEvents(); + if(!isCanceled()) + { + return ProgressState::callback(msg); + } + return false; + } + +public slots: + void cancel() + { + setCanceled(true); + } + +private: + ProgressDialog * dialog_; +}; + +} + + +#endif /* RECOVERYSTATE_H_ */ diff --git a/guilib/src/StatsToolBox.cpp b/guilib/src/StatsToolBox.cpp index 1212ba29..f2d2fc2b 100644 --- a/guilib/src/StatsToolBox.cpp +++ b/guilib/src/StatsToolBox.cpp @@ -81,6 +81,7 @@ void StatItem::clearCache() { _x.clear(); _y.clear(); + _value->clear(); } void StatItem::addValue(float y) diff --git a/guilib/src/ui/DatabaseViewer.ui b/guilib/src/ui/DatabaseViewer.ui index 88cf8157..48d8d583 100644 --- a/guilib/src/ui/DatabaseViewer.ui +++ b/guilib/src/ui/DatabaseViewer.ui @@ -21,7 +21,16 @@ 0 - + + 0 + + + 0 + + + 0 + + 0 @@ -52,8 +61,8 @@ 0 0 - 393 - 256 + 388 + 242 @@ -244,8 +253,8 @@ 0 0 - 393 - 256 + 387 + 242 @@ -425,7 +434,16 @@ - + + 12 + + + 12 + + + 12 + + 12 @@ -481,7 +499,16 @@ - + + 12 + + + 12 + + + 12 + + 12 @@ -552,7 +579,7 @@ 0 0 1547 - 22 + 25 @@ -578,6 +605,9 @@ + + + @@ -1040,7 +1070,16 @@ - + + 0 + + + 0 + + + 0 + + 0 @@ -1094,8 +1133,8 @@ 0 0 - 324 - 188 + 340 + 186 @@ -1230,8 +1269,8 @@ 0 0 - 280 - 666 + 282 + 626 @@ -1593,8 +1632,8 @@ 0 0 - 201 - 126 + 205 + 117 @@ -1693,8 +1732,8 @@ 0 0 - 186 - 496 + 185 + 487 @@ -1796,7 +1835,16 @@ 0 - + + 0 + + + 0 + + + 0 + + 0 @@ -1909,7 +1957,16 @@ 0 - + + 0 + + + 0 + + + 0 + + 0 @@ -2040,7 +2097,16 @@ - + + 0 + + + 0 + + + 0 + + 0 @@ -2058,7 +2124,16 @@ - + + 0 + + + 0 + + + 0 + + 0 @@ -2103,7 +2178,16 @@ 0 - + + 0 + + + 0 + + + 0 + + 0 @@ -2321,6 +2405,16 @@ Google Earth (*.kml) + + + Database recovery + + + + + Close database + + diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index c0db0aab..804eea61 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -7,6 +7,7 @@ ADD_SUBDIRECTORY( CameraRGBD ) ADD_SUBDIRECTORY( StereoEval ) ADD_SUBDIRECTORY( KittiDataset ) ADD_SUBDIRECTORY( RgbdDataset ) +ADD_SUBDIRECTORY( Recovery ) IF(OPENCV_NONFREE_FOUND) ADD_SUBDIRECTORY( VocabularyComparison ) diff --git a/tools/Recovery/CMakeLists.txt b/tools/Recovery/CMakeLists.txt new file mode 100644 index 00000000..df404b8f --- /dev/null +++ b/tools/Recovery/CMakeLists.txt @@ -0,0 +1,38 @@ + +SET(RTABMap_INCLUDE_DIRS + ${PROJECT_SOURCE_DIR}/utilite/include + ${PROJECT_SOURCE_DIR}/corelib/include +) +SET(RTABMap_LIBRARIES + rtabmap_core + rtabmap_utilite +) + +if(POLICY CMP0020) + cmake_policy(SET CMP0020 OLD) +endif() + +SET(INCLUDE_DIRS + ${RTABMap_INCLUDE_DIRS} + ${OpenCV_INCLUDE_DIRS} + ${PCL_INCLUDE_DIRS} +) + +SET(LIBRARIES + ${RTABMap_LIBRARIES} + ${OpenCV_LIBRARIES} + ${PCL_LIBRARIES} +) + +INCLUDE_DIRECTORIES(${INCLUDE_DIRS}) + +ADD_EXECUTABLE(recovery main.cpp) + +TARGET_LINK_LIBRARIES(recovery ${LIBRARIES}) + +SET_TARGET_PROPERTIES( recovery + PROPERTIES OUTPUT_NAME ${PROJECT_PREFIX}-recovery) + + + + diff --git a/tools/Recovery/main.cpp b/tools/Recovery/main.cpp new file mode 100644 index 00000000..aeaaf206 --- /dev/null +++ b/tools/Recovery/main.cpp @@ -0,0 +1,86 @@ +/* +Copyright (c) 2010-2016, Mathieu Labbe - IntRoLab - Universite de Sherbrooke +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Universite de Sherbrooke nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include +#include +#include + +using namespace rtabmap; + +void showUsage() +{ + printf("\nUsage:\n" + "rtabmap-recovery [-d] \"my_corrupted_map.db\"" + " Options:\n" + " -d Delete database backup on success (\"*.backup.db\").\n" + "\n"); + exit(1); +} + +class RecoveryProgressState: public ProgressState +{ + virtual bool callback(const std::string & msg) const + { + if(!msg.empty()) + printf("%s\n", msg.c_str()); + return true; + } +}; + +int main(int argc, char * argv[]) +{ + ULogger::setType(ULogger::kTypeConsole); + ULogger::setLevel(ULogger::kError); + + if(argc < 2) + { + showUsage(); + } + + bool keepBackup = true; + for(int i=1; i