mirror of
https://github.com/introlab/rtabmap.git
synced 2026-09-02 09:30:25 +08:00
6247 lines
208 KiB
C++
6247 lines
208 KiB
C++
/*
|
|
Copyright (c) 2010-2014, 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 "rtabmap/gui/MainWindow.h"
|
|
|
|
#include "ui_mainWindow.h"
|
|
|
|
#include "rtabmap/core/CameraRGB.h"
|
|
#include "rtabmap/core/CameraStereo.h"
|
|
#include "rtabmap/core/CameraThread.h"
|
|
#include "rtabmap/core/CameraEvent.h"
|
|
#include "rtabmap/core/DBReader.h"
|
|
#include "rtabmap/core/Parameters.h"
|
|
#include "rtabmap/core/ParamEvent.h"
|
|
#include "rtabmap/core/Signature.h"
|
|
#include "rtabmap/core/Memory.h"
|
|
#include "rtabmap/core/DBDriver.h"
|
|
#include "rtabmap/core/RegistrationVis.h"
|
|
|
|
#include "rtabmap/gui/ImageView.h"
|
|
#include "rtabmap/gui/KeypointItem.h"
|
|
#include "rtabmap/gui/DataRecorder.h"
|
|
#include "rtabmap/gui/DatabaseViewer.h"
|
|
#include "rtabmap/gui/PdfPlot.h"
|
|
#include "rtabmap/gui/StatsToolBox.h"
|
|
#include "rtabmap/gui/ProgressDialog.h"
|
|
|
|
#include <rtabmap/utilite/UStl.h>
|
|
#include <rtabmap/utilite/ULogger.h>
|
|
#include <rtabmap/utilite/UEventsManager.h>
|
|
#include <rtabmap/utilite/UFile.h>
|
|
#include <rtabmap/utilite/UConversion.h>
|
|
#include "rtabmap/utilite/UPlot.h"
|
|
#include "rtabmap/utilite/UCv2Qt.h"
|
|
|
|
#include "ExportCloudsDialog.h"
|
|
#include "AboutDialog.h"
|
|
#include "PostProcessingDialog.h"
|
|
|
|
#include <QtGui/QCloseEvent>
|
|
#include <QtGui/QPixmap>
|
|
#include <QtCore/QDir>
|
|
#include <QtCore/QFile>
|
|
#include <QtCore/QTextStream>
|
|
#include <QtCore/QFileInfo>
|
|
#include <QMessageBox>
|
|
#include <QFileDialog>
|
|
#include <QGraphicsEllipseItem>
|
|
#include <QDockWidget>
|
|
#include <QtCore/QBuffer>
|
|
#include <QtCore/QTimer>
|
|
#include <QtCore/QTime>
|
|
#include <QActionGroup>
|
|
#include <QtCore/QThread>
|
|
#include <QtGui/QDesktopServices>
|
|
#include <QtCore/QStringList>
|
|
#include <QtCore/QProcess>
|
|
#include <QSplashScreen>
|
|
#include <QInputDialog>
|
|
#include <QToolButton>
|
|
|
|
//RGB-D stuff
|
|
#include "rtabmap/core/CameraRGBD.h"
|
|
#include "rtabmap/core/Odometry.h"
|
|
#include "rtabmap/core/OdometryThread.h"
|
|
#include "rtabmap/core/OdometryEvent.h"
|
|
#include "rtabmap/core/util3d.h"
|
|
#include "rtabmap/core/util3d_transforms.h"
|
|
#include "rtabmap/core/util3d_filtering.h"
|
|
#include "rtabmap/core/util3d_mapping.h"
|
|
#include "rtabmap/core/util3d_surface.h"
|
|
#include "rtabmap/core/util3d_registration.h"
|
|
#include "rtabmap/core/Optimizer.h"
|
|
#include "rtabmap/core/OptimizerCVSBA.h"
|
|
#include "rtabmap/core/Graph.h"
|
|
#include "rtabmap/core/RegistrationIcp.h"
|
|
#include <pcl/visualization/cloud_viewer.h>
|
|
#include <pcl/common/transforms.h>
|
|
#include <pcl/common/common.h>
|
|
#include <pcl/io/pcd_io.h>
|
|
#include <pcl/io/ply_io.h>
|
|
#include <pcl/io/vtk_io.h>
|
|
#include <pcl/io/obj_io.h>
|
|
#include <pcl/filters/filter.h>
|
|
#include <pcl/search/kdtree.h>
|
|
|
|
#define LOG_FILE_NAME "LogRtabmap.txt"
|
|
#define SHARE_SHOW_LOG_FILE "share/rtabmap/showlogs.m"
|
|
#define SHARE_GET_PRECISION_RECALL_FILE "share/rtabmap/getPrecisionRecall.m"
|
|
#define SHARE_IMPORT_FILE "share/rtabmap/importfile.m"
|
|
|
|
using namespace rtabmap;
|
|
|
|
inline static void initGuiResource() { Q_INIT_RESOURCE(GuiLib); }
|
|
|
|
|
|
namespace rtabmap {
|
|
|
|
MainWindow::MainWindow(PreferencesDialog * prefDialog, QWidget * parent) :
|
|
QMainWindow(parent),
|
|
_ui(0),
|
|
_state(kIdle),
|
|
_camera(0),
|
|
_dbReader(0),
|
|
_odomThread(0),
|
|
_preferencesDialog(0),
|
|
_aboutDialog(0),
|
|
_exportDialog(0),
|
|
_dataRecorder(0),
|
|
_lastId(0),
|
|
_processingStatistics(false),
|
|
_processingDownloadedMap(false),
|
|
_odometryReceived(false),
|
|
_newDatabasePath(""),
|
|
_newDatabasePathOutput(""),
|
|
_openedDatabasePath(""),
|
|
_databaseUpdated(false),
|
|
_odomImageShow(true),
|
|
_odomImageDepthShow(false),
|
|
_savedMaximized(false),
|
|
_waypointsIndex(0),
|
|
_odometryCorrection(Transform::getIdentity()),
|
|
_processingOdometry(false),
|
|
_lastOdomInfoUpdateTime(0),
|
|
_oneSecondTimer(0),
|
|
_elapsedTime(0),
|
|
_posteriorCurve(0),
|
|
_likelihoodCurve(0),
|
|
_rawLikelihoodCurve(0),
|
|
_autoScreenCaptureOdomSync(false),
|
|
_autoScreenCaptureRAM(false),
|
|
_firstCall(true)
|
|
{
|
|
UDEBUG("");
|
|
|
|
initGuiResource();
|
|
|
|
QPixmap pixmap(":images/RTAB-Map.png");
|
|
QSplashScreen splash(pixmap);
|
|
splash.show();
|
|
splash.showMessage(tr("Loading..."));
|
|
QApplication::processEvents();
|
|
|
|
// Create dialogs
|
|
_aboutDialog = new AboutDialog(this);
|
|
_aboutDialog->setObjectName("AboutDialog");
|
|
_exportDialog = new ExportCloudsDialog(this);
|
|
_exportDialog->setObjectName("ExportCloudsDialog");
|
|
_postProcessingDialog = new PostProcessingDialog(this);
|
|
_postProcessingDialog->setObjectName("PostProcessingDialog");
|
|
|
|
_ui = new Ui_mainWindow();
|
|
_ui->setupUi(this);
|
|
|
|
QString title("RTAB-Map[*]");
|
|
this->setWindowTitle(title);
|
|
this->setWindowIconText(tr("RTAB-Map"));
|
|
this->setObjectName("MainWindow");
|
|
|
|
//Setup dock widgets position if it is the first time the application is started.
|
|
setDefaultViews();
|
|
|
|
_ui->widget_mainWindow->setVisible(false);
|
|
|
|
if(prefDialog)
|
|
{
|
|
_preferencesDialog = prefDialog;
|
|
_preferencesDialog->setParent(this, Qt::Dialog);
|
|
}
|
|
else // Default dialog
|
|
{
|
|
_preferencesDialog = new PreferencesDialog(this);
|
|
}
|
|
_preferencesDialog->setObjectName("PreferencesDialog");
|
|
_preferencesDialog->init();
|
|
|
|
// Restore window geometry
|
|
bool statusBarShown = false;
|
|
_preferencesDialog->loadMainWindowState(this, _savedMaximized, statusBarShown);
|
|
_preferencesDialog->loadWindowGeometry(_preferencesDialog);
|
|
_preferencesDialog->loadWindowGeometry(_exportDialog);
|
|
_preferencesDialog->loadWindowGeometry(_postProcessingDialog);
|
|
_preferencesDialog->loadWindowGeometry(_aboutDialog);
|
|
setupMainLayout(_preferencesDialog->isVerticalLayoutUsed());
|
|
|
|
// Timer
|
|
_oneSecondTimer = new QTimer(this);
|
|
_oneSecondTimer->setInterval(1000);
|
|
_elapsedTime = new QTime();
|
|
_ui->label_elapsedTime->setText("00:00:00");
|
|
connect(_oneSecondTimer, SIGNAL(timeout()), this, SLOT(updateElapsedTime()));
|
|
_logEventTime = new QTime();
|
|
_logEventTime->start();
|
|
|
|
//Graphics scenes
|
|
_ui->imageView_source->setBackgroundColor(Qt::black);
|
|
_ui->imageView_loopClosure->setBackgroundColor(Qt::black);
|
|
_ui->imageView_odometry->setBackgroundColor(Qt::black);
|
|
_ui->imageView_odometry->setAlpha(200);
|
|
_preferencesDialog->loadWidgetState(_ui->imageView_source);
|
|
_preferencesDialog->loadWidgetState(_ui->imageView_loopClosure);
|
|
_preferencesDialog->loadWidgetState(_ui->imageView_odometry);
|
|
_preferencesDialog->loadWidgetState(_ui->graphicsView_graphView);
|
|
|
|
_posteriorCurve = new PdfPlotCurve("Posterior", &_cachedSignatures, this);
|
|
_ui->posteriorPlot->addCurve(_posteriorCurve, false);
|
|
_ui->posteriorPlot->showLegend(false);
|
|
_ui->posteriorPlot->setFixedYAxis(0,1);
|
|
UPlotCurveThreshold * tc;
|
|
tc = _ui->posteriorPlot->addThreshold("Loop closure thr", float(_preferencesDialog->getLoopThr()));
|
|
connect(this, SIGNAL(loopClosureThrChanged(float)), tc, SLOT(setThreshold(float)));
|
|
|
|
_likelihoodCurve = new PdfPlotCurve("Likelihood", &_cachedSignatures, this);
|
|
_ui->likelihoodPlot->addCurve(_likelihoodCurve, false);
|
|
_ui->likelihoodPlot->showLegend(false);
|
|
|
|
_rawLikelihoodCurve = new PdfPlotCurve("Likelihood", &_cachedSignatures, this);
|
|
_ui->rawLikelihoodPlot->addCurve(_rawLikelihoodCurve, false);
|
|
_ui->rawLikelihoodPlot->showLegend(false);
|
|
|
|
_ui->doubleSpinBox_stats_imgRate->setValue(_preferencesDialog->getGeneralInputRate());
|
|
_ui->doubleSpinBox_stats_detectionRate->setValue(_preferencesDialog->getDetectionRate());
|
|
_ui->doubleSpinBox_stats_timeLimit->setValue(_preferencesDialog->getTimeLimit());
|
|
|
|
_initProgressDialog = new ProgressDialog(this);
|
|
_initProgressDialog->setWindowTitle(tr("Progress dialog"));
|
|
_initProgressDialog->setMinimumWidth(800);
|
|
|
|
connect(_ui->widget_mapVisibility, SIGNAL(visibilityChanged(int, bool)), this, SLOT(updateNodeVisibility(int, bool)));
|
|
|
|
//connect stuff
|
|
connect(_ui->actionExit, SIGNAL(triggered()), this, SLOT(close()));
|
|
qRegisterMetaType<MainWindow::State>("MainWindow::State");
|
|
connect(this, SIGNAL(stateChanged(MainWindow::State)), this, SLOT(changeState(MainWindow::State)));
|
|
connect(this, SIGNAL(rtabmapEventInitReceived(int, const QString &)), this, SLOT(processRtabmapEventInit(int, const QString &)));
|
|
qRegisterMetaType<rtabmap::RtabmapEvent3DMap>("rtabmap::RtabmapEvent3DMap");
|
|
connect(this, SIGNAL(rtabmapEvent3DMapReceived(const rtabmap::RtabmapEvent3DMap &)), this, SLOT(processRtabmapEvent3DMap(const rtabmap::RtabmapEvent3DMap &)));
|
|
qRegisterMetaType<rtabmap::RtabmapGlobalPathEvent>("rtabmap::RtabmapGlobalPathEvent");
|
|
connect(this, SIGNAL(rtabmapGlobalPathEventReceived(const rtabmap::RtabmapGlobalPathEvent &)), this, SLOT(processRtabmapGlobalPathEvent(const rtabmap::RtabmapGlobalPathEvent &)));
|
|
connect(this, SIGNAL(rtabmapLabelErrorReceived(int, const QString &)), this, SLOT(processRtabmapLabelErrorEvent(int, const QString &)));
|
|
connect(this, SIGNAL(rtabmapGoalStatusEventReceived(int)), this, SLOT(processRtabmapGoalStatusEvent(int)));
|
|
|
|
// Dock Widget view actions (Menu->Window)
|
|
_ui->menuShow_view->addAction(_ui->dockWidget_imageView->toggleViewAction());
|
|
_ui->menuShow_view->addAction(_ui->dockWidget_posterior->toggleViewAction());
|
|
_ui->menuShow_view->addAction(_ui->dockWidget_likelihood->toggleViewAction());
|
|
_ui->menuShow_view->addAction(_ui->dockWidget_rawlikelihood->toggleViewAction());
|
|
_ui->menuShow_view->addAction(_ui->dockWidget_statsV2->toggleViewAction());
|
|
_ui->menuShow_view->addAction(_ui->dockWidget_console->toggleViewAction());
|
|
_ui->menuShow_view->addAction(_ui->dockWidget_cloudViewer->toggleViewAction());
|
|
_ui->menuShow_view->addAction(_ui->dockWidget_loopClosureViewer->toggleViewAction());
|
|
_ui->menuShow_view->addAction(_ui->dockWidget_mapVisibility->toggleViewAction());
|
|
_ui->menuShow_view->addAction(_ui->dockWidget_graphViewer->toggleViewAction());
|
|
_ui->menuShow_view->addAction(_ui->dockWidget_odometry->toggleViewAction());
|
|
_ui->menuShow_view->addAction(_ui->toolBar->toggleViewAction());
|
|
_ui->toolBar->setWindowTitle(tr("File toolbar"));
|
|
_ui->menuShow_view->addAction(_ui->toolBar_2->toggleViewAction());
|
|
_ui->toolBar_2->setWindowTitle(tr("Control toolbar"));
|
|
QAction * a = _ui->menuShow_view->addAction("Progress dialog");
|
|
a->setCheckable(false);
|
|
connect(a, SIGNAL(triggered(bool)), _initProgressDialog, SLOT(show()));
|
|
QAction * statusBarAction = _ui->menuShow_view->addAction("Status bar");
|
|
statusBarAction->setCheckable(true);
|
|
statusBarAction->setChecked(statusBarShown);
|
|
connect(statusBarAction, SIGNAL(toggled(bool)), this->statusBar(), SLOT(setVisible(bool)));
|
|
|
|
// connect actions with custom slots
|
|
connect(_ui->actionSave_GUI_config, SIGNAL(triggered()), this, SLOT(saveConfigGUI()));
|
|
connect(_ui->actionNew_database, SIGNAL(triggered()), this, SLOT(newDatabase()));
|
|
connect(_ui->actionOpen_database, SIGNAL(triggered()), this, SLOT(openDatabase()));
|
|
connect(_ui->actionClose_database, SIGNAL(triggered()), this, SLOT(closeDatabase()));
|
|
connect(_ui->actionEdit_database, SIGNAL(triggered()), this, SLOT(editDatabase()));
|
|
connect(_ui->actionStart, SIGNAL(triggered()), this, SLOT(startDetection()));
|
|
connect(_ui->actionPause, SIGNAL(triggered()), this, SLOT(pauseDetection()));
|
|
connect(_ui->actionStop, SIGNAL(triggered()), this, SLOT(stopDetection()));
|
|
connect(_ui->actionDump_the_memory, SIGNAL(triggered()), this, SLOT(dumpTheMemory()));
|
|
connect(_ui->actionDump_the_prediction_matrix, SIGNAL(triggered()), this, SLOT(dumpThePrediction()));
|
|
connect(_ui->actionSend_goal, SIGNAL(triggered()), this, SLOT(sendGoal()));
|
|
connect(_ui->actionSend_waypoints, SIGNAL(triggered()), this, SLOT(sendWaypoints()));
|
|
connect(_ui->actionCancel_goal, SIGNAL(triggered()), this, SLOT(cancelGoal()));
|
|
connect(_ui->actionLabel_current_location, SIGNAL(triggered()), this, SLOT(label()));
|
|
connect(_ui->actionClear_cache, SIGNAL(triggered()), this, SLOT(clearTheCache()));
|
|
connect(_ui->actionAbout, SIGNAL(triggered()), _aboutDialog , SLOT(exec()));
|
|
connect(_ui->actionPrint_loop_closure_IDs_to_console, SIGNAL(triggered()), this, SLOT(printLoopClosureIds()));
|
|
connect(_ui->actionGenerate_map, SIGNAL(triggered()), this , SLOT(generateGraphDOT()));
|
|
connect(_ui->actionRaw_format_txt, SIGNAL(triggered()), this , SLOT(exportPosesRaw()));
|
|
connect(_ui->actionRGBD_SLAM_format_txt, SIGNAL(triggered()), this , SLOT(exportPosesRGBDSLAM()));
|
|
connect(_ui->actionKITTI_format_txt, SIGNAL(triggered()), this , SLOT(exportPosesKITTI()));
|
|
connect(_ui->actionTORO_graph, SIGNAL(triggered()), this , SLOT(exportPosesTORO()));
|
|
connect(_ui->actionG2o_g2o, SIGNAL(triggered()), this , SLOT(exportPosesG2O()));
|
|
_ui->actionG2o_g2o->setVisible(Optimizer::isAvailable(Optimizer::kTypeG2O));
|
|
connect(_ui->actionDelete_memory, SIGNAL(triggered()), this , SLOT(deleteMemory()));
|
|
connect(_ui->actionDownload_all_clouds, SIGNAL(triggered()), this , SLOT(downloadAllClouds()));
|
|
connect(_ui->actionDownload_graph, SIGNAL(triggered()), this , SLOT(downloadPoseGraph()));
|
|
connect(_ui->actionUpdate_cache_from_database, SIGNAL(triggered()), this, SLOT(updateCacheFromDatabase()));
|
|
connect(_ui->menuEdit, SIGNAL(aboutToShow()), this, SLOT(updateEditMenu()));
|
|
connect(_ui->actionDefault_views, SIGNAL(triggered(bool)), this, SLOT(setDefaultViews()));
|
|
connect(_ui->actionAuto_screen_capture, SIGNAL(triggered(bool)), this, SLOT(selectScreenCaptureFormat(bool)));
|
|
connect(_ui->actionScreenshot, SIGNAL(triggered()), this, SLOT(takeScreenshot()));
|
|
connect(_ui->action16_9, SIGNAL(triggered()), this, SLOT(setAspectRatio16_9()));
|
|
connect(_ui->action16_10, SIGNAL(triggered()), this, SLOT(setAspectRatio16_10()));
|
|
connect(_ui->action4_3, SIGNAL(triggered()), this, SLOT(setAspectRatio4_3()));
|
|
connect(_ui->action240p, SIGNAL(triggered()), this, SLOT(setAspectRatio240p()));
|
|
connect(_ui->action360p, SIGNAL(triggered()), this, SLOT(setAspectRatio360p()));
|
|
connect(_ui->action480p, SIGNAL(triggered()), this, SLOT(setAspectRatio480p()));
|
|
connect(_ui->action720p, SIGNAL(triggered()), this, SLOT(setAspectRatio720p()));
|
|
connect(_ui->action1080p, SIGNAL(triggered()), this, SLOT(setAspectRatio1080p()));
|
|
connect(_ui->actionCustom, SIGNAL(triggered()), this, SLOT(setAspectRatioCustom()));
|
|
connect(_ui->actionSave_point_cloud, SIGNAL(triggered()), this, SLOT(exportClouds()));
|
|
connect(_ui->actionExport_2D_scans_ply_pcd, SIGNAL(triggered()), this, SLOT(exportScans()));
|
|
connect(_ui->actionExport_2D_Grid_map_bmp_png, SIGNAL(triggered()), this, SLOT(exportGridMap()));
|
|
connect(_ui->actionExport_images_RGB_jpg_Depth_png, SIGNAL(triggered()), this , SLOT(exportImages()));
|
|
connect(_ui->actionExport_cameras_in_Bundle_format_out, SIGNAL(triggered()), SLOT(exportBundlerFormat()));
|
|
connect(_ui->actionView_scans, SIGNAL(triggered()), this, SLOT(viewScans()));
|
|
connect(_ui->actionView_high_res_point_cloud, SIGNAL(triggered()), this, SLOT(viewClouds()));
|
|
connect(_ui->actionReset_Odometry, SIGNAL(triggered()), this, SLOT(resetOdometry()));
|
|
connect(_ui->actionTrigger_a_new_map, SIGNAL(triggered()), this, SLOT(triggerNewMap()));
|
|
connect(_ui->actionData_recorder, SIGNAL(triggered()), this, SLOT(dataRecorder()));
|
|
connect(_ui->actionPost_processing, SIGNAL(triggered()), this, SLOT(postProcessing()));
|
|
|
|
_ui->actionPause->setShortcut(Qt::Key_Space);
|
|
_ui->actionSave_GUI_config->setShortcut(QKeySequence::Save);
|
|
_ui->actionReset_Odometry->setEnabled(false);
|
|
_ui->actionPost_processing->setEnabled(false);
|
|
|
|
QToolButton* toolButton = new QToolButton(this);
|
|
toolButton->setMenu(_ui->menuSelect_source);
|
|
toolButton->setPopupMode(QToolButton::InstantPopup);
|
|
toolButton->setIcon(QIcon(":images/kinect_xbox_360.png"));
|
|
toolButton->setToolTip("Select sensor driver");
|
|
_ui->toolBar->addWidget(toolButton)->setObjectName("toolbar_source");
|
|
|
|
#if defined(Q_WS_MAC) || defined(Q_WS_WIN)
|
|
connect(_ui->actionOpen_working_directory, SIGNAL(triggered()), SLOT(openWorkingDirectory()));
|
|
#else
|
|
_ui->menuEdit->removeAction(_ui->actionOpen_working_directory);
|
|
#endif
|
|
|
|
//Settings menu
|
|
connect(_ui->actionMore_options, SIGNAL(triggered()), this, SLOT(openPreferencesSource()));
|
|
connect(_ui->actionUsbCamera, SIGNAL(triggered()), this, SLOT(selectStream()));
|
|
connect(_ui->actionOpenNI_PCL, SIGNAL(triggered()), this, SLOT(selectOpenni()));
|
|
connect(_ui->actionOpenNI_PCL_ASUS, SIGNAL(triggered()), this, SLOT(selectOpenni()));
|
|
connect(_ui->actionFreenect, SIGNAL(triggered()), this, SLOT(selectFreenect()));
|
|
connect(_ui->actionOpenNI_CV, SIGNAL(triggered()), this, SLOT(selectOpenniCv()));
|
|
connect(_ui->actionOpenNI_CV_ASUS, SIGNAL(triggered()), this, SLOT(selectOpenniCvAsus()));
|
|
connect(_ui->actionOpenNI2, SIGNAL(triggered()), this, SLOT(selectOpenni2()));
|
|
connect(_ui->actionOpenNI2_kinect, SIGNAL(triggered()), this, SLOT(selectOpenni2()));
|
|
connect(_ui->actionOpenNI2_sense, SIGNAL(triggered()), this, SLOT(selectOpenni2()));
|
|
connect(_ui->actionFreenect2, SIGNAL(triggered()), this, SLOT(selectFreenect2()));
|
|
connect(_ui->actionStereoDC1394, SIGNAL(triggered()), this, SLOT(selectStereoDC1394()));
|
|
connect(_ui->actionStereoFlyCapture2, SIGNAL(triggered()), this, SLOT(selectStereoFlyCapture2()));
|
|
_ui->actionFreenect->setEnabled(CameraFreenect::available());
|
|
_ui->actionOpenNI_CV->setEnabled(CameraOpenNICV::available());
|
|
_ui->actionOpenNI_CV_ASUS->setEnabled(CameraOpenNICV::available());
|
|
_ui->actionOpenNI2->setEnabled(CameraOpenNI2::available());
|
|
_ui->actionOpenNI2_kinect->setEnabled(CameraOpenNI2::available());
|
|
_ui->actionOpenNI2_sense->setEnabled(CameraOpenNI2::available());
|
|
_ui->actionFreenect2->setEnabled(CameraFreenect2::available());
|
|
_ui->actionStereoDC1394->setEnabled(CameraStereoDC1394::available());
|
|
_ui->actionStereoFlyCapture2->setEnabled(CameraStereoFlyCapture2::available());
|
|
this->updateSelectSourceMenu();
|
|
|
|
connect(_ui->actionPreferences, SIGNAL(triggered()), this, SLOT(openPreferences()));
|
|
|
|
QActionGroup * modeGrp = new QActionGroup(this);
|
|
modeGrp->addAction(_ui->actionSLAM_mode);
|
|
modeGrp->addAction(_ui->actionLocalization_mode);
|
|
_ui->actionSLAM_mode->setChecked(_preferencesDialog->isSLAMMode());
|
|
_ui->actionLocalization_mode->setChecked(!_preferencesDialog->isSLAMMode());
|
|
connect(_ui->actionSLAM_mode, SIGNAL(triggered()), this, SLOT(changeMappingMode()));
|
|
connect(_ui->actionLocalization_mode, SIGNAL(triggered()), this, SLOT(changeMappingMode()));
|
|
connect(this, SIGNAL(mappingModeChanged(bool)), _preferencesDialog, SLOT(setSLAMMode(bool)));
|
|
|
|
// Settings changed
|
|
qRegisterMetaType<PreferencesDialog::PANEL_FLAGS>("PreferencesDialog::PANEL_FLAGS");
|
|
connect(_preferencesDialog, SIGNAL(settingsChanged(PreferencesDialog::PANEL_FLAGS)), this, SLOT(applyPrefSettings(PreferencesDialog::PANEL_FLAGS)));
|
|
qRegisterMetaType<rtabmap::ParametersMap>("rtabmap::ParametersMap");
|
|
connect(_preferencesDialog, SIGNAL(settingsChanged(rtabmap::ParametersMap)), this, SLOT(applyPrefSettings(rtabmap::ParametersMap)));
|
|
// config GUI modified
|
|
connect(_preferencesDialog, SIGNAL(settingsChanged(PreferencesDialog::PANEL_FLAGS)), this, SLOT(configGUIModified()));
|
|
if(prefDialog == 0)
|
|
{
|
|
connect(_preferencesDialog, SIGNAL(settingsChanged(rtabmap::ParametersMap)), this, SLOT(configGUIModified()));
|
|
}
|
|
connect(_ui->imageView_source, SIGNAL(configChanged()), this, SLOT(configGUIModified()));
|
|
connect(_ui->imageView_loopClosure, SIGNAL(configChanged()), this, SLOT(configGUIModified()));
|
|
connect(_ui->imageView_odometry, SIGNAL(configChanged()), this, SLOT(configGUIModified()));
|
|
connect(_ui->graphicsView_graphView, SIGNAL(configChanged()), this, SLOT(configGUIModified()));
|
|
connect(_ui->widget_cloudViewer, SIGNAL(configChanged()), this, SLOT(configGUIModified()));
|
|
connect(_exportDialog, SIGNAL(configChanged()), this, SLOT(configGUIModified()));
|
|
connect(_postProcessingDialog, SIGNAL(configChanged()), this, SLOT(configGUIModified()));
|
|
connect(_ui->toolBar->toggleViewAction(), SIGNAL(toggled(bool)), this, SLOT(configGUIModified()));
|
|
connect(_ui->toolBar, SIGNAL(orientationChanged(Qt::Orientation)), this, SLOT(configGUIModified()));
|
|
connect(statusBarAction, SIGNAL(toggled(bool)), this, SLOT(configGUIModified()));
|
|
QList<QDockWidget*> dockWidgets = this->findChildren<QDockWidget*>();
|
|
for(int i=0; i<dockWidgets.size(); ++i)
|
|
{
|
|
connect(dockWidgets[i], SIGNAL(dockLocationChanged(Qt::DockWidgetArea)), this, SLOT(configGUIModified()));
|
|
connect(dockWidgets[i]->toggleViewAction(), SIGNAL(toggled(bool)), this, SLOT(configGUIModified()));
|
|
}
|
|
// catch resize events
|
|
_ui->dockWidget_posterior->installEventFilter(this);
|
|
_ui->dockWidget_likelihood->installEventFilter(this);
|
|
_ui->dockWidget_rawlikelihood->installEventFilter(this);
|
|
_ui->dockWidget_statsV2->installEventFilter(this);
|
|
_ui->dockWidget_console->installEventFilter(this);
|
|
_ui->dockWidget_loopClosureViewer->installEventFilter(this);
|
|
_ui->dockWidget_mapVisibility->installEventFilter(this);
|
|
_ui->dockWidget_graphViewer->installEventFilter(this);
|
|
_ui->dockWidget_odometry->installEventFilter(this);
|
|
_ui->dockWidget_cloudViewer->installEventFilter(this);
|
|
_ui->dockWidget_imageView->installEventFilter(this);
|
|
|
|
// more connects...
|
|
connect(_ui->doubleSpinBox_stats_imgRate, SIGNAL(editingFinished()), this, SLOT(changeImgRateSetting()));
|
|
connect(_ui->doubleSpinBox_stats_detectionRate, SIGNAL(editingFinished()), this, SLOT(changeDetectionRateSetting()));
|
|
connect(_ui->doubleSpinBox_stats_timeLimit, SIGNAL(editingFinished()), this, SLOT(changeTimeLimitSetting()));
|
|
connect(this, SIGNAL(imgRateChanged(double)), _preferencesDialog, SLOT(setInputRate(double)));
|
|
connect(this, SIGNAL(detectionRateChanged(double)), _preferencesDialog, SLOT(setDetectionRate(double)));
|
|
connect(this, SIGNAL(timeLimitChanged(float)), _preferencesDialog, SLOT(setTimeLimit(float)));
|
|
|
|
// Statistics from the detector
|
|
qRegisterMetaType<rtabmap::Statistics>("rtabmap::Statistics");
|
|
connect(this, SIGNAL(statsReceived(rtabmap::Statistics)), this, SLOT(processStats(rtabmap::Statistics)));
|
|
|
|
qRegisterMetaType<rtabmap::CameraInfo>("rtabmap::CameraInfo");
|
|
connect(this, SIGNAL(cameraInfoReceived(rtabmap::CameraInfo)), this, SLOT(processCameraInfo(rtabmap::CameraInfo)));
|
|
|
|
qRegisterMetaType<rtabmap::OdometryEvent>("rtabmap::OdometryEvent");
|
|
connect(this, SIGNAL(odometryReceived(rtabmap::OdometryEvent)), this, SLOT(processOdometry(rtabmap::OdometryEvent)));
|
|
|
|
connect(this, SIGNAL(noMoreImagesReceived()), this, SLOT(notifyNoMoreImages()));
|
|
|
|
// Apply state
|
|
this->changeState(kIdle);
|
|
this->applyPrefSettings(PreferencesDialog::kPanelAll);
|
|
|
|
_ui->statsToolBox->setWorkingDirectory(_preferencesDialog->getWorkingDirectory());
|
|
_ui->graphicsView_graphView->setWorkingDirectory(_preferencesDialog->getWorkingDirectory());
|
|
_ui->widget_cloudViewer->setWorkingDirectory(_preferencesDialog->getWorkingDirectory());
|
|
_preferencesDialog->loadWidgetState(_ui->widget_cloudViewer);
|
|
|
|
//dialog states
|
|
_preferencesDialog->loadWidgetState(_exportDialog);
|
|
_preferencesDialog->loadWidgetState(_postProcessingDialog);
|
|
|
|
if(_ui->statsToolBox->findChildren<StatItem*>().size() == 0)
|
|
{
|
|
const std::map<std::string, float> & statistics = Statistics::defaultData();
|
|
for(std::map<std::string, float>::const_iterator iter = statistics.begin(); iter != statistics.end(); ++iter)
|
|
{
|
|
_ui->statsToolBox->updateStat(QString((*iter).first.c_str()).replace('_', ' '), 0, (*iter).second);
|
|
}
|
|
}
|
|
// Specific MainWindow
|
|
_ui->statsToolBox->updateStat("Planning/From/", 0.0f);
|
|
_ui->statsToolBox->updateStat("Planning/Time/ms", 0.0f);
|
|
_ui->statsToolBox->updateStat("Planning/Goal/", 0.0f);
|
|
_ui->statsToolBox->updateStat("Planning/Poses/", 0.0f);
|
|
_ui->statsToolBox->updateStat("Planning/Length/m", 0.0f);
|
|
this->loadFigures();
|
|
connect(_ui->statsToolBox, SIGNAL(figuresSetupChanged()), this, SLOT(configGUIModified()));
|
|
|
|
// update loop closure viewer parameters
|
|
ParametersMap parameters = _preferencesDialog->getAllParameters();
|
|
_ui->widget_loopClosureViewer->setDecimation(_preferencesDialog->getCloudDecimation(0));
|
|
_ui->widget_loopClosureViewer->setMaxDepth(_preferencesDialog->getCloudMaxDepth(0));
|
|
|
|
//update ui
|
|
_ui->doubleSpinBox_stats_detectionRate->setValue(_preferencesDialog->getDetectionRate());
|
|
_ui->doubleSpinBox_stats_timeLimit->setValue(_preferencesDialog->getTimeLimit());
|
|
_ui->actionSLAM_mode->setChecked(_preferencesDialog->isSLAMMode());
|
|
|
|
splash.close();
|
|
|
|
this->setFocus();
|
|
|
|
UDEBUG("");
|
|
}
|
|
|
|
MainWindow::~MainWindow()
|
|
{
|
|
UDEBUG("");
|
|
this->stopDetection();
|
|
delete _ui;
|
|
delete _elapsedTime;
|
|
}
|
|
|
|
void MainWindow::setupMainLayout(bool vertical)
|
|
{
|
|
if(vertical)
|
|
{
|
|
qobject_cast<QHBoxLayout *>(_ui->layout_imageview->layout())->setDirection(QBoxLayout::TopToBottom);
|
|
}
|
|
else if(!vertical)
|
|
{
|
|
qobject_cast<QHBoxLayout *>(_ui->layout_imageview->layout())->setDirection(QBoxLayout::LeftToRight);
|
|
}
|
|
}
|
|
|
|
void MainWindow::closeEvent(QCloseEvent* event)
|
|
{
|
|
// Try to close all children
|
|
/*QList<QMainWindow *> windows = this->findChildren<QMainWindow *>();
|
|
for(int i=0; i<windows.size(); i++) {
|
|
if(!windows[i]->close()) {
|
|
event->setAccepted(false);
|
|
return;
|
|
}
|
|
}*/
|
|
UDEBUG("");
|
|
bool processStopped = true;
|
|
if(_state != kIdle && _state != kMonitoring && _state != kMonitoringPaused)
|
|
{
|
|
this->stopDetection();
|
|
if(_state == kInitialized)
|
|
{
|
|
if(this->closeDatabase())
|
|
{
|
|
this->changeState(kApplicationClosing);
|
|
}
|
|
}
|
|
if(_state != kIdle)
|
|
{
|
|
processStopped = false;
|
|
}
|
|
}
|
|
|
|
if(processStopped)
|
|
{
|
|
//write settings before quit?
|
|
bool save = false;
|
|
if(this->isWindowModified())
|
|
{
|
|
QMessageBox::Button b=QMessageBox::question(this,
|
|
tr("RTAB-Map"),
|
|
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)
|
|
{
|
|
saveConfigGUI();
|
|
}
|
|
|
|
_ui->statsToolBox->closeFigures();
|
|
|
|
_ui->dockWidget_imageView->close();
|
|
_ui->dockWidget_likelihood->close();
|
|
_ui->dockWidget_rawlikelihood->close();
|
|
_ui->dockWidget_posterior->close();
|
|
_ui->dockWidget_statsV2->close();
|
|
_ui->dockWidget_console->close();
|
|
_ui->dockWidget_cloudViewer->close();
|
|
_ui->dockWidget_loopClosureViewer->close();
|
|
_ui->dockWidget_mapVisibility->close();
|
|
_ui->dockWidget_graphViewer->close();
|
|
_ui->dockWidget_odometry->close();
|
|
|
|
if(_camera)
|
|
{
|
|
UERROR("Camera must be already deleted here!");
|
|
delete _camera;
|
|
_camera = 0;
|
|
}
|
|
if(_dbReader)
|
|
{
|
|
UERROR("DBReader must be already deleted here!");
|
|
delete _dbReader;
|
|
_dbReader = 0;
|
|
}
|
|
if(_odomThread)
|
|
{
|
|
UERROR("OdomThread must be already deleted here!");
|
|
delete _odomThread;
|
|
_odomThread = 0;
|
|
}
|
|
event->accept();
|
|
}
|
|
else
|
|
{
|
|
event->ignore();
|
|
}
|
|
UDEBUG("");
|
|
}
|
|
|
|
void MainWindow::handleEvent(UEvent* anEvent)
|
|
{
|
|
if(anEvent->getClassName().compare("RtabmapEvent") == 0)
|
|
{
|
|
RtabmapEvent * rtabmapEvent = (RtabmapEvent*)anEvent;
|
|
Statistics stats = rtabmapEvent->getStats();
|
|
int highestHypothesisId = int(uValue(stats.data(), Statistics::kLoopHighest_hypothesis_id(), 0.0f));
|
|
int localLoopClosureId = int(uValue(stats.data(), Statistics::kLocalLoopSpace_last_closure_id(), 0.0f));
|
|
bool rejectedHyp = bool(uValue(stats.data(), Statistics::kLoopRejectedHypothesis(), 0.0f));
|
|
float highestHypothesisValue = uValue(stats.data(), Statistics::kLoopHighest_hypothesis_value(), 0.0f);
|
|
if((stats.loopClosureId() > 0 &&
|
|
_ui->actionPause_on_match->isChecked())
|
|
||
|
|
(stats.loopClosureId() == 0 &&
|
|
highestHypothesisId > 0 &&
|
|
highestHypothesisValue >= _preferencesDialog->getLoopThr() &&
|
|
_ui->actionPause_when_a_loop_hypothesis_is_rejected->isChecked() &&
|
|
rejectedHyp)
|
|
||
|
|
(localLoopClosureId > 0 &&
|
|
_ui->actionPause_on_local_loop_detection->isChecked()))
|
|
{
|
|
if(_state != kPaused && _state != kMonitoringPaused && !_processingDownloadedMap)
|
|
{
|
|
if(_preferencesDialog->beepOnPause())
|
|
{
|
|
QMetaObject::invokeMethod(this, "beep");
|
|
}
|
|
this->pauseDetection();
|
|
}
|
|
}
|
|
|
|
if(!_processingDownloadedMap)
|
|
{
|
|
_processingStatistics = true;
|
|
emit statsReceived(stats);
|
|
}
|
|
}
|
|
else if(anEvent->getClassName().compare("RtabmapEventInit") == 0)
|
|
{
|
|
RtabmapEventInit * rtabmapEventInit = (RtabmapEventInit*)anEvent;
|
|
emit rtabmapEventInitReceived((int)rtabmapEventInit->getStatus(), rtabmapEventInit->getInfo().c_str());
|
|
}
|
|
else if(anEvent->getClassName().compare("RtabmapEvent3DMap") == 0)
|
|
{
|
|
RtabmapEvent3DMap * rtabmapEvent3DMap = (RtabmapEvent3DMap*)anEvent;
|
|
emit rtabmapEvent3DMapReceived(*rtabmapEvent3DMap);
|
|
}
|
|
else if(anEvent->getClassName().compare("RtabmapGlobalPathEvent") == 0)
|
|
{
|
|
RtabmapGlobalPathEvent * rtabmapGlobalPathEvent = (RtabmapGlobalPathEvent*)anEvent;
|
|
emit rtabmapGlobalPathEventReceived(*rtabmapGlobalPathEvent);
|
|
}
|
|
else if(anEvent->getClassName().compare("RtabmapLabelErrorEvent") == 0)
|
|
{
|
|
RtabmapLabelErrorEvent * rtabmapLabelErrorEvent = (RtabmapLabelErrorEvent*)anEvent;
|
|
emit rtabmapLabelErrorReceived(rtabmapLabelErrorEvent->id(), QString(rtabmapLabelErrorEvent->label().c_str()));
|
|
}
|
|
else if(anEvent->getClassName().compare("RtabmapGoalStatusEvent") == 0)
|
|
{
|
|
emit rtabmapGoalStatusEventReceived(anEvent->getCode());
|
|
}
|
|
else if(anEvent->getClassName().compare("CameraEvent") == 0)
|
|
{
|
|
CameraEvent * cameraEvent = (CameraEvent*)anEvent;
|
|
if(cameraEvent->getCode() == CameraEvent::kCodeNoMoreImages)
|
|
{
|
|
if(_preferencesDialog->beepOnPause())
|
|
{
|
|
QMetaObject::invokeMethod(this, "beep");
|
|
}
|
|
emit noMoreImagesReceived();
|
|
}
|
|
else
|
|
{
|
|
emit cameraInfoReceived(cameraEvent->info());
|
|
}
|
|
}
|
|
else if(anEvent->getClassName().compare("OdometryEvent") == 0)
|
|
{
|
|
// limit 10 Hz max
|
|
if(UTimer::now() - _lastOdomInfoUpdateTime > 0.1)
|
|
{
|
|
_lastOdomInfoUpdateTime = UTimer::now();
|
|
OdometryEvent * odomEvent = (OdometryEvent*)anEvent;
|
|
if(!_processingOdometry && !_processingStatistics)
|
|
{
|
|
_processingOdometry = true; // if we receive too many odometry events!
|
|
emit odometryReceived(*odomEvent);
|
|
}
|
|
else
|
|
{
|
|
// we receive too many odometry events! just send without data
|
|
OdometryEvent tmp(SensorData(cv::Mat(), odomEvent->data().id()), odomEvent->pose(), odomEvent->covariance(), odomEvent->info());
|
|
emit odometryReceived(tmp);
|
|
}
|
|
}
|
|
}
|
|
else if(anEvent->getClassName().compare("ULogEvent") == 0)
|
|
{
|
|
ULogEvent * logEvent = (ULogEvent*)anEvent;
|
|
if(logEvent->getCode() >= _preferencesDialog->getGeneralLoggerPauseLevel())
|
|
{
|
|
QMetaObject::invokeMethod(_ui->dockWidget_console, "show");
|
|
// The timer prevents multiple calls to pauseDetection() before the state can be changed
|
|
if(_state != kPaused && _state != kMonitoringPaused && _logEventTime->elapsed() > 1000)
|
|
{
|
|
_logEventTime->start();
|
|
if(_preferencesDialog->beepOnPause())
|
|
{
|
|
QMetaObject::invokeMethod(this, "beep");
|
|
}
|
|
pauseDetection();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void MainWindow::processCameraInfo(const rtabmap::CameraInfo & info)
|
|
{
|
|
_ui->statsToolBox->updateStat("Camera/Time capturing/ms", (float)info.id_, (float)info.timeCapture_*1000.0);
|
|
_ui->statsToolBox->updateStat("Camera/Time disparity/ms", (float)info.id_, (float)info.timeDisparity_*1000.0);
|
|
_ui->statsToolBox->updateStat("Camera/Time mirroring/ms", (float)info.id_, (float)info.timeMirroring_*1000.0);
|
|
}
|
|
|
|
void MainWindow::processOdometry(const rtabmap::OdometryEvent & odom)
|
|
{
|
|
_processingOdometry = true;
|
|
UTimer time;
|
|
// Process Data
|
|
if(!odom.data().imageRaw().empty())
|
|
{
|
|
Transform pose = odom.pose();
|
|
bool lost = false;
|
|
bool lostStateChanged = false;
|
|
|
|
if(pose.isNull())
|
|
{
|
|
UDEBUG("odom lost"); // use last pose
|
|
lostStateChanged = _ui->widget_cloudViewer->getBackgroundColor() != Qt::darkRed;
|
|
_ui->widget_cloudViewer->setBackgroundColor(Qt::darkRed);
|
|
_ui->imageView_odometry->setBackgroundColor(Qt::darkRed);
|
|
|
|
pose = _lastOdomPose;
|
|
lost = true;
|
|
}
|
|
else if(odom.info().inliers>0 &&
|
|
_preferencesDialog->getOdomQualityWarnThr() &&
|
|
odom.info().inliers < _preferencesDialog->getOdomQualityWarnThr())
|
|
{
|
|
UDEBUG("odom warn, quality(inliers)=%d thr=%d", odom.info().inliers, _preferencesDialog->getOdomQualityWarnThr());
|
|
lostStateChanged = _ui->widget_cloudViewer->getBackgroundColor() == Qt::darkRed;
|
|
_ui->widget_cloudViewer->setBackgroundColor(Qt::darkYellow);
|
|
_ui->imageView_odometry->setBackgroundColor(Qt::darkYellow);
|
|
}
|
|
else
|
|
{
|
|
UDEBUG("odom ok");
|
|
lostStateChanged = _ui->widget_cloudViewer->getBackgroundColor() == Qt::darkRed;
|
|
_ui->widget_cloudViewer->setBackgroundColor(_ui->widget_cloudViewer->getDefaultBackgroundColor());
|
|
_ui->imageView_odometry->setBackgroundColor(Qt::black);
|
|
}
|
|
|
|
if(!pose.isNull() && (_ui->dockWidget_cloudViewer->isVisible() || _ui->graphicsView_graphView->isVisible()))
|
|
{
|
|
_lastOdomPose = pose;
|
|
_odometryReceived = true;
|
|
}
|
|
|
|
if(_ui->dockWidget_cloudViewer->isVisible())
|
|
{
|
|
bool cloudUpdated = false;
|
|
bool scanUpdated = false;
|
|
if(!pose.isNull())
|
|
{
|
|
// 3d cloud
|
|
if(odom.data().depthOrRightRaw().cols == odom.data().imageRaw().cols &&
|
|
odom.data().depthOrRightRaw().rows == odom.data().imageRaw().rows &&
|
|
!odom.data().depthOrRightRaw().empty() &&
|
|
(odom.data().cameraModels().size() || odom.data().stereoCameraModel().isValid()) &&
|
|
_preferencesDialog->isCloudsShown(1))
|
|
{
|
|
pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud;
|
|
cloud = util3d::cloudRGBFromSensorData(odom.data(),
|
|
_preferencesDialog->getCloudDecimation(1),
|
|
_preferencesDialog->getCloudMaxDepth(1),
|
|
_preferencesDialog->getCloudVoxelSize(1));
|
|
if(cloud->size())
|
|
{
|
|
cloud = util3d::transformPointCloud(cloud, pose);
|
|
|
|
if(!_ui->widget_cloudViewer->addOrUpdateCloud("cloudOdom", cloud, _odometryCorrection))
|
|
{
|
|
UERROR("Adding cloudOdom to viewer failed!");
|
|
}
|
|
_ui->widget_cloudViewer->setCloudVisibility("cloudOdom", true);
|
|
_ui->widget_cloudViewer->setCloudOpacity("cloudOdom", _preferencesDialog->getCloudOpacity(1));
|
|
_ui->widget_cloudViewer->setCloudPointSize("cloudOdom", _preferencesDialog->getCloudPointSize(1));
|
|
|
|
cloudUpdated = true;
|
|
}
|
|
}
|
|
|
|
// 2d cloud
|
|
if(!odom.data().laserScanRaw().empty() &&
|
|
_preferencesDialog->isScansShown(1))
|
|
{
|
|
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud;
|
|
cloud = util3d::laserScanToPointCloud(odom.data().laserScanRaw(), pose);
|
|
if(_preferencesDialog->getDownsamplingStepScan(1) > 0)
|
|
{
|
|
cloud = util3d::downsample(cloud, _preferencesDialog->getDownsamplingStepScan(1));
|
|
}
|
|
if(_preferencesDialog->getCloudVoxelSizeScan(1) > 0.0)
|
|
{
|
|
cloud = util3d::voxelize(cloud, _preferencesDialog->getCloudVoxelSizeScan(1));
|
|
}
|
|
if(!_ui->widget_cloudViewer->addOrUpdateCloud("scanOdom", cloud, _odometryCorrection))
|
|
{
|
|
UERROR("Adding scanOdom to viewer failed!");
|
|
}
|
|
_ui->widget_cloudViewer->setCloudVisibility("scanOdom", true);
|
|
_ui->widget_cloudViewer->setCloudOpacity("scanOdom", _preferencesDialog->getScanOpacity(1));
|
|
_ui->widget_cloudViewer->setCloudPointSize("scanOdom", _preferencesDialog->getScanPointSize(1));
|
|
|
|
scanUpdated = true;
|
|
}
|
|
}
|
|
if(!cloudUpdated && _ui->widget_cloudViewer->getAddedClouds().contains("cloudOdom"))
|
|
{
|
|
_ui->widget_cloudViewer->setCloudVisibility("cloudOdom", false);
|
|
}
|
|
if(!scanUpdated && _ui->widget_cloudViewer->getAddedClouds().contains("scanOdom"))
|
|
{
|
|
_ui->widget_cloudViewer->setCloudVisibility("scanOdom", false);
|
|
}
|
|
}
|
|
|
|
if(!odom.pose().isNull())
|
|
{
|
|
// update camera position
|
|
_ui->widget_cloudViewer->updateCameraTargetPosition(_odometryCorrection*odom.pose());
|
|
}
|
|
_ui->widget_cloudViewer->update();
|
|
|
|
if(_ui->graphicsView_graphView->isVisible())
|
|
{
|
|
if(!pose.isNull() && !odom.pose().isNull())
|
|
{
|
|
_ui->graphicsView_graphView->updateReferentialPosition(_odometryCorrection*odom.pose());
|
|
_ui->graphicsView_graphView->update();
|
|
}
|
|
}
|
|
|
|
if(_ui->dockWidget_odometry->isVisible() &&
|
|
!odom.data().imageRaw().empty())
|
|
{
|
|
if(_ui->imageView_odometry->isFeaturesShown())
|
|
{
|
|
if(odom.info().type == 0)
|
|
{
|
|
_ui->imageView_odometry->setFeatures(
|
|
odom.info().words,
|
|
odom.data().depthRaw(),
|
|
Qt::yellow);
|
|
}
|
|
else if(odom.info().type == 1)
|
|
{
|
|
std::vector<cv::KeyPoint> kpts;
|
|
cv::KeyPoint::convert(odom.info().refCorners, kpts);
|
|
_ui->imageView_odometry->setFeatures(
|
|
kpts,
|
|
odom.data().depthRaw(),
|
|
Qt::red);
|
|
}
|
|
}
|
|
|
|
//detect if it is OdometryMono intitialization
|
|
bool monoInitialization = false;
|
|
if(_preferencesDialog->getOdomStrategy() == 2 && odom.info().type == 1)
|
|
{
|
|
monoInitialization = true;
|
|
}
|
|
|
|
_ui->imageView_odometry->clearLines();
|
|
if(lost && !monoInitialization)
|
|
{
|
|
if(lostStateChanged)
|
|
{
|
|
// save state
|
|
_odomImageShow = _ui->imageView_odometry->isImageShown();
|
|
_odomImageDepthShow = _ui->imageView_odometry->isImageDepthShown();
|
|
}
|
|
_ui->imageView_odometry->setImageDepth(uCvMat2QImage(odom.data().imageRaw()));
|
|
_ui->imageView_odometry->setImageShown(true);
|
|
_ui->imageView_odometry->setImageDepthShown(true);
|
|
}
|
|
else
|
|
{
|
|
if(lostStateChanged)
|
|
{
|
|
// restore state
|
|
_ui->imageView_odometry->setImageShown(_odomImageShow);
|
|
_ui->imageView_odometry->setImageDepthShown(_odomImageDepthShow);
|
|
}
|
|
|
|
_ui->imageView_odometry->setImage(uCvMat2QImage(odom.data().imageRaw()));
|
|
if(_ui->imageView_odometry->isImageDepthShown())
|
|
{
|
|
_ui->imageView_odometry->setImageDepth(uCvMat2QImage(odom.data().depthOrRightRaw()));
|
|
}
|
|
|
|
if(odom.info().type == 0)
|
|
{
|
|
if(_ui->imageView_odometry->isFeaturesShown())
|
|
{
|
|
for(unsigned int i=0; i<odom.info().wordMatches.size(); ++i)
|
|
{
|
|
_ui->imageView_odometry->setFeatureColor(odom.info().wordMatches[i], Qt::red); // outliers
|
|
}
|
|
for(unsigned int i=0; i<odom.info().wordInliers.size(); ++i)
|
|
{
|
|
_ui->imageView_odometry->setFeatureColor(odom.info().wordInliers[i], Qt::green); // inliers
|
|
}
|
|
}
|
|
}
|
|
if(odom.info().type == 1 && odom.info().refCorners.size())
|
|
{
|
|
if(_ui->imageView_odometry->isFeaturesShown() || _ui->imageView_odometry->isLinesShown())
|
|
{
|
|
//draw lines
|
|
UASSERT(odom.info().refCorners.size() == odom.info().newCorners.size());
|
|
std::set<int> inliers(odom.info().cornerInliers.begin(), odom.info().cornerInliers.end());
|
|
for(unsigned int i=0; i<odom.info().refCorners.size(); ++i)
|
|
{
|
|
if(_ui->imageView_odometry->isFeaturesShown() && inliers.find(i) != inliers.end())
|
|
{
|
|
_ui->imageView_odometry->setFeatureColor(i, Qt::green); // inliers
|
|
}
|
|
if(_ui->imageView_odometry->isLinesShown())
|
|
{
|
|
_ui->imageView_odometry->addLine(
|
|
odom.info().refCorners[i].x,
|
|
odom.info().refCorners[i].y,
|
|
odom.info().newCorners[i].x,
|
|
odom.info().newCorners[i].y,
|
|
inliers.find(i) != inliers.end()?Qt::blue:Qt::yellow);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if(!odom.data().imageRaw().empty())
|
|
{
|
|
_ui->imageView_odometry->setSceneRect(QRectF(0,0,(float)odom.data().imageRaw().cols, (float)odom.data().imageRaw().rows));
|
|
}
|
|
|
|
_ui->imageView_odometry->update();
|
|
}
|
|
|
|
if(_ui->actionAuto_screen_capture->isChecked() && _autoScreenCaptureOdomSync)
|
|
{
|
|
this->captureScreen(_autoScreenCaptureRAM);
|
|
}
|
|
}
|
|
|
|
//Process info
|
|
if(odom.info().inliers >= 0)
|
|
{
|
|
_ui->statsToolBox->updateStat("Odometry/Inliers/", (float)odom.data().id(), (float)odom.info().inliers);
|
|
}
|
|
if(odom.info().matches >= 0)
|
|
{
|
|
_ui->statsToolBox->updateStat("Odometry/Matches/", (float)odom.data().id(), (float)odom.info().matches);
|
|
}
|
|
if(odom.info().variance >= 0)
|
|
{
|
|
_ui->statsToolBox->updateStat("Odometry/StdDev/", (float)odom.data().id(), sqrt((float)odom.info().variance));
|
|
}
|
|
if(odom.info().variance >= 0)
|
|
{
|
|
_ui->statsToolBox->updateStat("Odometry/Variance/", (float)odom.data().id(), (float)odom.info().variance);
|
|
}
|
|
if(odom.info().timeEstimation > 0)
|
|
{
|
|
_ui->statsToolBox->updateStat("Odometry/TimeEstimation/ms", (float)odom.data().id(), (float)odom.info().timeEstimation*1000.0f);
|
|
}
|
|
if(odom.info().timeParticleFiltering > 0)
|
|
{
|
|
_ui->statsToolBox->updateStat("Odometry/TimeFiltering/ms", (float)odom.data().id(), (float)odom.info().timeParticleFiltering*1000.0f);
|
|
}
|
|
if(odom.info().features >=0)
|
|
{
|
|
_ui->statsToolBox->updateStat("Odometry/Features/", (float)odom.data().id(), (float)odom.info().features);
|
|
}
|
|
if(odom.info().localMapSize >=0)
|
|
{
|
|
_ui->statsToolBox->updateStat("Odometry/Local_map_size/", (float)odom.data().id(), (float)odom.info().localMapSize);
|
|
}
|
|
_ui->statsToolBox->updateStat("Odometry/ID/", (float)odom.data().id(), (float)odom.data().id());
|
|
|
|
float x=0.0f,y,z, roll,pitch,yaw;
|
|
if(!odom.info().transform.isNull())
|
|
{
|
|
odom.info().transform.getTranslationAndEulerAngles(x,y,z,roll,pitch,yaw);
|
|
_ui->statsToolBox->updateStat("Odometry/Tx/m", (float)odom.data().id(), x);
|
|
_ui->statsToolBox->updateStat("Odometry/Ty/m", (float)odom.data().id(), y);
|
|
_ui->statsToolBox->updateStat("Odometry/Tz/m", (float)odom.data().id(), z);
|
|
_ui->statsToolBox->updateStat("Odometry/Troll/deg", (float)odom.data().id(), roll*180.0/CV_PI);
|
|
_ui->statsToolBox->updateStat("Odometry/Tpitch/deg", (float)odom.data().id(), pitch*180.0/CV_PI);
|
|
_ui->statsToolBox->updateStat("Odometry/Tyaw/deg", (float)odom.data().id(), yaw*180.0/CV_PI);
|
|
}
|
|
|
|
if(!odom.info().transformFiltered.isNull())
|
|
{
|
|
odom.info().transformFiltered.getTranslationAndEulerAngles(x,y,z,roll,pitch,yaw);
|
|
_ui->statsToolBox->updateStat("Odometry/Fx/m", (float)odom.data().id(), x);
|
|
_ui->statsToolBox->updateStat("Odometry/Fy/m", (float)odom.data().id(), y);
|
|
_ui->statsToolBox->updateStat("Odometry/Fz/m", (float)odom.data().id(), z);
|
|
_ui->statsToolBox->updateStat("Odometry/Froll/deg", (float)odom.data().id(), roll*180.0/CV_PI);
|
|
_ui->statsToolBox->updateStat("Odometry/Fpitch/deg", (float)odom.data().id(), pitch*180.0/CV_PI);
|
|
_ui->statsToolBox->updateStat("Odometry/Fyaw/deg", (float)odom.data().id(), yaw*180.0/CV_PI);
|
|
}
|
|
|
|
if(odom.info().interval > 0)
|
|
{
|
|
_ui->statsToolBox->updateStat("Odometry/Interval/ms", (float)odom.data().id(), odom.info().interval*1000.f);
|
|
_ui->statsToolBox->updateStat("Odometry/Speed/kph", (float)odom.data().id(), x/odom.info().interval*3.6f);
|
|
}
|
|
if(odom.info().distanceTravelled > 0)
|
|
{
|
|
_ui->statsToolBox->updateStat("Odometry/Distance/m", (float)odom.data().id(), odom.info().distanceTravelled);
|
|
}
|
|
|
|
_ui->statsToolBox->updateStat("/Gui refresh odom/ms", (float)odom.data().id(), time.elapsed()*1000.0);
|
|
_processingOdometry = false;
|
|
}
|
|
|
|
void MainWindow::processStats(const rtabmap::Statistics & stat)
|
|
{
|
|
_processingStatistics = true;
|
|
ULOGGER_DEBUG("");
|
|
QTime time, totalTime;
|
|
time.start();
|
|
totalTime.start();
|
|
//Affichage des stats et images
|
|
|
|
int refMapId = -1, loopMapId = -1;
|
|
if(uContains(stat.getSignatures(), stat.refImageId()))
|
|
{
|
|
refMapId = stat.getSignatures().at(stat.refImageId()).mapId();
|
|
}
|
|
int highestHypothesisId = static_cast<float>(uValue(stat.data(), Statistics::kLoopHighest_hypothesis_id(), 0.0f));
|
|
int loopId = stat.loopClosureId()>0?stat.loopClosureId():stat.localLoopClosureId()>0?stat.localLoopClosureId():highestHypothesisId;
|
|
if(_cachedSignatures.contains(loopId))
|
|
{
|
|
loopMapId = _cachedSignatures.value(loopId).mapId();
|
|
}
|
|
|
|
_ui->label_refId->setText(QString("New ID = %1 [%2]").arg(stat.refImageId()).arg(refMapId));
|
|
|
|
if(stat.extended())
|
|
{
|
|
float totalTime = static_cast<float>(uValue(stat.data(), Statistics::kTimingTotal(), 0.0f));
|
|
if(totalTime/1000.0f > float(1.0/_preferencesDialog->getDetectionRate()))
|
|
{
|
|
UWARN("Processing time (%fs) is over detection rate (%fs), real-time problem!", totalTime/1000.0f, 1.0/_preferencesDialog->getDetectionRate());
|
|
}
|
|
|
|
UDEBUG("");
|
|
bool highestHypothesisIsSaved = (bool)uValue(stat.data(), Statistics::kLoopHypothesis_reactivated(), 0.0f);
|
|
|
|
// update cache
|
|
Signature signature;
|
|
if(uContains(stat.getSignatures(), stat.refImageId()))
|
|
{
|
|
signature = stat.getSignatures().at(stat.refImageId());
|
|
signature.sensorData().uncompressData(); // make sure data are uncompressed
|
|
_cachedSignatures.insert(signature.id(), signature);
|
|
}
|
|
|
|
// For intermediate empty nodes, keep latest image shown
|
|
if(!signature.sensorData().imageRaw().empty() || signature.getWords().size())
|
|
{
|
|
_ui->imageView_source->clear();
|
|
_ui->imageView_loopClosure->clear();
|
|
|
|
_ui->imageView_source->setBackgroundColor(Qt::black);
|
|
_ui->imageView_loopClosure->setBackgroundColor(Qt::black);
|
|
|
|
_ui->label_matchId->clear();
|
|
}
|
|
|
|
int rehearsalMerged = (int)uValue(stat.data(), Statistics::kMemoryRehearsal_merged(), 0.0f);
|
|
bool rehearsedSimilarity = (float)uValue(stat.data(), Statistics::kMemoryRehearsal_id(), 0.0f) != 0.0f;
|
|
int localTimeClosures = (int)uValue(stat.data(), Statistics::kLocalLoopTime_closures(), 0.0f);
|
|
bool scanMatchingSuccess = (bool)uValue(stat.data(), Statistics::kNeighborLinkRefiningAccepted(), 0.0f);
|
|
bool smallMovement = (bool)uValue(stat.data(), Statistics::kMemorySmall_movement(), 0.0f);
|
|
_ui->label_stats_imageNumber->setText(QString("%1 [%2]").arg(stat.refImageId()).arg(refMapId));
|
|
|
|
if(rehearsalMerged > 0)
|
|
{
|
|
_ui->imageView_source->setBackgroundColor(Qt::blue);
|
|
}
|
|
else if(localTimeClosures > 0)
|
|
{
|
|
_ui->imageView_source->setBackgroundColor(Qt::darkYellow);
|
|
}
|
|
else if(scanMatchingSuccess)
|
|
{
|
|
_ui->imageView_source->setBackgroundColor(Qt::darkCyan);
|
|
}
|
|
else if(rehearsedSimilarity)
|
|
{
|
|
_ui->imageView_source->setBackgroundColor(Qt::darkBlue);
|
|
}
|
|
else if(smallMovement)
|
|
{
|
|
_ui->imageView_source->setBackgroundColor(Qt::gray);
|
|
}
|
|
|
|
UDEBUG("time= %d ms", time.restart());
|
|
|
|
int rejectedHyp = bool(uValue(stat.data(), Statistics::kLoopRejectedHypothesis(), 0.0f));
|
|
float highestHypothesisValue = uValue(stat.data(), Statistics::kLoopHighest_hypothesis_value(), 0.0f);
|
|
int matchId = 0;
|
|
Signature loopSignature;
|
|
int shownLoopId = 0;
|
|
if(highestHypothesisId > 0 || stat.localLoopClosureId()>0)
|
|
{
|
|
bool show = true;
|
|
if(stat.loopClosureId() > 0)
|
|
{
|
|
_ui->imageView_loopClosure->setBackgroundColor(Qt::green);
|
|
_ui->label_stats_loopClosuresDetected->setText(QString::number(_ui->label_stats_loopClosuresDetected->text().toInt() + 1));
|
|
if(highestHypothesisIsSaved)
|
|
{
|
|
_ui->label_stats_loopClosuresReactivatedDetected->setText(QString::number(_ui->label_stats_loopClosuresReactivatedDetected->text().toInt() + 1));
|
|
}
|
|
_ui->label_matchId->setText(QString("Match ID = %1 [%2]").arg(stat.loopClosureId()).arg(loopMapId));
|
|
matchId = stat.loopClosureId();
|
|
}
|
|
else if(stat.localLoopClosureId())
|
|
{
|
|
_ui->imageView_loopClosure->setBackgroundColor(Qt::yellow);
|
|
_ui->label_matchId->setText(QString("Local match = %1 [%2]").arg(stat.localLoopClosureId()).arg(loopMapId));
|
|
matchId = stat.localLoopClosureId();
|
|
}
|
|
else if(rejectedHyp && highestHypothesisValue >= _preferencesDialog->getLoopThr())
|
|
{
|
|
show = _preferencesDialog->imageRejectedShown() || _preferencesDialog->imageHighestHypShown();
|
|
if(show)
|
|
{
|
|
_ui->imageView_loopClosure->setBackgroundColor(Qt::red);
|
|
_ui->label_stats_loopClosuresRejected->setText(QString::number(_ui->label_stats_loopClosuresRejected->text().toInt() + 1));
|
|
_ui->label_matchId->setText(QString("Loop hypothesis %1 rejected!").arg(highestHypothesisId));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
show = _preferencesDialog->imageHighestHypShown();
|
|
if(show)
|
|
{
|
|
_ui->label_matchId->setText(QString("Highest hypothesis (%1)").arg(highestHypothesisId));
|
|
}
|
|
}
|
|
|
|
if(show)
|
|
{
|
|
shownLoopId = stat.loopClosureId()>0?stat.loopClosureId():stat.localLoopClosureId()>0?stat.localLoopClosureId():highestHypothesisId;
|
|
QMap<int, Signature>::iterator iter = _cachedSignatures.find(shownLoopId);
|
|
if(iter != _cachedSignatures.end())
|
|
{
|
|
iter.value().sensorData().uncompressData();
|
|
loopSignature = iter.value();
|
|
}
|
|
}
|
|
}
|
|
_refIds.push_back(stat.refImageId());
|
|
_loopClosureIds.push_back(matchId);
|
|
|
|
//update image views
|
|
{
|
|
UCvMat2QImageThread qimageThread(signature.sensorData().imageRaw());
|
|
UCvMat2QImageThread qimageLoopThread(loopSignature.sensorData().imageRaw());
|
|
UCvMat2QImageThread qdepthThread(signature.sensorData().depthOrRightRaw());
|
|
UCvMat2QImageThread qdepthLoopThread(loopSignature.sensorData().depthOrRightRaw());
|
|
qimageThread.start();
|
|
qdepthThread.start();
|
|
qimageLoopThread.start();
|
|
qdepthLoopThread.start();
|
|
qimageThread.join();
|
|
qdepthThread.join();
|
|
qimageLoopThread.join();
|
|
qdepthLoopThread.join();
|
|
QImage img = qimageThread.getQImage();
|
|
QImage lcImg = qimageLoopThread.getQImage();
|
|
QImage depth = qdepthThread.getQImage();
|
|
QImage lcDepth = qdepthLoopThread.getQImage();
|
|
UDEBUG("time= %d ms", time.restart());
|
|
|
|
if(!img.isNull())
|
|
{
|
|
_ui->imageView_source->setImage(img);
|
|
}
|
|
if(!depth.isNull())
|
|
{
|
|
_ui->imageView_source->setImageDepth(depth);
|
|
}
|
|
if(!lcImg.isNull())
|
|
{
|
|
_ui->imageView_loopClosure->setImage(lcImg);
|
|
}
|
|
if(!lcDepth.isNull())
|
|
{
|
|
_ui->imageView_loopClosure->setImageDepth(lcDepth);
|
|
}
|
|
if(_ui->imageView_loopClosure->sceneRect().isNull())
|
|
{
|
|
_ui->imageView_loopClosure->setSceneRect(_ui->imageView_source->sceneRect());
|
|
}
|
|
}
|
|
|
|
UDEBUG("time= %d ms", time.restart());
|
|
|
|
// do it after scaling
|
|
this->drawKeypoints(signature.getWords(), loopSignature.getWords());
|
|
|
|
UDEBUG("time= %d ms", time.restart());
|
|
|
|
_ui->statsToolBox->updateStat("Keypoint/Keypoints count in the last signature/", stat.refImageId(), signature.getWords().size());
|
|
_ui->statsToolBox->updateStat("Keypoint/Keypoints count in the loop signature/", stat.refImageId(), loopSignature.getWords().size());
|
|
|
|
// PDF AND LIKELIHOOD
|
|
if(!stat.posterior().empty() && _ui->dockWidget_posterior->isVisible())
|
|
{
|
|
UDEBUG("");
|
|
_posteriorCurve->setData(QMap<int, float>(stat.posterior()), QMap<int, int>(stat.weights()));
|
|
|
|
ULOGGER_DEBUG("");
|
|
//Adjust thresholds
|
|
float value;
|
|
value = float(_preferencesDialog->getLoopThr());
|
|
emit(loopClosureThrChanged(value));
|
|
}
|
|
if(!stat.likelihood().empty() && _ui->dockWidget_likelihood->isVisible())
|
|
{
|
|
_likelihoodCurve->setData(QMap<int, float>(stat.likelihood()), QMap<int, int>(stat.weights()));
|
|
}
|
|
if(!stat.rawLikelihood().empty() && _ui->dockWidget_rawlikelihood->isVisible())
|
|
{
|
|
_rawLikelihoodCurve->setData(QMap<int, float>(stat.rawLikelihood()), QMap<int, int>(stat.weights()));
|
|
}
|
|
|
|
// Update statistics tool box
|
|
const std::map<std::string, float> & statistics = stat.data();
|
|
for(std::map<std::string, float>::const_iterator iter = statistics.begin(); iter != statistics.end(); ++iter)
|
|
{
|
|
//ULOGGER_DEBUG("Updating stat \"%s\"", (*iter).first.c_str());
|
|
_ui->statsToolBox->updateStat(QString((*iter).first.c_str()).replace('_', ' '), stat.refImageId(), (*iter).second);
|
|
}
|
|
|
|
UDEBUG("time= %d ms", time.restart());
|
|
|
|
//======================
|
|
// RGB-D Mapping stuff
|
|
//======================
|
|
UTimer timerVis;
|
|
|
|
// update clouds
|
|
if(stat.poses().size())
|
|
{
|
|
// update pose only if odometry is not received
|
|
std::map<int, int> mapIds;
|
|
std::map<int, std::string> labels;
|
|
for(std::map<int, Signature>::const_iterator iter=stat.getSignatures().begin(); iter!=stat.getSignatures().end();++iter)
|
|
{
|
|
mapIds.insert(std::make_pair(iter->first, iter->second.mapId()));
|
|
if(!iter->second.getLabel().empty())
|
|
{
|
|
labels.insert(std::make_pair(iter->first, iter->second.getLabel()));
|
|
}
|
|
}
|
|
updateMapCloud(stat.poses(),
|
|
_odometryReceived||stat.poses().size()==0?Transform():stat.poses().rbegin()->second,
|
|
stat.constraints(),
|
|
mapIds,
|
|
labels);
|
|
|
|
_odometryReceived = false;
|
|
|
|
_odometryCorrection = stat.mapCorrection();
|
|
|
|
UDEBUG("time= %d ms", time.restart());
|
|
_ui->statsToolBox->updateStat("/Gui RGB-D cloud/ms", stat.refImageId(), int(timerVis.elapsed()*1000.0f));
|
|
|
|
// loop closure view
|
|
if((stat.loopClosureId() > 0 || stat.localLoopClosureId() > 0) &&
|
|
!stat.loopClosureTransform().isNull() &&
|
|
!loopSignature.sensorData().imageRaw().empty())
|
|
{
|
|
// the last loop closure data
|
|
Transform loopClosureTransform = stat.loopClosureTransform();
|
|
signature.setPose(loopClosureTransform);
|
|
_ui->widget_loopClosureViewer->setData(loopSignature, signature);
|
|
if(_ui->dockWidget_loopClosureViewer->isVisible())
|
|
{
|
|
UTimer loopTimer;
|
|
_ui->widget_loopClosureViewer->updateView();
|
|
UINFO("Updating loop closure cloud view time=%fs", loopTimer.elapsed());
|
|
_ui->statsToolBox->updateStat("/Gui RGB-D closure view/ms", stat.refImageId(), int(loopTimer.elapsed()*1000.0f));
|
|
}
|
|
|
|
UDEBUG("time= %d ms", time.restart());
|
|
}
|
|
}
|
|
|
|
if( _ui->graphicsView_graphView->isVisible())
|
|
{
|
|
// update posterior on the graph view
|
|
if(_preferencesDialog->isPosteriorGraphView() &&
|
|
stat.posterior().size())
|
|
{
|
|
_ui->graphicsView_graphView->updatePosterior(stat.posterior());
|
|
}
|
|
// update local path on the graph view
|
|
_ui->graphicsView_graphView->updateLocalPath(stat.localPath());
|
|
if(stat.localPath().size() == 0)
|
|
{
|
|
// clear the global path if set (goal reached)
|
|
_ui->graphicsView_graphView->setGlobalPath(std::vector<std::pair<int, Transform> >());
|
|
}
|
|
// update current goal id
|
|
if(stat.currentGoalId() > 0)
|
|
{
|
|
_ui->graphicsView_graphView->setCurrentGoalID(stat.currentGoalId(), uValue(stat.poses(), stat.currentGoalId(), Transform()));
|
|
}
|
|
}
|
|
|
|
UDEBUG("");
|
|
}
|
|
else if(!stat.extended() && stat.loopClosureId()>0)
|
|
{
|
|
_ui->label_stats_loopClosuresDetected->setText(QString::number(_ui->label_stats_loopClosuresDetected->text().toInt() + 1));
|
|
_ui->label_matchId->setText(QString("Match ID = %1 [%2]").arg(stat.loopClosureId()).arg(loopMapId));
|
|
}
|
|
else
|
|
{
|
|
_ui->label_matchId->clear();
|
|
}
|
|
float elapsedTime = static_cast<float>(totalTime.elapsed());
|
|
UINFO("Updating GUI time = %fs", elapsedTime/1000.0f);
|
|
_ui->statsToolBox->updateStat("/Gui refresh stats/ms", stat.refImageId(), elapsedTime);
|
|
if(_ui->actionAuto_screen_capture->isChecked() && !_autoScreenCaptureOdomSync)
|
|
{
|
|
this->captureScreen(_autoScreenCaptureRAM);
|
|
}
|
|
|
|
if(!_preferencesDialog->isImagesKept())
|
|
{
|
|
_cachedSignatures.clear();
|
|
}
|
|
if(_state != kMonitoring && _state != kDetecting)
|
|
{
|
|
_ui->actionExport_images_RGB_jpg_Depth_png->setEnabled(!_cachedSignatures.empty() && !_currentPosesMap.empty());
|
|
_ui->actionExport_cameras_in_Bundle_format_out->setEnabled(!_cachedSignatures.empty() && !_currentPosesMap.empty());
|
|
}
|
|
|
|
_processingStatistics = false;
|
|
}
|
|
|
|
void MainWindow::updateMapCloud(
|
|
const std::map<int, Transform> & posesIn,
|
|
const Transform & currentPose,
|
|
const std::multimap<int, Link> & constraints,
|
|
const std::map<int, int> & mapIdsIn,
|
|
const std::map<int, std::string> & labels,
|
|
bool verboseProgress)
|
|
{
|
|
UDEBUG("posesIn=%d constraints=%d mapIdsIn=%d labelsIn=%d currentPose=%s",
|
|
(int)posesIn.size(), (int)constraints.size(), (int)mapIdsIn.size(), (int)labels.size(), currentPose.prettyPrint().c_str());
|
|
if(posesIn.size())
|
|
{
|
|
_currentPosesMap = posesIn;
|
|
_currentLinksMap = constraints;
|
|
_currentMapIds = mapIdsIn;
|
|
_curentLabels = labels;
|
|
if(_state != kMonitoring && _state != kDetecting)
|
|
{
|
|
_ui->actionPost_processing->setEnabled(_cachedSignatures.size() >= 2 && _currentPosesMap.size() >= 2 && _currentLinksMap.size() >= 1);
|
|
_ui->menuExport_poses->setEnabled(!_currentPosesMap.empty());
|
|
}
|
|
}
|
|
|
|
// filter duplicated poses
|
|
std::map<int, Transform> poses;
|
|
std::map<int, int> mapIds;
|
|
if(_preferencesDialog->isCloudFiltering() && posesIn.size())
|
|
{
|
|
float radius = _preferencesDialog->getCloudFilteringRadius();
|
|
float angle = _preferencesDialog->getCloudFilteringAngle()*CV_PI/180.0; // convert to rad
|
|
poses = rtabmap::graph::radiusPosesFiltering(posesIn, radius, angle);
|
|
// make sure the last is here
|
|
poses.insert(*posesIn.rbegin());
|
|
for(std::map<int, Transform>::iterator iter= poses.begin(); iter!=poses.end(); ++iter)
|
|
{
|
|
std::map<int, int>::const_iterator jter = mapIdsIn.find(iter->first);
|
|
if(jter!=mapIdsIn.end())
|
|
{
|
|
mapIds.insert(*jter);
|
|
}
|
|
else
|
|
{
|
|
UERROR("map id of node %d not found!", iter->first);
|
|
}
|
|
}
|
|
|
|
if(verboseProgress)
|
|
{
|
|
_initProgressDialog->appendText(tr("Map update: %1 nodes shown of %2 (cloud filtering is on)").arg(poses.size()).arg(posesIn.size()));
|
|
QApplication::processEvents();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
poses = posesIn;
|
|
mapIds = mapIdsIn;
|
|
}
|
|
|
|
std::map<int, bool> posesMask;
|
|
for(std::map<int, Transform>::const_iterator iter = posesIn.begin(); iter!=posesIn.end(); ++iter)
|
|
{
|
|
posesMask.insert(posesMask.end(), std::make_pair(iter->first, poses.find(iter->first) != poses.end()));
|
|
}
|
|
_ui->widget_mapVisibility->setMap(posesIn, posesMask);
|
|
|
|
// Map updated! regenerate the assembled cloud, last pose is the new one
|
|
UDEBUG("Update map with %d locations (currentPose=%s)", poses.size(), currentPose.prettyPrint().c_str());
|
|
QMap<std::string, Transform> viewerClouds = _ui->widget_cloudViewer->getAddedClouds();
|
|
int i=1;
|
|
for(std::map<int, Transform>::const_iterator iter = poses.begin(); iter!=poses.end(); ++iter)
|
|
{
|
|
if(!iter->second.isNull())
|
|
{
|
|
std::string cloudName = uFormat("cloud%d", iter->first);
|
|
|
|
// 3d point cloud
|
|
if((_ui->widget_cloudViewer->isVisible() && _preferencesDialog->isCloudsShown(0)) ||
|
|
(_ui->graphicsView_graphView->isVisible() && _ui->graphicsView_graphView->isGridMapVisible() && _preferencesDialog->isGridMapFrom3DCloud()))
|
|
{
|
|
if(viewerClouds.contains(cloudName))
|
|
{
|
|
// Update only if the pose has changed
|
|
Transform tCloud;
|
|
_ui->widget_cloudViewer->getPose(cloudName, tCloud);
|
|
if(tCloud.isNull() || iter->second != tCloud)
|
|
{
|
|
if(!_ui->widget_cloudViewer->updateCloudPose(cloudName, iter->second))
|
|
{
|
|
UERROR("Updating pose cloud %d failed!", iter->first);
|
|
}
|
|
}
|
|
_ui->widget_cloudViewer->setCloudVisibility(cloudName, true);
|
|
_ui->widget_cloudViewer->setCloudOpacity(cloudName, _preferencesDialog->getCloudOpacity(0));
|
|
_ui->widget_cloudViewer->setCloudPointSize(cloudName, _preferencesDialog->getCloudPointSize(0));
|
|
}
|
|
else if(_cachedSignatures.contains(iter->first))
|
|
{
|
|
QMap<int, Signature>::iterator jter = _cachedSignatures.find(iter->first);
|
|
if((!jter->sensorData().imageCompressed().empty() && !jter->sensorData().depthOrRightCompressed().empty()) || jter->getWords3().size())
|
|
{
|
|
this->createAndAddCloudToMap(iter->first, iter->second, uValue(mapIds, iter->first, -1));
|
|
}
|
|
}
|
|
}
|
|
else if(viewerClouds.contains(cloudName))
|
|
{
|
|
UDEBUG("Hide cloud %s", cloudName.c_str());
|
|
_ui->widget_cloudViewer->setCloudVisibility(cloudName.c_str(), false);
|
|
}
|
|
|
|
// 2d point cloud
|
|
std::string scanName = uFormat("scan%d", iter->first);
|
|
if((_ui->widget_cloudViewer->isVisible() && (_preferencesDialog->isScansShown(0) || _preferencesDialog->getGridMapShown())) ||
|
|
(_ui->graphicsView_graphView->isVisible() && _ui->graphicsView_graphView->isGridMapVisible()))
|
|
{
|
|
if(viewerClouds.contains(scanName))
|
|
{
|
|
// Update only if the pose has changed
|
|
Transform tScan;
|
|
_ui->widget_cloudViewer->getPose(scanName, tScan);
|
|
if(tScan.isNull() || iter->second != tScan)
|
|
{
|
|
if(!_ui->widget_cloudViewer->updateCloudPose(scanName, iter->second))
|
|
{
|
|
UERROR("Updating pose scan %d failed!", iter->first);
|
|
}
|
|
}
|
|
_ui->widget_cloudViewer->setCloudVisibility(scanName, true);
|
|
_ui->widget_cloudViewer->setCloudOpacity(scanName, _preferencesDialog->getScanOpacity(0));
|
|
_ui->widget_cloudViewer->setCloudPointSize(scanName, _preferencesDialog->getScanPointSize(0));
|
|
}
|
|
else if(_cachedSignatures.contains(iter->first))
|
|
{
|
|
QMap<int, Signature>::iterator jter = _cachedSignatures.find(iter->first);
|
|
if(!jter->sensorData().laserScanCompressed().empty())
|
|
{
|
|
this->createAndAddScanToMap(iter->first, iter->second, uValue(mapIds, iter->first, -1));
|
|
}
|
|
}
|
|
if(!_preferencesDialog->isScansShown(0))
|
|
{
|
|
UDEBUG("Hide scan %s", scanName.c_str());
|
|
_ui->widget_cloudViewer->setCloudVisibility(scanName.c_str(), false);
|
|
}
|
|
}
|
|
else if(viewerClouds.contains(scanName))
|
|
{
|
|
UDEBUG("Hide scan %s", scanName.c_str());
|
|
_ui->widget_cloudViewer->setCloudVisibility(scanName.c_str(), false);
|
|
}
|
|
|
|
if(verboseProgress)
|
|
{
|
|
_initProgressDialog->appendText(tr("Updated cloud %1 (%2/%3)").arg(iter->first).arg(i).arg(poses.size()));
|
|
_initProgressDialog->incrementStep();
|
|
if(poses.size() < 200 || i % 100 == 0)
|
|
{
|
|
QApplication::processEvents();
|
|
}
|
|
}
|
|
}
|
|
|
|
++i;
|
|
}
|
|
|
|
// activate actions
|
|
if(_state != kMonitoring && _state != kDetecting)
|
|
{
|
|
_ui->actionSave_point_cloud->setEnabled(!_createdClouds.empty());
|
|
_ui->actionView_high_res_point_cloud->setEnabled(!_createdClouds.empty());
|
|
_ui->actionExport_2D_scans_ply_pcd->setEnabled(!_createdScans.empty());
|
|
_ui->actionExport_2D_Grid_map_bmp_png->setEnabled(!_gridLocalMaps.empty() || !_projectionLocalMaps.empty());
|
|
_ui->actionView_scans->setEnabled(!_createdScans.empty());
|
|
}
|
|
|
|
//remove not used clouds
|
|
for(QMap<std::string, Transform>::iterator iter = viewerClouds.begin(); iter!=viewerClouds.end(); ++iter)
|
|
{
|
|
std::list<std::string> splitted = uSplitNumChar(iter.key());
|
|
if(splitted.size() == 2)
|
|
{
|
|
int id = std::atoi(splitted.back().c_str());
|
|
if(poses.find(id) == poses.end())
|
|
{
|
|
if(_ui->widget_cloudViewer->getCloudVisibility(iter.key()))
|
|
{
|
|
UDEBUG("Hide %s", iter.key().c_str());
|
|
_ui->widget_cloudViewer->setCloudVisibility(iter.key(), false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// update 3D graphes (show all poses)
|
|
_ui->widget_cloudViewer->removeAllGraphs();
|
|
_ui->widget_cloudViewer->removeCloud("graph_nodes");
|
|
if(_preferencesDialog->isGraphsShown() && _currentPosesMap.size())
|
|
{
|
|
// Find all graphs
|
|
std::map<int, pcl::PointCloud<pcl::PointXYZ>::Ptr > graphs;
|
|
for(std::map<int, Transform>::iterator iter=_currentPosesMap.begin(); iter!=_currentPosesMap.end(); ++iter)
|
|
{
|
|
int mapId = uValue(_currentMapIds, iter->first, -1);
|
|
|
|
//edges
|
|
std::map<int, pcl::PointCloud<pcl::PointXYZ>::Ptr >::iterator kter = graphs.find(mapId);
|
|
if(kter == graphs.end())
|
|
{
|
|
kter = graphs.insert(std::make_pair(mapId, pcl::PointCloud<pcl::PointXYZ>::Ptr(new pcl::PointCloud<pcl::PointXYZ>))).first;
|
|
}
|
|
pcl::PointXYZ pt(iter->second.x(), iter->second.y(), iter->second.z());
|
|
kter->second->push_back(pt);
|
|
}
|
|
|
|
// add graphs
|
|
for(std::map<int, pcl::PointCloud<pcl::PointXYZ>::Ptr >::iterator iter=graphs.begin(); iter!=graphs.end(); ++iter)
|
|
{
|
|
QColor color = Qt::gray;
|
|
if(iter->first >= 0)
|
|
{
|
|
color = (Qt::GlobalColor)((iter->first+3) % 12 + 7 );
|
|
}
|
|
_ui->widget_cloudViewer->addOrUpdateGraph(uFormat("graph_%d", iter->first), iter->second, color);
|
|
}
|
|
}
|
|
|
|
// Update labels
|
|
_ui->widget_cloudViewer->removeAllTexts();
|
|
if(_preferencesDialog->isLabelsShown() && labels.size())
|
|
{
|
|
for(std::map<int, std::string>::const_iterator iter=labels.begin(); iter!=labels.end(); ++iter)
|
|
{
|
|
if(posesIn.find(iter->first)!=posesIn.end())
|
|
{
|
|
int mapId = uValue(mapIdsIn, iter->first, -1);
|
|
QColor color = Qt::gray;
|
|
if(mapId >= 0)
|
|
{
|
|
color = (Qt::GlobalColor)((mapId+3) % 12 + 7 );
|
|
}
|
|
_ui->widget_cloudViewer->addOrUpdateText(
|
|
std::string("label_") + uNumber2Str(iter->first),
|
|
iter->second,
|
|
_currentPosesMap.at(iter->first),
|
|
0.1,
|
|
color);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update occupancy grid map in 3D map view and graph view
|
|
if(_ui->graphicsView_graphView->isVisible())
|
|
{
|
|
_ui->graphicsView_graphView->updateGraph(posesIn, constraints, mapIdsIn);
|
|
if(!currentPose.isNull())
|
|
{
|
|
_ui->graphicsView_graphView->updateReferentialPosition(currentPose);
|
|
}
|
|
}
|
|
cv::Mat map8U;
|
|
if((_ui->graphicsView_graphView->isVisible() || _preferencesDialog->getGridMapShown()) && (_createdScans.size() || _preferencesDialog->isGridMapFrom3DCloud()))
|
|
{
|
|
float xMin, yMin;
|
|
float resolution = _preferencesDialog->getGridMapResolution();
|
|
cv::Mat map8S = util3d::create2DMapFromOccupancyLocalMaps(
|
|
poses,
|
|
_preferencesDialog->isGridMapFrom3DCloud()?_projectionLocalMaps:_gridLocalMaps,
|
|
resolution,
|
|
xMin, yMin,
|
|
0,
|
|
_preferencesDialog->isGridMapEroded());
|
|
if(!map8S.empty())
|
|
{
|
|
//convert to gray scaled map
|
|
map8U = util3d::convertMap2Image8U(map8S);
|
|
|
|
if(_preferencesDialog->getGridMapShown())
|
|
{
|
|
float opacity = _preferencesDialog->getGridMapOpacity();
|
|
_ui->widget_cloudViewer->addOccupancyGridMap(map8U, resolution, xMin, yMin, opacity);
|
|
}
|
|
if(_ui->graphicsView_graphView->isVisible())
|
|
{
|
|
_ui->graphicsView_graphView->updateMap(map8U, resolution, xMin, yMin);
|
|
}
|
|
}
|
|
}
|
|
_ui->graphicsView_graphView->update();
|
|
|
|
if(!_preferencesDialog->getGridMapShown())
|
|
{
|
|
_ui->widget_cloudViewer->removeOccupancyGridMap();
|
|
}
|
|
|
|
if(viewerClouds.contains("cloudOdom"))
|
|
{
|
|
if(!_preferencesDialog->isCloudsShown(1))
|
|
{
|
|
_ui->widget_cloudViewer->setCloudVisibility("cloudOdom", false);
|
|
}
|
|
else
|
|
{
|
|
_ui->widget_cloudViewer->updateCloudPose("cloudOdom", _odometryCorrection);
|
|
_ui->widget_cloudViewer->setCloudOpacity("cloudOdom", _preferencesDialog->getCloudOpacity(1));
|
|
_ui->widget_cloudViewer->setCloudPointSize("cloudOdom", _preferencesDialog->getCloudPointSize(1));
|
|
}
|
|
}
|
|
if(viewerClouds.contains("scanOdom"))
|
|
{
|
|
if(!_preferencesDialog->isScansShown(1))
|
|
{
|
|
_ui->widget_cloudViewer->setCloudVisibility("scanOdom", false);
|
|
}
|
|
else
|
|
{
|
|
_ui->widget_cloudViewer->updateCloudPose("scanOdom", _odometryCorrection);
|
|
_ui->widget_cloudViewer->setCloudOpacity("scanOdom", _preferencesDialog->getScanOpacity(1));
|
|
_ui->widget_cloudViewer->setCloudPointSize("scanOdom", _preferencesDialog->getScanPointSize(1));
|
|
}
|
|
}
|
|
|
|
if(!currentPose.isNull())
|
|
{
|
|
_ui->widget_cloudViewer->updateCameraTargetPosition(currentPose);
|
|
}
|
|
|
|
_ui->widget_cloudViewer->update();
|
|
}
|
|
|
|
void MainWindow::createAndAddCloudToMap(int nodeId, const Transform & pose, int mapId)
|
|
{
|
|
UASSERT(!pose.isNull());
|
|
std::string cloudName = uFormat("cloud%d", nodeId);
|
|
if(_ui->widget_cloudViewer->getAddedClouds().contains(cloudName))
|
|
{
|
|
UERROR("Cloud %d already added to map.", nodeId);
|
|
return;
|
|
}
|
|
|
|
QMap<int, Signature>::iterator iter = _cachedSignatures.find(nodeId);
|
|
if(iter == _cachedSignatures.end())
|
|
{
|
|
UERROR("Node %d is not in the cache.", nodeId);
|
|
return;
|
|
}
|
|
|
|
if(!iter->sensorData().imageCompressed().empty() && !iter->sensorData().depthOrRightCompressed().empty())
|
|
{
|
|
|
|
cv::Mat image, depth;
|
|
SensorData data = iter->sensorData();
|
|
data.uncompressData(&image, &depth, 0);
|
|
|
|
pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud;
|
|
UASSERT(nodeId == data.id());
|
|
cloud = util3d::cloudRGBFromSensorData(data,
|
|
_preferencesDialog->getCloudDecimation(0),
|
|
_preferencesDialog->getCloudMaxDepth(0),
|
|
_preferencesDialog->getCloudVoxelSize(0));
|
|
_createdClouds.insert(std::make_pair(nodeId, cloud));
|
|
|
|
if(cloud->size() && _preferencesDialog->isGridMapFrom3DCloud())
|
|
{
|
|
UTimer timer;
|
|
float cellSize = _preferencesDialog->getGridMapResolution();
|
|
float groundNormalMaxAngle = M_PI_4;
|
|
int minClusterSize = 20;
|
|
cv::Mat ground, obstacles;
|
|
pcl::PointCloud<pcl::PointXYZRGB>::Ptr voxelizedCloud = cloud;
|
|
if(voxelizedCloud->size() && cellSize > _preferencesDialog->getCloudVoxelSize(0))
|
|
{
|
|
voxelizedCloud = util3d::voxelize(cloud, cellSize);
|
|
}
|
|
util3d::occupancy2DFromCloud3D<pcl::PointXYZRGB>(
|
|
voxelizedCloud,
|
|
ground, obstacles,
|
|
cellSize,
|
|
groundNormalMaxAngle,
|
|
minClusterSize);
|
|
if(!ground.empty() || !obstacles.empty())
|
|
{
|
|
_projectionLocalMaps.insert(std::make_pair(nodeId, std::make_pair(ground, obstacles)));
|
|
}
|
|
UDEBUG("time gridMapFrom2DCloud = %f s", timer.ticks());
|
|
}
|
|
|
|
pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloudFiltered = cloud;
|
|
if(_preferencesDialog->isSubtractFiltering() &&
|
|
_preferencesDialog->getCloudVoxelSize(0) > 0.0 &&
|
|
cloud->size() &&
|
|
_createdClouds.size() &&
|
|
_currentPosesMap.size() &&
|
|
_currentLinksMap.size())
|
|
{
|
|
// find link to previous neighbor
|
|
std::map<int, Transform>::const_iterator previousIter = _currentPosesMap.find(nodeId);
|
|
Link link;
|
|
if(previousIter != _currentPosesMap.begin())
|
|
{
|
|
--previousIter;
|
|
std::multimap<int, Link>::const_iterator linkIter = graph::findLink(_currentLinksMap, nodeId, previousIter->first);
|
|
if(linkIter != _currentLinksMap.end())
|
|
{
|
|
link = linkIter->second;
|
|
if(link.from() != nodeId)
|
|
{
|
|
link = link.inverse();
|
|
}
|
|
}
|
|
}
|
|
if(link.isValid())
|
|
{
|
|
std::map<int, pcl::PointCloud<pcl::PointXYZRGB>::Ptr>::iterator iter = _createdClouds.find(link.to());
|
|
if(iter!=_createdClouds.end() && iter->second->size())
|
|
{
|
|
pcl::PointCloud<pcl::PointXYZRGB>::Ptr previousCloud = util3d::transformPointCloud(iter->second, link.transform());
|
|
cloudFiltered = util3d::subtractFiltering(
|
|
cloud,
|
|
previousCloud,
|
|
_preferencesDialog->getCloudVoxelSize(0),
|
|
_preferencesDialog->getSubstractFilteringMinPts());
|
|
UDEBUG("Filtering %d from %d -> %d", (int)previousCloud->size(), (int)cloud->size(), (int)cloudFiltered->size());
|
|
}
|
|
}
|
|
}
|
|
|
|
if(_preferencesDialog->isCloudMeshing())
|
|
{
|
|
pcl::PolygonMesh::Ptr mesh(new pcl::PolygonMesh);
|
|
if(cloudFiltered->size())
|
|
{
|
|
pcl::PointCloud<pcl::PointXYZRGBNormal>::Ptr cloudWithNormals;
|
|
if(_preferencesDialog->getMeshSmoothing())
|
|
{
|
|
cloudWithNormals = util3d::mls(
|
|
cloudFiltered,
|
|
(float)_preferencesDialog->getMeshSmoothingRadius(),
|
|
false,
|
|
(float)_preferencesDialog->getCloudVoxelSize(0));
|
|
//if(_preferencesDialog->getCloudVoxelSize(0))
|
|
//{
|
|
// cloudWithNormals = util3d::voxelize(cloudWithNormals, _preferencesDialog->getCloudVoxelSize(0));
|
|
//}
|
|
}
|
|
else
|
|
{
|
|
cloudWithNormals = util3d::computeNormals(cloudFiltered, _preferencesDialog->getMeshNormalKSearch());
|
|
}
|
|
mesh = util3d::createMesh(cloudWithNormals, _preferencesDialog->getMeshGP3Radius(), _preferencesDialog->getMeshGP3Mu());
|
|
}
|
|
|
|
if(mesh->polygons.size())
|
|
{
|
|
pcl::PointCloud<pcl::PointXYZRGB>::Ptr tmp(new pcl::PointCloud<pcl::PointXYZRGB>);
|
|
pcl::fromPCLPointCloud2(mesh->cloud, *tmp);
|
|
if(!_ui->widget_cloudViewer->addCloudMesh(cloudName, tmp, mesh->polygons, pose))
|
|
{
|
|
UERROR("Adding mesh cloud %d to viewer failed!", nodeId);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(_preferencesDialog->getMeshSmoothing())
|
|
{
|
|
pcl::PointCloud<pcl::PointXYZRGBNormal>::Ptr cloudWithNormals;
|
|
cloudWithNormals = util3d::mls(
|
|
cloudFiltered,
|
|
(float)_preferencesDialog->getMeshSmoothingRadius(),
|
|
false,
|
|
(float)_preferencesDialog->getCloudVoxelSize(0));
|
|
cloudFiltered.reset(new pcl::PointCloud<pcl::PointXYZRGB>);
|
|
pcl::copyPointCloud(*cloudWithNormals, *cloudFiltered);
|
|
}
|
|
QColor color = Qt::gray;
|
|
if(mapId >= 0)
|
|
{
|
|
color = (Qt::GlobalColor)(mapId+3 % 12 + 7 );
|
|
}
|
|
if(!_ui->widget_cloudViewer->addOrUpdateCloud(cloudName, cloudFiltered, pose, color))
|
|
{
|
|
UERROR("Adding cloud %d to viewer failed!", nodeId);
|
|
}
|
|
}
|
|
}
|
|
else if(iter->getWords3().size())
|
|
{
|
|
QColor color = Qt::gray;
|
|
if(mapId >= 0)
|
|
{
|
|
color = (Qt::GlobalColor)(mapId+3 % 12 + 7 );
|
|
}
|
|
pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZRGB>);
|
|
cloud->resize(iter->getWords3().size());
|
|
int oi=0;
|
|
UASSERT(iter->getWords().size() == iter->getWords3().size());
|
|
std::multimap<int, cv::KeyPoint>::const_iterator kter=iter->getWords().begin();
|
|
for(std::multimap<int, pcl::PointXYZ>::const_iterator jter=iter->getWords3().begin();
|
|
jter!=iter->getWords3().end(); ++jter, ++kter, ++oi)
|
|
{
|
|
(*cloud)[oi].x = jter->second.x;
|
|
(*cloud)[oi].y = jter->second.y;
|
|
(*cloud)[oi].z = jter->second.z;
|
|
int u = kter->second.pt.x+0.5;
|
|
int v = kter->second.pt.x+0.5;
|
|
if(!iter->sensorData().imageRaw().empty() &&
|
|
uIsInBounds(u, 0, iter->sensorData().imageRaw().cols-1) &&
|
|
uIsInBounds(v, 0, iter->sensorData().imageRaw().rows-1))
|
|
{
|
|
if(iter->sensorData().imageRaw().channels() == 1)
|
|
{
|
|
(*cloud)[oi].r = (*cloud)[oi].g = (*cloud)[oi].b = iter->sensorData().imageRaw().at<unsigned char>(u, v);
|
|
}
|
|
else
|
|
{
|
|
cv::Vec3b bgr = iter->sensorData().imageRaw().at<cv::Vec3b>(u, v);
|
|
(*cloud)[oi].r = bgr.val[0];
|
|
(*cloud)[oi].g = bgr.val[1];
|
|
(*cloud)[oi].b = bgr.val[2];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
(*cloud)[oi].r = (*cloud)[oi].g = (*cloud)[oi].b = 255;
|
|
}
|
|
}
|
|
if(!_ui->widget_cloudViewer->addOrUpdateCloud(cloudName, cloud, pose, color))
|
|
{
|
|
UERROR("Adding cloud %d to viewer failed!", nodeId);
|
|
}
|
|
else
|
|
{
|
|
_createdClouds.insert(std::make_pair(nodeId, cloud));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
|
|
_ui->widget_cloudViewer->setCloudOpacity(cloudName, _preferencesDialog->getCloudOpacity(0));
|
|
_ui->widget_cloudViewer->setCloudPointSize(cloudName, _preferencesDialog->getCloudPointSize(0));
|
|
}
|
|
|
|
void MainWindow::createAndAddScanToMap(int nodeId, const Transform & pose, int mapId)
|
|
{
|
|
std::string scanName = uFormat("scan%d", nodeId);
|
|
if(_ui->widget_cloudViewer->getAddedClouds().contains(scanName))
|
|
{
|
|
UERROR("Scan %d already added to map.", nodeId);
|
|
return;
|
|
}
|
|
|
|
QMap<int, Signature>::iterator iter = _cachedSignatures.find(nodeId);
|
|
if(iter == _cachedSignatures.end())
|
|
{
|
|
UERROR("Node %d is not in the cache.", nodeId);
|
|
return;
|
|
}
|
|
|
|
if(!iter->sensorData().laserScanCompressed().empty())
|
|
{
|
|
cv::Mat depth2D;
|
|
iter->sensorData().uncompressData(0, 0, &depth2D);
|
|
|
|
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud;
|
|
cloud = util3d::laserScanToPointCloud(depth2D);
|
|
if(_preferencesDialog->getDownsamplingStepScan(0) > 0)
|
|
{
|
|
cloud = util3d::downsample(cloud, _preferencesDialog->getDownsamplingStepScan(0));
|
|
}
|
|
if(_preferencesDialog->getCloudVoxelSizeScan(0) > 0.0)
|
|
{
|
|
cloud = util3d::voxelize(cloud, _preferencesDialog->getCloudVoxelSizeScan(0));
|
|
}
|
|
QColor color = Qt::gray;
|
|
if(mapId >= 0)
|
|
{
|
|
color = (Qt::GlobalColor)(mapId+3 % 12 + 7 );
|
|
}
|
|
if(!_ui->widget_cloudViewer->addOrUpdateCloud(scanName, cloud, pose, color))
|
|
{
|
|
UERROR("Adding cloud %d to viewer failed!", nodeId);
|
|
}
|
|
else
|
|
{
|
|
_createdScans.insert(std::make_pair(nodeId, cloud));
|
|
|
|
if(depth2D.channels() == 2)
|
|
{
|
|
cv::Mat ground, obstacles;
|
|
util3d::occupancy2DFromLaserScan(depth2D, ground, obstacles, _preferencesDialog->getGridMapResolution());
|
|
_gridLocalMaps.insert(std::make_pair(nodeId, std::make_pair(ground, obstacles)));
|
|
}
|
|
}
|
|
_ui->widget_cloudViewer->setCloudOpacity(scanName, _preferencesDialog->getScanOpacity(0));
|
|
_ui->widget_cloudViewer->setCloudPointSize(scanName, _preferencesDialog->getScanPointSize(0));
|
|
}
|
|
}
|
|
|
|
void MainWindow::updateNodeVisibility(int nodeId, bool visible)
|
|
{
|
|
if(_currentPosesMap.find(nodeId) != _currentPosesMap.end())
|
|
{
|
|
QMap<std::string, Transform> viewerClouds = _ui->widget_cloudViewer->getAddedClouds();
|
|
if(_preferencesDialog->isCloudsShown(0))
|
|
{
|
|
std::string cloudName = uFormat("cloud%d", nodeId);
|
|
if(visible && !viewerClouds.contains(cloudName) && _cachedSignatures.contains(nodeId))
|
|
{
|
|
createAndAddCloudToMap(nodeId, _currentPosesMap.find(nodeId)->second, uValue(_currentMapIds, nodeId, -1));
|
|
}
|
|
else if(viewerClouds.contains(cloudName))
|
|
{
|
|
if(visible)
|
|
{
|
|
//make sure the transformation was done
|
|
_ui->widget_cloudViewer->updateCloudPose(cloudName, _currentPosesMap.find(nodeId)->second);
|
|
}
|
|
_ui->widget_cloudViewer->setCloudVisibility(cloudName, visible);
|
|
}
|
|
}
|
|
|
|
if(_preferencesDialog->isScansShown(0))
|
|
{
|
|
std::string scanName = uFormat("scan%d", nodeId);
|
|
if(visible && !viewerClouds.contains(scanName) && _cachedSignatures.contains(nodeId))
|
|
{
|
|
createAndAddScanToMap(nodeId, _currentPosesMap.find(nodeId)->second, uValue(_currentMapIds, nodeId, -1));
|
|
}
|
|
else if(viewerClouds.contains(scanName))
|
|
{
|
|
if(visible)
|
|
{
|
|
//make sure the transformation was done
|
|
_ui->widget_cloudViewer->updateCloudPose(scanName, _currentPosesMap.find(nodeId)->second);
|
|
}
|
|
_ui->widget_cloudViewer->setCloudVisibility(scanName, visible);
|
|
}
|
|
}
|
|
}
|
|
_ui->widget_cloudViewer->update();
|
|
}
|
|
|
|
void MainWindow::processRtabmapEventInit(int status, const QString & info)
|
|
{
|
|
if((RtabmapEventInit::Status)status == RtabmapEventInit::kInitializing)
|
|
{
|
|
_initProgressDialog->resetProgress();
|
|
_initProgressDialog->show();
|
|
this->changeState(MainWindow::kInitializing);
|
|
}
|
|
else if((RtabmapEventInit::Status)status == RtabmapEventInit::kInitialized)
|
|
{
|
|
_initProgressDialog->setValue(_initProgressDialog->maximumSteps());
|
|
this->changeState(MainWindow::kInitialized);
|
|
}
|
|
else if((RtabmapEventInit::Status)status == RtabmapEventInit::kClosing)
|
|
{
|
|
_initProgressDialog->resetProgress();
|
|
_initProgressDialog->show();
|
|
if(_state!=kApplicationClosing)
|
|
{
|
|
this->changeState(MainWindow::kClosing);
|
|
}
|
|
}
|
|
else if((RtabmapEventInit::Status)status == RtabmapEventInit::kClosed)
|
|
{
|
|
_initProgressDialog->setValue(_initProgressDialog->maximumSteps());
|
|
|
|
if(_databaseUpdated)
|
|
{
|
|
if(!_newDatabasePath.isEmpty())
|
|
{
|
|
if(!_newDatabasePathOutput.isEmpty())
|
|
{
|
|
bool removed = true;
|
|
if(QFile::exists(_newDatabasePathOutput))
|
|
{
|
|
removed = QFile::remove(_newDatabasePathOutput);
|
|
}
|
|
if(removed)
|
|
{
|
|
if(QFile::rename(_newDatabasePath, _newDatabasePathOutput))
|
|
{
|
|
std::string msg = uFormat("Database saved to \"%s\".", _newDatabasePathOutput.toStdString().c_str());
|
|
UINFO(msg.c_str());
|
|
QMessageBox::information(this, tr("Database saved!"), QString(msg.c_str()));
|
|
}
|
|
else
|
|
{
|
|
std::string msg = uFormat("Failed to rename temporary database from \"%s\" to \"%s\".",
|
|
_newDatabasePath.toStdString().c_str(), _newDatabasePathOutput.toStdString().c_str());
|
|
UERROR(msg.c_str());
|
|
QMessageBox::critical(this, tr("Closing failed!"), QString(msg.c_str()));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
std::string msg = uFormat("Failed to overwrite the database \"%s\". The temporary database is still correctly saved at \"%s\".",
|
|
_newDatabasePathOutput.toStdString().c_str(), _newDatabasePath.toStdString().c_str());
|
|
UERROR(msg.c_str());
|
|
QMessageBox::critical(this, tr("Closing failed!"), QString(msg.c_str()));
|
|
}
|
|
}
|
|
else if(QFile::remove(_newDatabasePath))
|
|
{
|
|
UINFO("Deleted temporary database \"%s\".", _newDatabasePath.toStdString().c_str());
|
|
}
|
|
else
|
|
{
|
|
UERROR("Temporary database \"%s\" could not be deleted.", _newDatabasePath.toStdString().c_str());
|
|
}
|
|
|
|
}
|
|
else if(!_openedDatabasePath.isEmpty())
|
|
{
|
|
std::string msg = uFormat("Database \"%s\" updated.", _openedDatabasePath.toStdString().c_str());
|
|
UINFO(msg.c_str());
|
|
QMessageBox::information(this, tr("Database updated!"), QString(msg.c_str()));
|
|
}
|
|
}
|
|
else if(!_newDatabasePath.isEmpty())
|
|
{
|
|
// just remove temporary database;
|
|
if(QFile::remove(_newDatabasePath))
|
|
{
|
|
UINFO("Deleted temporary database \"%s\".", _newDatabasePath.toStdString().c_str());
|
|
}
|
|
else
|
|
{
|
|
UERROR("Temporary database \"%s\" could not be deleted.", _newDatabasePath.toStdString().c_str());
|
|
}
|
|
}
|
|
_openedDatabasePath.clear();
|
|
_newDatabasePath.clear();
|
|
_newDatabasePathOutput.clear();
|
|
bool applicationClosing = _state == kApplicationClosing;
|
|
this->changeState(MainWindow::kIdle);
|
|
if(applicationClosing)
|
|
{
|
|
this->close();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_initProgressDialog->incrementStep();
|
|
QString msg(info);
|
|
if((RtabmapEventInit::Status)status == RtabmapEventInit::kError)
|
|
{
|
|
_openedDatabasePath.clear();
|
|
_newDatabasePath.clear();
|
|
_newDatabasePathOutput.clear();
|
|
_initProgressDialog->setAutoClose(false);
|
|
msg.prepend(tr("[ERROR] "));
|
|
_initProgressDialog->appendText(msg);
|
|
this->changeState(MainWindow::kIdle);
|
|
}
|
|
else
|
|
{
|
|
_initProgressDialog->appendText(msg);
|
|
}
|
|
}
|
|
}
|
|
|
|
void MainWindow::processRtabmapEvent3DMap(const rtabmap::RtabmapEvent3DMap & event)
|
|
{
|
|
_initProgressDialog->appendText("Downloading the map... done.");
|
|
_initProgressDialog->incrementStep();
|
|
|
|
if(event.getCode())
|
|
{
|
|
UERROR("Map received with code error %d!", event.getCode());
|
|
_initProgressDialog->appendText(uFormat("[ERROR] Map received with code error %d!", event.getCode()).c_str());
|
|
_initProgressDialog->setAutoClose(false);
|
|
}
|
|
else
|
|
{
|
|
|
|
_processingDownloadedMap = true;
|
|
UINFO("Received map!");
|
|
_initProgressDialog->appendText(tr(" poses = %1").arg(event.getPoses().size()));
|
|
_initProgressDialog->appendText(tr(" constraints = %1").arg(event.getConstraints().size()));
|
|
|
|
_initProgressDialog->setMaximumSteps(int(event.getSignatures().size()+event.getPoses().size()+1));
|
|
_initProgressDialog->appendText(QString("Inserting data in the cache (%1 signatures downloaded)...").arg(event.getSignatures().size()));
|
|
QApplication::processEvents();
|
|
|
|
int addedSignatures = 0;
|
|
std::map<int, int> mapIds;
|
|
std::map<int, std::string> labels;
|
|
for(std::map<int, Signature>::const_iterator iter = event.getSignatures().begin();
|
|
iter!=event.getSignatures().end();
|
|
++iter)
|
|
{
|
|
mapIds.insert(std::make_pair(iter->first, iter->second.mapId()));
|
|
if(!iter->second.getLabel().empty())
|
|
{
|
|
labels.insert(std::make_pair(iter->first, iter->second.getLabel()));
|
|
}
|
|
if(!_cachedSignatures.contains(iter->first) ||
|
|
(_cachedSignatures.value(iter->first).sensorData().imageCompressed().empty() && !iter->second.sensorData().imageCompressed().empty()))
|
|
{
|
|
_cachedSignatures.insert(iter->first, iter->second);
|
|
++addedSignatures;
|
|
}
|
|
_initProgressDialog->incrementStep();
|
|
QApplication::processEvents();
|
|
}
|
|
_initProgressDialog->appendText(tr("Inserted %1 new signatures.").arg(addedSignatures));
|
|
_initProgressDialog->incrementStep();
|
|
QApplication::processEvents();
|
|
|
|
_initProgressDialog->appendText("Inserting data in the cache... done.");
|
|
|
|
if(event.getPoses().size())
|
|
{
|
|
_initProgressDialog->appendText("Updating the 3D map cloud...");
|
|
_initProgressDialog->incrementStep();
|
|
QApplication::processEvents();
|
|
this->updateMapCloud(event.getPoses(), Transform(), event.getConstraints(), mapIds, labels, true);
|
|
_initProgressDialog->appendText("Updating the 3D map cloud... done.");
|
|
}
|
|
else
|
|
{
|
|
_initProgressDialog->appendText("No poses received! The map cloud cannot be updated...");
|
|
UINFO("Map received is empty! Cannot update the map cloud...");
|
|
}
|
|
|
|
_initProgressDialog->appendText(tr("%1 locations are updated to/inserted in the cache.").arg(event.getPoses().size()));
|
|
|
|
if(!_preferencesDialog->isImagesKept())
|
|
{
|
|
_cachedSignatures.clear();
|
|
}
|
|
if(_state != kMonitoring && _state != kDetecting)
|
|
{
|
|
_ui->actionExport_images_RGB_jpg_Depth_png->setEnabled(!_cachedSignatures.empty() && !_currentPosesMap.empty());
|
|
_ui->actionExport_cameras_in_Bundle_format_out->setEnabled(!_cachedSignatures.empty() && !_currentPosesMap.empty());
|
|
}
|
|
_processingDownloadedMap = false;
|
|
}
|
|
_initProgressDialog->setValue(_initProgressDialog->maximumSteps());
|
|
}
|
|
|
|
void MainWindow::processRtabmapGlobalPathEvent(const rtabmap::RtabmapGlobalPathEvent & event)
|
|
{
|
|
if(!event.getPoses().empty())
|
|
{
|
|
_ui->graphicsView_graphView->setGlobalPath(event.getPoses());
|
|
}
|
|
|
|
_ui->statsToolBox->updateStat("Planning/From/", float(event.getPoses().size()?event.getPoses().begin()->first:0));
|
|
_ui->statsToolBox->updateStat("Planning/Time/ms", float(event.getPlanningTime()*1000.0));
|
|
_ui->statsToolBox->updateStat("Planning/Goal/", float(event.getGoal()));
|
|
_ui->statsToolBox->updateStat("Planning/Poses/", float(event.getPoses().size()));
|
|
_ui->statsToolBox->updateStat("Planning/Length/m", float(graph::computePathLength(event.getPoses())));
|
|
|
|
if(_preferencesDialog->notifyWhenNewGlobalPathIsReceived())
|
|
{
|
|
// use MessageBox
|
|
if(event.getPoses().empty())
|
|
{
|
|
QMessageBox * warn = new QMessageBox(
|
|
QMessageBox::Warning,
|
|
tr("Setting goal failed!"),
|
|
tr("Setting goal to location %1%2 failed. "
|
|
"Some reasons: \n"
|
|
"1) the robot is not yet localized in the map,\n"
|
|
"2) the location doesn't exist in the map,\n"
|
|
"3) the location is not linked to the global map or\n"
|
|
"4) the location is too near of the current location (goal already reached).")
|
|
.arg(event.getGoal())
|
|
.arg(!event.getGoalLabel().empty()?QString(" \"%1\"").arg(event.getGoalLabel().c_str()):""),
|
|
QMessageBox::Ok,
|
|
this);
|
|
warn->setAttribute(Qt::WA_DeleteOnClose, true);
|
|
warn->show();
|
|
}
|
|
else
|
|
{
|
|
QMessageBox * info = new QMessageBox(
|
|
QMessageBox::Information,
|
|
tr("Goal detected!"),
|
|
tr("Global path computed to %1%2 (%3 poses, %4 m).")
|
|
.arg(event.getGoal())
|
|
.arg(!event.getGoalLabel().empty()?QString(" \"%1\"").arg(event.getGoalLabel().c_str()):"")
|
|
.arg(event.getPoses().size())
|
|
.arg(graph::computePathLength(event.getPoses())),
|
|
QMessageBox::Ok,
|
|
this);
|
|
info->setAttribute(Qt::WA_DeleteOnClose, true);
|
|
info->show();
|
|
}
|
|
}
|
|
else if(event.getPoses().empty() && _waypoints.size())
|
|
{
|
|
// resend the same goal
|
|
uSleep(1000);
|
|
this->postGoal(_waypoints.at(_waypointsIndex % _waypoints.size()));
|
|
}
|
|
}
|
|
|
|
void MainWindow::processRtabmapLabelErrorEvent(int id, const QString & label)
|
|
{
|
|
QMessageBox * warn = new QMessageBox(
|
|
QMessageBox::Warning,
|
|
tr("Setting label failed!"),
|
|
tr("Setting label %1 to location %2 failed. "
|
|
"Some reasons: \n"
|
|
"1) the location doesn't exist in the map,\n"
|
|
"2) the location has already a label.").arg(label).arg(id),
|
|
QMessageBox::Ok,
|
|
this);
|
|
warn->setAttribute(Qt::WA_DeleteOnClose, true);
|
|
warn->show();
|
|
}
|
|
|
|
void MainWindow::processRtabmapGoalStatusEvent(int status)
|
|
{
|
|
_ui->widget_console->appendMsg(tr("Goal status received=%1").arg(status), ULogger::kInfo);
|
|
if(_waypoints.size())
|
|
{
|
|
this->postGoal(_waypoints.at(++_waypointsIndex % _waypoints.size()));
|
|
}
|
|
}
|
|
|
|
void MainWindow::applyPrefSettings(PreferencesDialog::PANEL_FLAGS flags)
|
|
{
|
|
ULOGGER_DEBUG("");
|
|
if(flags & PreferencesDialog::kPanelSource)
|
|
{
|
|
// Camera settings...
|
|
_ui->doubleSpinBox_stats_imgRate->setValue(_preferencesDialog->getGeneralInputRate());
|
|
this->updateSelectSourceMenu();
|
|
_ui->label_stats_source->setText(_preferencesDialog->getSourceDriverStr());
|
|
|
|
if(_camera)
|
|
{
|
|
_camera->setImageRate(_preferencesDialog->getGeneralInputRate());
|
|
}
|
|
if(_dbReader)
|
|
{
|
|
_dbReader->setFrameRate( _preferencesDialog->getSourceDatabaseStampsUsed()?-1:_preferencesDialog->getGeneralInputRate());
|
|
}
|
|
}//This will update the statistics toolbox
|
|
|
|
if(flags & PreferencesDialog::kPanelGeneral)
|
|
{
|
|
UDEBUG("General settings changed...");
|
|
setupMainLayout(_preferencesDialog->isVerticalLayoutUsed());
|
|
if(!_preferencesDialog->isPosteriorGraphView() && _ui->graphicsView_graphView->isVisible())
|
|
{
|
|
_ui->graphicsView_graphView->clearPosterior();
|
|
}
|
|
}
|
|
|
|
if(flags & PreferencesDialog::kPanelCloudRendering)
|
|
{
|
|
UDEBUG("Cloud rendering settings changed...");
|
|
if(_currentPosesMap.size())
|
|
{
|
|
this->updateMapCloud(
|
|
std::map<int, Transform>(_currentPosesMap),
|
|
Transform(),
|
|
std::multimap<int, Link>(_currentLinksMap),
|
|
std::map<int, int>(_currentMapIds),
|
|
std::map<int, std::string>(_curentLabels));
|
|
}
|
|
}
|
|
|
|
if(flags & PreferencesDialog::kPanelLogging)
|
|
{
|
|
UDEBUG("Logging settings changed...");
|
|
ULogger::setLevel((ULogger::Level)_preferencesDialog->getGeneralLoggerLevel());
|
|
ULogger::setEventLevel((ULogger::Level)_preferencesDialog->getGeneralLoggerEventLevel());
|
|
ULogger::setType((ULogger::Type)_preferencesDialog->getGeneralLoggerType(),
|
|
(_preferencesDialog->getWorkingDirectory()+QDir::separator()+LOG_FILE_NAME).toStdString(), true);
|
|
ULogger::setPrintTime(_preferencesDialog->getGeneralLoggerPrintTime());
|
|
}
|
|
}
|
|
|
|
void MainWindow::applyPrefSettings(const rtabmap::ParametersMap & parameters)
|
|
{
|
|
applyPrefSettings(parameters, true); //post parameters
|
|
}
|
|
|
|
void MainWindow::applyPrefSettings(const rtabmap::ParametersMap & parameters, bool postParamEvent)
|
|
{
|
|
ULOGGER_DEBUG("");
|
|
if(parameters.size())
|
|
{
|
|
for(rtabmap::ParametersMap::const_iterator iter = parameters.begin(); iter!=parameters.end(); ++iter)
|
|
{
|
|
UDEBUG("Parameter changed: Key=%s Value=%s", iter->first.c_str(), iter->second.c_str());
|
|
}
|
|
|
|
rtabmap::ParametersMap parametersModified = parameters;
|
|
|
|
if(_state != kIdle && parametersModified.size())
|
|
{
|
|
if(parametersModified.erase(Parameters::kRtabmapWorkingDirectory()))
|
|
{
|
|
if(_state == kMonitoring || _state == kMonitoringPaused)
|
|
{
|
|
QMessageBox::information(this, tr("Working memory changed"), tr("The remote working directory can't be changed while the interface is in monitoring mode."));
|
|
}
|
|
else
|
|
{
|
|
QMessageBox::information(this, tr("Working memory changed"), tr("The working directory can't be changed while the detector is running. This will be applied when the detector will stop."));
|
|
}
|
|
}
|
|
if(postParamEvent)
|
|
{
|
|
this->post(new ParamEvent(parametersModified));
|
|
}
|
|
}
|
|
|
|
if(_state != kMonitoring && _state != kMonitoringPaused &&
|
|
uContains(parameters, Parameters::kRtabmapWorkingDirectory()))
|
|
{
|
|
_ui->statsToolBox->setWorkingDirectory(_preferencesDialog->getWorkingDirectory());
|
|
_ui->graphicsView_graphView->setWorkingDirectory(_preferencesDialog->getWorkingDirectory());
|
|
_ui->widget_cloudViewer->setWorkingDirectory(_preferencesDialog->getWorkingDirectory());
|
|
}
|
|
|
|
// update loop closure viewer parameters (Use Map parameters)
|
|
_ui->widget_loopClosureViewer->setDecimation(_preferencesDialog->getCloudDecimation(0));
|
|
_ui->widget_loopClosureViewer->setMaxDepth(_preferencesDialog->getCloudMaxDepth(0));
|
|
|
|
// update graph view parameters
|
|
if(uContains(parameters, Parameters::kRGBDLocalRadius()))
|
|
{
|
|
_ui->graphicsView_graphView->setLocalRadius(uStr2Float(parameters.at(Parameters::kRGBDLocalRadius())));
|
|
}
|
|
}
|
|
|
|
//update ui
|
|
_ui->doubleSpinBox_stats_detectionRate->setValue(_preferencesDialog->getDetectionRate());
|
|
_ui->doubleSpinBox_stats_timeLimit->setValue(_preferencesDialog->getTimeLimit());
|
|
_ui->actionSLAM_mode->setChecked(_preferencesDialog->isSLAMMode());
|
|
|
|
float value;
|
|
value = float(_preferencesDialog->getLoopThr());
|
|
emit(loopClosureThrChanged(value));
|
|
}
|
|
|
|
void MainWindow::drawKeypoints(const std::multimap<int, cv::KeyPoint> & refWords, const std::multimap<int, cv::KeyPoint> & loopWords)
|
|
{
|
|
UTimer timer;
|
|
|
|
timer.start();
|
|
ULOGGER_DEBUG("refWords.size() = %d", refWords.size());
|
|
for(std::multimap<int, cv::KeyPoint>::const_iterator iter = refWords.begin(); iter != refWords.end(); ++iter )
|
|
{
|
|
int id = iter->first;
|
|
QColor color;
|
|
if(uContains(loopWords, id))
|
|
{
|
|
// PINK = FOUND IN LOOP SIGNATURE
|
|
color = Qt::magenta;
|
|
}
|
|
else if(_lastIds.contains(id))
|
|
{
|
|
// BLUE = FOUND IN LAST SIGNATURE
|
|
color = Qt::blue;
|
|
}
|
|
else if(id<=_lastId)
|
|
{
|
|
// RED = ALREADY EXISTS
|
|
color = Qt::red;
|
|
}
|
|
else if(refWords.count(id) > 1)
|
|
{
|
|
// YELLOW = NEW and multiple times
|
|
color = Qt::yellow;
|
|
}
|
|
else
|
|
{
|
|
// GREEN = NEW
|
|
color = Qt::green;
|
|
}
|
|
_ui->imageView_source->addFeature(iter->first, iter->second, 0, color);
|
|
}
|
|
ULOGGER_DEBUG("source time = %f s", timer.ticks());
|
|
|
|
timer.start();
|
|
ULOGGER_DEBUG("loopWords.size() = %d", loopWords.size());
|
|
QList<QPair<cv::Point2f, cv::Point2f> > uniqueCorrespondences;
|
|
for(std::multimap<int, cv::KeyPoint>::const_iterator iter = loopWords.begin(); iter != loopWords.end(); ++iter )
|
|
{
|
|
int id = iter->first;
|
|
QColor color;
|
|
if(uContains(refWords, id))
|
|
{
|
|
// PINK = FOUND IN LOOP SIGNATURE
|
|
color = Qt::magenta;
|
|
//To draw lines... get only unique correspondences
|
|
if(uValues(refWords, id).size() == 1 && uValues(loopWords, id).size() == 1)
|
|
{
|
|
const cv::KeyPoint & a = refWords.find(id)->second;
|
|
const cv::KeyPoint & b = iter->second;
|
|
uniqueCorrespondences.push_back(QPair<cv::Point2f, cv::Point2f>(a.pt, b.pt));
|
|
}
|
|
}
|
|
else if(id<=_lastId)
|
|
{
|
|
// RED = ALREADY EXISTS
|
|
color = Qt::red;
|
|
}
|
|
else if(refWords.count(id) > 1)
|
|
{
|
|
// YELLOW = NEW and multiple times
|
|
color = Qt::yellow;
|
|
}
|
|
else
|
|
{
|
|
// GREEN = NEW
|
|
color = Qt::green;
|
|
}
|
|
_ui->imageView_loopClosure->addFeature(iter->first, iter->second, 0, color);
|
|
}
|
|
|
|
ULOGGER_DEBUG("loop closure time = %f s", timer.ticks());
|
|
|
|
if(refWords.size()>0)
|
|
{
|
|
if((*refWords.rbegin()).first > _lastId)
|
|
{
|
|
_lastId = (*refWords.rbegin()).first;
|
|
}
|
|
_lastIds = QSet<int>::fromList(QList<int>::fromStdList(uKeysList(refWords)));
|
|
}
|
|
|
|
// Draw lines between corresponding features...
|
|
float scaleSource = _ui->imageView_source->viewScale();
|
|
float scaleLoop = _ui->imageView_loopClosure->viewScale();
|
|
UDEBUG("scale source=%f loop=%f", scaleSource, scaleLoop);
|
|
// Delta in actual window pixels
|
|
float sourceMarginX = (_ui->imageView_source->width() - _ui->imageView_source->sceneRect().width()*scaleSource)/2.0f;
|
|
float sourceMarginY = (_ui->imageView_source->height() - _ui->imageView_source->sceneRect().height()*scaleSource)/2.0f;
|
|
float loopMarginX = (_ui->imageView_loopClosure->width() - _ui->imageView_loopClosure->sceneRect().width()*scaleLoop)/2.0f;
|
|
float loopMarginY = (_ui->imageView_loopClosure->height() - _ui->imageView_loopClosure->sceneRect().height()*scaleLoop)/2.0f;
|
|
|
|
float deltaX = 0;
|
|
float deltaY = 0;
|
|
|
|
if(_preferencesDialog->isVerticalLayoutUsed())
|
|
{
|
|
deltaY = _ui->label_matchId->height() + _ui->imageView_source->height();
|
|
}
|
|
else
|
|
{
|
|
deltaX = _ui->imageView_source->width();
|
|
}
|
|
|
|
for(QList<QPair<cv::Point2f, cv::Point2f> >::iterator iter = uniqueCorrespondences.begin();
|
|
iter!=uniqueCorrespondences.end();
|
|
++iter)
|
|
{
|
|
|
|
_ui->imageView_source->addLine(
|
|
iter->first.x,
|
|
iter->first.y,
|
|
(iter->second.x*scaleLoop+loopMarginX+deltaX-sourceMarginX)/scaleSource,
|
|
(iter->second.y*scaleLoop+loopMarginY+deltaY-sourceMarginY)/scaleSource,
|
|
Qt::cyan);
|
|
|
|
_ui->imageView_loopClosure->addLine(
|
|
(iter->first.x*scaleSource+sourceMarginX-deltaX-loopMarginX)/scaleLoop,
|
|
(iter->first.y*scaleSource+sourceMarginY-deltaY-loopMarginY)/scaleLoop,
|
|
iter->second.x,
|
|
iter->second.y,
|
|
Qt::cyan);
|
|
}
|
|
_ui->imageView_source->update();
|
|
_ui->imageView_loopClosure->update();
|
|
}
|
|
|
|
void MainWindow::showEvent(QShowEvent* anEvent)
|
|
{
|
|
//if the config file doesn't exist, make the GUI obsolete
|
|
this->setWindowModified(!QFile::exists(_preferencesDialog->getIniFilePath()));
|
|
}
|
|
|
|
void MainWindow::moveEvent(QMoveEvent* anEvent)
|
|
{
|
|
if(this->isVisible())
|
|
{
|
|
// HACK, there is a move event when the window is shown the first time.
|
|
if(!_firstCall)
|
|
{
|
|
this->configGUIModified();
|
|
}
|
|
_firstCall = false;
|
|
}
|
|
}
|
|
|
|
void MainWindow::resizeEvent(QResizeEvent* anEvent)
|
|
{
|
|
if(this->isVisible())
|
|
{
|
|
this->configGUIModified();
|
|
}
|
|
}
|
|
|
|
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
|
|
{
|
|
if (event->type() == QEvent::Resize && qobject_cast<QDockWidget*>(obj))
|
|
{
|
|
this->setWindowModified(true);
|
|
}
|
|
return QWidget::eventFilter(obj, event);
|
|
}
|
|
|
|
void MainWindow::updateSelectSourceMenu()
|
|
{
|
|
_ui->actionUsbCamera->setChecked(_preferencesDialog->getSourceDriver() == PreferencesDialog::kSrcUsbDevice);
|
|
|
|
_ui->actionMore_options->setChecked(
|
|
_preferencesDialog->getSourceDriver() == PreferencesDialog::kSrcDatabase ||
|
|
_preferencesDialog->getSourceDriver() == PreferencesDialog::kSrcImages ||
|
|
_preferencesDialog->getSourceDriver() == PreferencesDialog::kSrcVideo ||
|
|
_preferencesDialog->getSourceDriver() == PreferencesDialog::kSrcStereoImages);
|
|
|
|
_ui->actionOpenNI_PCL->setChecked(_preferencesDialog->getSourceDriver() == PreferencesDialog::kSrcOpenNI_PCL);
|
|
_ui->actionOpenNI_PCL_ASUS->setChecked(_preferencesDialog->getSourceDriver() == PreferencesDialog::kSrcOpenNI_PCL);
|
|
_ui->actionFreenect->setChecked(_preferencesDialog->getSourceDriver() == PreferencesDialog::kSrcFreenect);
|
|
_ui->actionOpenNI_CV->setChecked(_preferencesDialog->getSourceDriver() == PreferencesDialog::kSrcOpenNI_CV);
|
|
_ui->actionOpenNI_CV_ASUS->setChecked(_preferencesDialog->getSourceDriver() == PreferencesDialog::kSrcOpenNI_CV_ASUS);
|
|
_ui->actionOpenNI2->setChecked(_preferencesDialog->getSourceDriver() == PreferencesDialog::kSrcOpenNI2);
|
|
_ui->actionOpenNI2_kinect->setChecked(_preferencesDialog->getSourceDriver() == PreferencesDialog::kSrcOpenNI2);
|
|
_ui->actionOpenNI2_sense->setChecked(_preferencesDialog->getSourceDriver() == PreferencesDialog::kSrcOpenNI2);
|
|
_ui->actionFreenect2->setChecked(_preferencesDialog->getSourceDriver() == PreferencesDialog::kSrcFreenect2);
|
|
_ui->actionStereoDC1394->setChecked(_preferencesDialog->getSourceDriver() == PreferencesDialog::kSrcDC1394);
|
|
_ui->actionStereoFlyCapture2->setChecked(_preferencesDialog->getSourceDriver() == PreferencesDialog::kSrcFlyCapture2);
|
|
}
|
|
|
|
void MainWindow::changeImgRateSetting()
|
|
{
|
|
emit imgRateChanged(_ui->doubleSpinBox_stats_imgRate->value());
|
|
}
|
|
|
|
void MainWindow::changeDetectionRateSetting()
|
|
{
|
|
emit detectionRateChanged(_ui->doubleSpinBox_stats_detectionRate->value());
|
|
}
|
|
|
|
void MainWindow::changeTimeLimitSetting()
|
|
{
|
|
emit timeLimitChanged((float)_ui->doubleSpinBox_stats_timeLimit->value());
|
|
}
|
|
|
|
void MainWindow::changeMappingMode()
|
|
{
|
|
emit mappingModeChanged(_ui->actionSLAM_mode->isChecked());
|
|
}
|
|
|
|
QString MainWindow::captureScreen(bool cacheInRAM)
|
|
{
|
|
QString name = (QDateTime::currentDateTime().toString("yyMMddhhmmsszzz") + ".png");
|
|
_ui->statusbar->clearMessage();
|
|
QPixmap figure = QPixmap::grabWidget(this);
|
|
|
|
QString targetDir = _preferencesDialog->getWorkingDirectory() + QDir::separator() + "ScreensCaptured";
|
|
QString msg;
|
|
if(cacheInRAM)
|
|
{
|
|
msg = tr("Screen captured \"%1\"").arg(name);
|
|
QByteArray bytes;
|
|
QBuffer buffer(&bytes);
|
|
buffer.open(QIODevice::WriteOnly);
|
|
figure.save(&buffer, "PNG");
|
|
_autoScreenCaptureCachedImages.insert(name, bytes);
|
|
}
|
|
else
|
|
{
|
|
QDir dir;
|
|
if(!dir.exists(targetDir))
|
|
{
|
|
dir.mkdir(targetDir);
|
|
}
|
|
targetDir += QDir::separator();
|
|
targetDir += "Main_window";
|
|
if(!dir.exists(targetDir))
|
|
{
|
|
dir.mkdir(targetDir);
|
|
}
|
|
targetDir += QDir::separator();
|
|
|
|
figure.save(targetDir + name);
|
|
msg = tr("Screen captured \"%1\"").arg(targetDir + name);
|
|
}
|
|
_ui->statusbar->showMessage(msg, _preferencesDialog->getTimeLimit()*500);
|
|
_ui->widget_console->appendMsg(msg);
|
|
|
|
return targetDir + name;
|
|
}
|
|
|
|
void MainWindow::beep()
|
|
{
|
|
QApplication::beep();
|
|
}
|
|
|
|
void MainWindow::configGUIModified()
|
|
{
|
|
this->setWindowModified(true);
|
|
}
|
|
|
|
//ACTIONS
|
|
void MainWindow::saveConfigGUI()
|
|
{
|
|
_savedMaximized = this->isMaximized();
|
|
_preferencesDialog->saveMainWindowState(this);
|
|
_preferencesDialog->saveWindowGeometry(_preferencesDialog);
|
|
_preferencesDialog->saveWindowGeometry(_aboutDialog);
|
|
_preferencesDialog->saveWidgetState(_ui->widget_cloudViewer);
|
|
_preferencesDialog->saveWidgetState(_ui->imageView_source);
|
|
_preferencesDialog->saveWidgetState(_ui->imageView_loopClosure);
|
|
_preferencesDialog->saveWidgetState(_ui->imageView_odometry);
|
|
_preferencesDialog->saveWidgetState(_exportDialog);
|
|
_preferencesDialog->saveWidgetState(_postProcessingDialog);
|
|
_preferencesDialog->saveWidgetState(_ui->graphicsView_graphView);
|
|
_preferencesDialog->saveSettings();
|
|
this->saveFigures();
|
|
this->setWindowModified(false);
|
|
}
|
|
|
|
void MainWindow::newDatabase()
|
|
{
|
|
if(_state != MainWindow::kIdle)
|
|
{
|
|
UERROR("This method can be called only in IDLE state.");
|
|
return;
|
|
}
|
|
_openedDatabasePath.clear();
|
|
_newDatabasePath.clear();
|
|
_newDatabasePathOutput.clear();
|
|
_databaseUpdated = false;
|
|
ULOGGER_DEBUG("");
|
|
this->clearTheCache();
|
|
std::string databasePath = (_preferencesDialog->getWorkingDirectory()+QDir::separator()+QString("rtabmap.tmp.db")).toStdString();
|
|
if(QFile::exists(databasePath.c_str()))
|
|
{
|
|
int r = QMessageBox::question(this,
|
|
tr("Creating temporary database"),
|
|
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);
|
|
|
|
if(r == QMessageBox::Yes)
|
|
{
|
|
if(QFile::remove(databasePath.c_str()))
|
|
{
|
|
UINFO("Deleted temporary database \"%s\".", databasePath.c_str());
|
|
}
|
|
else
|
|
{
|
|
UERROR("Temporary database \"%s\" could not be deleted!", databasePath.c_str());
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
_newDatabasePath = databasePath.c_str();
|
|
this->post(new RtabmapEventCmd(RtabmapEventCmd::kCmdInit, databasePath, _preferencesDialog->getAllParameters()));
|
|
applyPrefSettings(_preferencesDialog->getAllParameters(), false);
|
|
}
|
|
|
|
void MainWindow::openDatabase()
|
|
{
|
|
if(_state != MainWindow::kIdle)
|
|
{
|
|
UERROR("This method can be called only in IDLE state.");
|
|
return;
|
|
}
|
|
_openedDatabasePath.clear();
|
|
_newDatabasePath.clear();
|
|
_newDatabasePathOutput.clear();
|
|
_databaseUpdated = false;
|
|
QString path = QFileDialog::getOpenFileName(this, tr("Open database..."), _preferencesDialog->getWorkingDirectory(), tr("RTAB-Map database files (*.db)"));
|
|
if(!path.isEmpty())
|
|
{
|
|
this->clearTheCache();
|
|
_openedDatabasePath = path;
|
|
this->post(new RtabmapEventCmd(RtabmapEventCmd::kCmdInit, path.toStdString(), 0, _preferencesDialog->getAllParameters()));
|
|
}
|
|
applyPrefSettings(_preferencesDialog->getAllParameters(), false);
|
|
}
|
|
|
|
bool MainWindow::closeDatabase()
|
|
{
|
|
if(_state != MainWindow::kInitialized)
|
|
{
|
|
UERROR("This method can be called only in INITIALIZED state.");
|
|
return false;
|
|
}
|
|
|
|
_newDatabasePathOutput.clear();
|
|
if(!_newDatabasePath.isEmpty() && _databaseUpdated)
|
|
{
|
|
QMessageBox::Button b = QMessageBox::question(this,
|
|
tr("Save database"),
|
|
tr("Save the new database?"),
|
|
QMessageBox::Save | QMessageBox::Cancel | QMessageBox::Discard,
|
|
QMessageBox::Save);
|
|
|
|
if(b == QMessageBox::Save)
|
|
{
|
|
// Temp database used, automatically backup with unique name (timestamp)
|
|
QString newName = QDateTime::currentDateTime().toString("yyMMdd-hhmmss");
|
|
QString newPath = _preferencesDialog->getWorkingDirectory()+QDir::separator()+newName+".db";
|
|
|
|
newPath = QFileDialog::getSaveFileName(this, tr("Save database"), newPath, tr("RTAB-Map database files (*.db)"));
|
|
if(newPath.isEmpty())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if(QFileInfo(newPath).suffix() == "")
|
|
{
|
|
newPath += ".db";
|
|
}
|
|
|
|
_newDatabasePathOutput = newPath;
|
|
}
|
|
else if(b != QMessageBox::Discard)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
this->post(new RtabmapEventCmd(RtabmapEventCmd::kCmdClose));
|
|
return true;
|
|
}
|
|
|
|
void MainWindow::editDatabase()
|
|
{
|
|
if(_state != MainWindow::kIdle)
|
|
{
|
|
UERROR("This method can be called only in IDLE state.");
|
|
return;
|
|
}
|
|
QString path = QFileDialog::getOpenFileName(this, tr("Edit database..."), _preferencesDialog->getWorkingDirectory(), tr("RTAB-Map database files (*.db)"));
|
|
if(!path.isEmpty())
|
|
{
|
|
DatabaseViewer * viewer = new DatabaseViewer(this);
|
|
viewer->setWindowModality(Qt::WindowModal);
|
|
viewer->setAttribute(Qt::WA_DeleteOnClose, true);
|
|
viewer->showCloseButton();
|
|
|
|
if(viewer->isSavedMaximized())
|
|
{
|
|
viewer->showMaximized();
|
|
}
|
|
else
|
|
{
|
|
viewer->show();
|
|
}
|
|
|
|
viewer->openDatabase(path);
|
|
}
|
|
}
|
|
|
|
void MainWindow::startDetection()
|
|
{
|
|
ParametersMap parameters = _preferencesDialog->getAllParameters();
|
|
// verify source with input rates
|
|
if(_preferencesDialog->getSourceDriver() == PreferencesDialog::kSrcImages ||
|
|
_preferencesDialog->getSourceDriver() == PreferencesDialog::kSrcVideo ||
|
|
_preferencesDialog->getSourceDriver() == PreferencesDialog::kSrcRGBDImages ||
|
|
_preferencesDialog->getSourceDriver() == PreferencesDialog::kSrcStereoImages ||
|
|
_preferencesDialog->getSourceDriver() == PreferencesDialog::kSrcStereoVideo ||
|
|
_preferencesDialog->getSourceDriver() == PreferencesDialog::kSrcDatabase)
|
|
{
|
|
float inputRate = _preferencesDialog->getGeneralInputRate();
|
|
float detectionRate = uStr2Float(parameters.at(Parameters::kRtabmapDetectionRate()));
|
|
int bufferingSize = uStr2Float(parameters.at(Parameters::kRtabmapImageBufferSize()));
|
|
if(((detectionRate!=0.0f && detectionRate <= inputRate) || (detectionRate > 0.0f && inputRate == 0.0f)) &&
|
|
(_preferencesDialog->getSourceDriver() != PreferencesDialog::kSrcDatabase || !_preferencesDialog->getSourceDatabaseStampsUsed()))
|
|
{
|
|
int button = QMessageBox::question(this,
|
|
tr("Incompatible frame rates!"),
|
|
tr("\"Source/Input rate\" (%1 Hz) is equal to/higher than \"RTAB-Map/Detection rate\" (%2 Hz). As the "
|
|
"source input is a directory of images/video/database, some images may be "
|
|
"skipped by the detector. You may want to increase the \"RTAB-Map/Detection rate\" over "
|
|
"the \"Source/Input rate\" to guaranty that all images are processed. Would you want to "
|
|
"start the detection anyway?").arg(inputRate).arg(detectionRate),
|
|
QMessageBox::Yes | QMessageBox::No);
|
|
if(button == QMessageBox::No)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
if(bufferingSize != 0 &&
|
|
(_preferencesDialog->getSourceDriver() != PreferencesDialog::kSrcDatabase || !_preferencesDialog->getSourceDatabaseStampsUsed()))
|
|
{
|
|
int button = QMessageBox::question(this,
|
|
tr("Some images may be skipped!"),
|
|
tr("\"RTAB-Map/Images buffer size\" is not infinite (size=%1). As the "
|
|
"source input is a directory of images/video/database, some images may be "
|
|
"skipped by the detector if the \"Source/Input rate\" (which is %2 Hz) is higher than the "
|
|
"rate at which RTAB-Map can process the images. You may want to set the "
|
|
"\"RTAB-Map/Images buffer size\" to 0 (infinite) to guaranty that all "
|
|
"images are processed. Would you want to start the detection "
|
|
"anyway?").arg(bufferingSize).arg(inputRate),
|
|
QMessageBox::Yes | QMessageBox::No);
|
|
if(button == QMessageBox::No)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!_preferencesDialog->isCloudsShown(0) || !_preferencesDialog->isScansShown(0))
|
|
{
|
|
QMessageBox::information(this,
|
|
tr("Some data may not be shown!"),
|
|
tr("Note that clouds and/or scans visibility settings are set to "
|
|
"OFF (see General->\"3D Rendering\" section under Map column)."));
|
|
}
|
|
|
|
UDEBUG("");
|
|
emit stateChanged(kStartingDetection);
|
|
|
|
if(_camera != 0)
|
|
{
|
|
QMessageBox::warning(this,
|
|
tr("RTAB-Map"),
|
|
tr("A camera is running, stop it first."));
|
|
UWARN("_camera is not null... it must be stopped first");
|
|
emit stateChanged(kInitialized);
|
|
return;
|
|
}
|
|
if(_dbReader != 0)
|
|
{
|
|
QMessageBox::warning(this,
|
|
tr("RTAB-Map"),
|
|
tr("A database reader is running, stop it first."));
|
|
UWARN("_dbReader is not null... it must be stopped first");
|
|
emit stateChanged(kInitialized);
|
|
return;
|
|
}
|
|
|
|
// Adjust pre-requirements
|
|
if(_preferencesDialog->getSourceDriver() == PreferencesDialog::kSrcUndef)
|
|
{
|
|
QMessageBox::warning(this,
|
|
tr("RTAB-Map"),
|
|
tr("No sources are selected. See Preferences->Source panel."));
|
|
UWARN("No sources are selected. See Preferences->Source panel.");
|
|
emit stateChanged(kInitialized);
|
|
return;
|
|
}
|
|
|
|
|
|
if(_preferencesDialog->getSourceDriver() < PreferencesDialog::kSrcDatabase)
|
|
{
|
|
Camera * camera = _preferencesDialog->createCamera();
|
|
if(!camera)
|
|
{
|
|
emit stateChanged(kInitialized);
|
|
return;
|
|
}
|
|
|
|
_camera = new CameraThread(camera);
|
|
_camera->setMirroringEnabled(_preferencesDialog->isSourceMirroring());
|
|
_camera->setColorOnly(_preferencesDialog->isSourceRGBDColorOnly());
|
|
_camera->setStereoToDepth(_preferencesDialog->isSourceStereoDepthGenerated());
|
|
|
|
//Create odometry thread if rgbd slam
|
|
if(uStr2Bool(parameters.at(Parameters::kRGBDEnabled()).c_str()))
|
|
{
|
|
// Require calibrated camera
|
|
if(!camera->isCalibrated())
|
|
{
|
|
UWARN("Camera is not calibrated!");
|
|
emit stateChanged(kInitialized);
|
|
delete _camera;
|
|
_camera = 0;
|
|
|
|
int button = QMessageBox::question(this,
|
|
tr("Camera is not calibrated!"),
|
|
tr("RTAB-Map cannot run with an uncalibrated camera. Do you want to calibrate the camera now?"),
|
|
QMessageBox::Yes | QMessageBox::No);
|
|
if(button == QMessageBox::Yes)
|
|
{
|
|
QTimer::singleShot(0, _preferencesDialog, SLOT(calibrate()));
|
|
}
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
if(_odomThread)
|
|
{
|
|
UERROR("OdomThread must be already deleted here?!");
|
|
delete _odomThread;
|
|
_odomThread = 0;
|
|
}
|
|
Odometry * odom;
|
|
if(_preferencesDialog->getOdomStrategy() == 1)
|
|
{
|
|
odom = new OdometryOpticalFlow(parameters);
|
|
}
|
|
else if(_preferencesDialog->getOdomStrategy() == 2)
|
|
{
|
|
odom = new OdometryMono(parameters);
|
|
}
|
|
else
|
|
{
|
|
odom = new OdometryBOW(parameters);
|
|
}
|
|
_odomThread = new OdometryThread(odom, _preferencesDialog->getOdomBufferSize());
|
|
|
|
UEventsManager::addHandler(_odomThread);
|
|
UEventsManager::createPipe(_camera, _odomThread, "CameraEvent");
|
|
UEventsManager::createPipe(_camera, this, "CameraEvent");
|
|
_odomThread->start();
|
|
}
|
|
}
|
|
}
|
|
else if(_preferencesDialog->getSourceDriver() == PreferencesDialog::kSrcDatabase)
|
|
{
|
|
_dbReader = new DBReader(_preferencesDialog->getSourceDatabasePath().toStdString(),
|
|
_preferencesDialog->getSourceDatabaseStampsUsed()?-1:_preferencesDialog->getGeneralInputRate(),
|
|
_preferencesDialog->getSourceDatabaseOdometryIgnored(),
|
|
_preferencesDialog->getSourceDatabaseGoalDelayIgnored(),
|
|
_preferencesDialog->getSourceDatabaseGoalsIgnored());
|
|
|
|
//Create odometry thread if rgdb slam
|
|
if(uStr2Bool(parameters.at(Parameters::kRGBDEnabled()).c_str()) &&
|
|
_preferencesDialog->getSourceDatabaseOdometryIgnored())
|
|
{
|
|
if(_odomThread)
|
|
{
|
|
UERROR("OdomThread must be already deleted here?!");
|
|
delete _odomThread;
|
|
}
|
|
Odometry * odom;
|
|
if(_preferencesDialog->getOdomStrategy() == 1)
|
|
{
|
|
odom = new OdometryOpticalFlow(parameters);
|
|
}
|
|
else if(_preferencesDialog->getOdomStrategy() == 2)
|
|
{
|
|
odom = new OdometryMono(parameters);
|
|
}
|
|
else
|
|
{
|
|
odom = new OdometryBOW(parameters);
|
|
}
|
|
_odomThread = new OdometryThread(odom, _preferencesDialog->getOdomBufferSize());
|
|
|
|
UEventsManager::addHandler(_odomThread);
|
|
_odomThread->start();
|
|
}
|
|
|
|
if(!_dbReader->init(_preferencesDialog->getSourceDatabaseStartPos()))
|
|
{
|
|
ULOGGER_WARN("init DBReader failed... ");
|
|
QMessageBox::warning(this,
|
|
tr("RTAB-Map"),
|
|
tr("Database reader initialization failed..."));
|
|
emit stateChanged(kInitialized);
|
|
delete _dbReader;
|
|
_dbReader = 0;
|
|
if(_odomThread)
|
|
{
|
|
delete _odomThread;
|
|
_odomThread = 0;
|
|
}
|
|
return;
|
|
}
|
|
|
|
if(_odomThread)
|
|
{
|
|
UEventsManager::createPipe(_dbReader, _odomThread, "CameraEvent");
|
|
UEventsManager::createPipe(_dbReader, this, "CameraEvent");
|
|
}
|
|
}
|
|
|
|
if(_dataRecorder)
|
|
{
|
|
if(_camera)
|
|
{
|
|
UEventsManager::createPipe(_camera, _dataRecorder, "CameraEvent");
|
|
}
|
|
else if(_dbReader)
|
|
{
|
|
UEventsManager::createPipe(_dbReader, _dataRecorder, "CameraEvent");
|
|
}
|
|
}
|
|
|
|
_lastOdomPose.setNull();
|
|
this->post(new RtabmapEventCmd(RtabmapEventCmd::kCmdCleanDataBuffer)); // clean sensors buffer
|
|
this->post(new RtabmapEventCmd(RtabmapEventCmd::kCmdTriggerNewMap)); // Trigger a new map
|
|
|
|
if(_odomThread)
|
|
{
|
|
_ui->actionReset_Odometry->setEnabled(true);
|
|
}
|
|
|
|
if(!_preferencesDialog->isStatisticsPublished())
|
|
{
|
|
QMessageBox::information(this,
|
|
tr("Information"),
|
|
tr("Note that publishing statistics is disabled, "
|
|
"progress will not be shown in the GUI."));
|
|
}
|
|
|
|
emit stateChanged(kDetecting);
|
|
}
|
|
|
|
// Could not be in the main thread here! (see handleEvents())
|
|
void MainWindow::pauseDetection()
|
|
{
|
|
if(_camera || _dbReader)
|
|
{
|
|
if(_state == kPaused && (QApplication::keyboardModifiers() & Qt::ShiftModifier))
|
|
{
|
|
// On Ctrl-click, start the camera and pause it automatically
|
|
emit stateChanged(kPaused);
|
|
if(_preferencesDialog->getGeneralInputRate())
|
|
{
|
|
QTimer::singleShot(1000.0/_preferencesDialog->getGeneralInputRate() + 10, this, SLOT(pauseDetection()));
|
|
}
|
|
else
|
|
{
|
|
emit stateChanged(kPaused);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
emit stateChanged(kPaused);
|
|
}
|
|
}
|
|
else if(_state == kMonitoring)
|
|
{
|
|
UINFO("Sending pause event!");
|
|
emit stateChanged(kMonitoringPaused);
|
|
}
|
|
else if(_state == kMonitoringPaused)
|
|
{
|
|
UINFO("Sending unpause event!");
|
|
emit stateChanged(kMonitoring);
|
|
}
|
|
}
|
|
|
|
void MainWindow::stopDetection()
|
|
{
|
|
if(!_camera && !_dbReader && !_odomThread)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if(_state == kDetecting &&
|
|
( (_camera && _camera->isRunning()) ||
|
|
(_dbReader && _dbReader->isRunning()) ) )
|
|
{
|
|
QMessageBox::StandardButton button = QMessageBox::question(this, tr("Stopping process..."), tr("Are you sure you want to stop the process?"), QMessageBox::Yes|QMessageBox::No, QMessageBox::No);
|
|
|
|
if(button != QMessageBox::Yes)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
ULOGGER_DEBUG("");
|
|
// kill the processes
|
|
if(_camera)
|
|
{
|
|
_camera->join(true);
|
|
}
|
|
|
|
if(_dbReader)
|
|
{
|
|
_dbReader->join(true);
|
|
}
|
|
|
|
if(_odomThread)
|
|
{
|
|
_ui->actionReset_Odometry->setEnabled(false);
|
|
_odomThread->kill();
|
|
}
|
|
|
|
// delete the processes
|
|
if(_camera)
|
|
{
|
|
delete _camera;
|
|
_camera = 0;
|
|
}
|
|
if(_dbReader)
|
|
{
|
|
delete _dbReader;
|
|
_dbReader = 0;
|
|
}
|
|
if(_odomThread)
|
|
{
|
|
delete _odomThread;
|
|
_odomThread = 0;
|
|
}
|
|
|
|
if(_dataRecorder)
|
|
{
|
|
delete _dataRecorder;
|
|
_dataRecorder = 0;
|
|
}
|
|
|
|
emit stateChanged(kInitialized);
|
|
}
|
|
|
|
void MainWindow::notifyNoMoreImages()
|
|
{
|
|
QMessageBox::information(this,
|
|
tr("No more images..."),
|
|
tr("The camera has reached the end of the stream."));
|
|
}
|
|
|
|
void MainWindow::printLoopClosureIds()
|
|
{
|
|
_ui->dockWidget_console->show();
|
|
QString msgRef;
|
|
QString msgLoop;
|
|
for(int i = 0; i<_refIds.size(); ++i)
|
|
{
|
|
msgRef.append(QString::number(_refIds[i]));
|
|
msgLoop.append(QString::number(_loopClosureIds[i]));
|
|
if(i < _refIds.size() - 1)
|
|
{
|
|
msgRef.append(" ");
|
|
msgLoop.append(" ");
|
|
}
|
|
}
|
|
_ui->widget_console->appendMsg(QString("IDs = [%1];").arg(msgRef));
|
|
_ui->widget_console->appendMsg(QString("LoopIDs = [%1];").arg(msgLoop));
|
|
}
|
|
|
|
void MainWindow::generateGraphDOT()
|
|
{
|
|
if(_graphSavingFileName.isEmpty())
|
|
{
|
|
_graphSavingFileName = _preferencesDialog->getWorkingDirectory() + QDir::separator() + "Graph.dot";
|
|
}
|
|
|
|
bool ok;
|
|
int id = QInputDialog::getInt(this, tr("Around which location?"), tr("Location ID (0=full map)"), 0, 0, 999999, 0, &ok);
|
|
if(ok)
|
|
{
|
|
int margin = 0;
|
|
if(id > 0)
|
|
{
|
|
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"), _graphSavingFileName, tr("Graphiz file (*.dot)"));
|
|
if(!path.isEmpty())
|
|
{
|
|
_graphSavingFileName = path;
|
|
this->post(new RtabmapEventCmd(RtabmapEventCmd::kCmdGenerateDOTGraph, false, path.toStdString(), id, margin));
|
|
|
|
_ui->dockWidget_console->show();
|
|
_ui->widget_console->appendMsg(QString("Graph saved... Tip:\nneato -Tpdf \"%1\" -o out.pdf").arg(_graphSavingFileName));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void MainWindow::exportPosesRaw()
|
|
{
|
|
exportPoses(0);
|
|
}
|
|
void MainWindow::exportPosesRGBDSLAM()
|
|
{
|
|
exportPoses(1);
|
|
}
|
|
void MainWindow::exportPosesKITTI()
|
|
{
|
|
exportPoses(2);
|
|
}
|
|
void MainWindow::exportPosesTORO()
|
|
{
|
|
exportPoses(3);
|
|
}
|
|
void MainWindow::exportPosesG2O()
|
|
{
|
|
exportPoses(4);
|
|
}
|
|
|
|
void MainWindow::exportPoses(int format)
|
|
{
|
|
if(_currentPosesMap.size())
|
|
{
|
|
std::map<int, double> stamps;
|
|
if(format == 1)
|
|
{
|
|
for(std::map<int, Transform>::iterator iter=_currentPosesMap.begin(); iter!=_currentPosesMap.end(); ++iter)
|
|
{
|
|
if(_cachedSignatures.contains(iter->first))
|
|
{
|
|
stamps.insert(std::make_pair(iter->first, _cachedSignatures.value(iter->first).getStamp()));
|
|
}
|
|
}
|
|
if(stamps.size()!=_currentPosesMap.size())
|
|
{
|
|
QMessageBox::warning(this, tr("Export poses..."), tr("RGB-D SLAM format: Poses (%1) and stamps (%2) have not the same size! Try again after updating the cache.")
|
|
.arg(_currentPosesMap.size()).arg(stamps.size()));
|
|
return;
|
|
}
|
|
}
|
|
|
|
if(_exportPosesFileName[format].isEmpty())
|
|
{
|
|
_exportPosesFileName[format] = _preferencesDialog->getWorkingDirectory() + QDir::separator() + (format==3?"toro.graph":format==4?"poses.g2o":"poses.txt");
|
|
}
|
|
|
|
QString path = QFileDialog::getSaveFileName(
|
|
this,
|
|
tr("Save File"),
|
|
_exportPosesFileName[format],
|
|
format == 3?tr("TORO file (*.graph)"):format==4?tr("g2o file (*.g2o)"):tr("Text file (*.txt)"));
|
|
|
|
if(!path.isEmpty())
|
|
{
|
|
_exportPosesFileName[format] = path;
|
|
|
|
bool saved = graph::exportPoses(path.toStdString(), format, _currentPosesMap, _currentLinksMap, stamps);
|
|
|
|
if(saved)
|
|
{
|
|
QMessageBox::information(this,
|
|
tr("Export poses..."),
|
|
tr("%1 saved to \"%2\".")
|
|
.arg(format == 3?"TORO graph":format == 4?"g2o graph":"Poses")
|
|
.arg(_exportPosesFileName[format]));
|
|
}
|
|
else
|
|
{
|
|
QMessageBox::information(this,
|
|
tr("Export poses..."),
|
|
tr("Failed to save %1 to \"%2\"!")
|
|
.arg(format == 3?"TORO graph":format == 4?"g2o graph":"poses")
|
|
.arg(_exportPosesFileName[format]));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void MainWindow::postProcessing()
|
|
{
|
|
if(_cachedSignatures.size() == 0)
|
|
{
|
|
QMessageBox::warning(this,
|
|
tr("Post-processing..."),
|
|
tr("Signatures must be cached in the GUI for post-processing. "
|
|
"Check the option in Preferences->General Settings (GUI), then "
|
|
"refresh the cache."));
|
|
return;
|
|
}
|
|
if(_postProcessingDialog->exec() != QDialog::Accepted)
|
|
{
|
|
return;
|
|
}
|
|
|
|
bool detectMoreLoopClosures = _postProcessingDialog->isDetectMoreLoopClosures();
|
|
bool reextractFeatures = _postProcessingDialog->isReextractFeatures();
|
|
bool refineNeighborLinks = _postProcessingDialog->isRefineNeighborLinks();
|
|
bool refineLoopClosureLinks = _postProcessingDialog->isRefineLoopClosureLinks();
|
|
double clusterRadius = _postProcessingDialog->clusterRadius();
|
|
double clusterAngle = _postProcessingDialog->clusterAngle();
|
|
int detectLoopClosureIterations = _postProcessingDialog->iterations();
|
|
bool sba = _postProcessingDialog->isSBA();
|
|
int sbaIterations = _postProcessingDialog->sbaIterations();
|
|
double sbaEpsilon = _postProcessingDialog->sbaEpsilon();
|
|
double sbaInlierDistance = _postProcessingDialog->sbaInlierDistance();
|
|
int sbaMinInliers = _postProcessingDialog->sbaMinInliers();
|
|
|
|
if(!detectMoreLoopClosures && !refineNeighborLinks && !refineLoopClosureLinks && !sba)
|
|
{
|
|
UWARN("No post-processing selection...");
|
|
return;
|
|
}
|
|
|
|
// First, verify that we have all data required in the GUI
|
|
bool allDataAvailable = true;
|
|
std::map<int, Transform> odomPoses;
|
|
for(std::map<int, Transform>::iterator iter = _currentPosesMap.begin();
|
|
iter!=_currentPosesMap.end() && allDataAvailable;
|
|
++iter)
|
|
{
|
|
QMap<int, Signature>::iterator jter = _cachedSignatures.find(iter->first);
|
|
if(jter != _cachedSignatures.end())
|
|
{
|
|
if(jter->getPose().isNull())
|
|
{
|
|
UWARN("Odometry pose of %d is null.", iter->first);
|
|
allDataAvailable = false;
|
|
}
|
|
else
|
|
{
|
|
odomPoses.insert(*iter); // fill raw poses
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UWARN("Node %d missing.", iter->first);
|
|
allDataAvailable = false;
|
|
}
|
|
}
|
|
|
|
if(!allDataAvailable)
|
|
{
|
|
QMessageBox::warning(this, tr("Not all data available in the GUI..."),
|
|
tr("Some data missing in the cache to respect the constraints chosen. "
|
|
"Try \"Edit->Download all clouds\" to update the cache and try again."));
|
|
return;
|
|
}
|
|
|
|
_initProgressDialog->resetProgress();
|
|
_initProgressDialog->clear();
|
|
_initProgressDialog->show();
|
|
_initProgressDialog->appendText("Post-processing beginning!");
|
|
|
|
int totalSteps = 0;
|
|
if(refineNeighborLinks)
|
|
{
|
|
totalSteps+=(int)odomPoses.size();
|
|
}
|
|
if(refineLoopClosureLinks)
|
|
{
|
|
totalSteps+=(int)_currentLinksMap.size() - (int)odomPoses.size();
|
|
}
|
|
if(sba)
|
|
{
|
|
totalSteps+=1;
|
|
}
|
|
_initProgressDialog->setMaximumSteps(totalSteps);
|
|
_initProgressDialog->show();
|
|
|
|
ParametersMap parameters = _preferencesDialog->getAllParameters();
|
|
Optimizer * optimizer = Optimizer::create(parameters);
|
|
bool optimizeFromGraphEnd = Parameters::defaultRGBDOptimizeFromGraphEnd();
|
|
Parameters::parse(parameters, Parameters::kRGBDOptimizeFromGraphEnd(), optimizeFromGraphEnd);
|
|
|
|
bool warn = false;
|
|
int loopClosuresAdded = 0;
|
|
if(detectMoreLoopClosures)
|
|
{
|
|
UDEBUG("");
|
|
|
|
UASSERT(detectLoopClosureIterations>0);
|
|
for(int n=0; n<detectLoopClosureIterations; ++n)
|
|
{
|
|
_initProgressDialog->appendText(tr("Looking for more loop closures, clustering poses... (iteration=%1/%2, radius=%3 m angle=%4 degrees)")
|
|
.arg(n+1).arg(detectLoopClosureIterations).arg(clusterRadius).arg(clusterAngle));
|
|
|
|
std::multimap<int, int> clusters = graph::radiusPosesClustering(
|
|
_currentPosesMap,
|
|
clusterRadius,
|
|
clusterAngle*CV_PI/180.0);
|
|
|
|
_initProgressDialog->setMaximumSteps(_initProgressDialog->maximumSteps()+(int)clusters.size());
|
|
_initProgressDialog->appendText(tr("Looking for more loop closures, clustering poses... found %1 clusters.").arg(clusters.size()));
|
|
|
|
int i=0;
|
|
std::set<int> addedLinks;
|
|
for(std::multimap<int, int>::iterator iter=clusters.begin(); iter!= clusters.end(); ++iter, ++i)
|
|
{
|
|
int from = iter->first;
|
|
int to = iter->second;
|
|
if(iter->first < iter->second)
|
|
{
|
|
from = iter->second;
|
|
to = iter->first;
|
|
}
|
|
|
|
// only add new links and one per cluster per iteration
|
|
if(addedLinks.find(from) == addedLinks.end() && addedLinks.find(to) == addedLinks.end() &&
|
|
rtabmap::graph::findLink(_currentLinksMap, from, to) == _currentLinksMap.end())
|
|
{
|
|
if(!_cachedSignatures.contains(from))
|
|
{
|
|
UERROR("Didn't find signature %d", from);
|
|
}
|
|
else if(!_cachedSignatures.contains(to))
|
|
{
|
|
UERROR("Didn't find signature %d", to);
|
|
}
|
|
else
|
|
{
|
|
_initProgressDialog->incrementStep();
|
|
QApplication::processEvents();
|
|
|
|
Signature signatureFrom = _cachedSignatures[from];
|
|
Signature signatureTo = _cachedSignatures[to];
|
|
|
|
if(reextractFeatures)
|
|
{
|
|
signatureFrom.setWords(std::multimap<int, cv::KeyPoint>());
|
|
signatureFrom.setWords3(std::multimap<int, pcl::PointXYZ>());
|
|
signatureTo.setWords(std::multimap<int, cv::KeyPoint>());
|
|
signatureTo.setWords3(std::multimap<int, pcl::PointXYZ>());
|
|
}
|
|
|
|
Transform transform;
|
|
std::string rejectedMsg;
|
|
int inliers = -1;
|
|
float variance = -1.0f;
|
|
RegistrationVis registration(parameters);
|
|
transform = registration.computeTransformation(signatureFrom, signatureTo, Transform(), &rejectedMsg, &inliers, &variance);
|
|
|
|
if(!transform.isNull())
|
|
{
|
|
UINFO("Added new loop closure between %d and %d.", from, to);
|
|
addedLinks.insert(from);
|
|
addedLinks.insert(to);
|
|
_currentLinksMap.insert(std::make_pair(from, Link(from, to, Link::kUserClosure, transform, variance, variance)));
|
|
++loopClosuresAdded;
|
|
_initProgressDialog->appendText(tr("Detected loop closure %1->%2! (%3/%4)").arg(from).arg(to).arg(i+1).arg(clusters.size()));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
_initProgressDialog->appendText(tr("Iteration %1/%2: Detected %3 loop closures!")
|
|
.arg(n+1).arg(detectLoopClosureIterations).arg(addedLinks.size()/2));
|
|
if(addedLinks.size() == 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if(n+1 < detectLoopClosureIterations)
|
|
{
|
|
_initProgressDialog->appendText(tr("Optimizing graph with new links (%1 nodes, %2 constraints)...")
|
|
.arg(odomPoses.size()).arg(_currentLinksMap.size()));
|
|
int fromId = optimizeFromGraphEnd?odomPoses.rbegin()->first:odomPoses.begin()->first;
|
|
std::map<int, rtabmap::Transform> posesOut;
|
|
std::multimap<int, rtabmap::Link> linksOut;
|
|
std::map<int, rtabmap::Transform> optimizedPoses;
|
|
optimizer->getConnectedGraph(
|
|
fromId,
|
|
odomPoses,
|
|
_currentLinksMap,
|
|
posesOut,
|
|
linksOut);
|
|
optimizedPoses = optimizer->optimize(fromId, posesOut, linksOut);
|
|
_currentPosesMap = optimizedPoses;
|
|
_initProgressDialog->appendText(tr("Optimizing graph with new links... done!"));
|
|
}
|
|
}
|
|
UINFO("Added %d loop closures.", loopClosuresAdded);
|
|
_initProgressDialog->appendText(tr("Total new loop closures detected=%1").arg(loopClosuresAdded));
|
|
}
|
|
|
|
if(refineNeighborLinks || refineLoopClosureLinks)
|
|
{
|
|
UDEBUG("");
|
|
if(refineLoopClosureLinks)
|
|
{
|
|
_initProgressDialog->setMaximumSteps(_initProgressDialog->maximumSteps()+loopClosuresAdded);
|
|
}
|
|
// TODO: support ICP from laser scans?
|
|
_initProgressDialog->appendText(tr("Refining links..."));
|
|
|
|
RegistrationIcp regIcp(parameters);
|
|
|
|
int i=0;
|
|
for(std::multimap<int, Link>::iterator iter = _currentLinksMap.begin(); iter!=_currentLinksMap.end(); ++iter, ++i)
|
|
{
|
|
int type = iter->second.type();
|
|
|
|
if((refineNeighborLinks && type==Link::kNeighbor) ||
|
|
(refineLoopClosureLinks && type!=Link::kNeighbor))
|
|
{
|
|
int from = iter->second.from();
|
|
int to = iter->second.to();
|
|
|
|
_initProgressDialog->appendText(tr("Refining link %1->%2 (%3/%4)").arg(from).arg(to).arg(i+1).arg(_currentLinksMap.size()));
|
|
_initProgressDialog->incrementStep();
|
|
QApplication::processEvents();
|
|
|
|
if(!_cachedSignatures.contains(from))
|
|
{
|
|
UERROR("Didn't find signature %d",from);
|
|
}
|
|
else if(!_cachedSignatures.contains(to))
|
|
{
|
|
UERROR("Didn't find signature %d", to);
|
|
}
|
|
else
|
|
{
|
|
Signature & signatureFrom = _cachedSignatures[from];
|
|
Signature & signatureTo = _cachedSignatures[to];
|
|
|
|
if(!signatureFrom.sensorData().laserScanRaw().empty() &&
|
|
!signatureTo.sensorData().laserScanRaw().empty())
|
|
{
|
|
std::string rejectedMsg;
|
|
float variance = -1.0f;
|
|
Transform transform = regIcp.computeTransformation(signatureFrom, signatureTo, iter->second.transform(), &rejectedMsg, 0, &variance);
|
|
|
|
if(!transform.isNull())
|
|
{
|
|
Link newLink(from, to, iter->second.type(), transform*iter->second.transform(), variance, variance);
|
|
iter->second = newLink;
|
|
}
|
|
else
|
|
{
|
|
QString str = tr("Cannot refine link %1->%2 (%3").arg(from).arg(to).arg(rejectedMsg.c_str());
|
|
_initProgressDialog->appendText(str, Qt::darkYellow);
|
|
UWARN("%s", str.toStdString().c_str());
|
|
warn = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
QString str;
|
|
if(signatureFrom.getWeight() < 0 || signatureTo.getWeight() < 0)
|
|
{
|
|
str = tr("Cannot refine link %1->%2 (Intermediate node detected!)").arg(from).arg(to);
|
|
}
|
|
else
|
|
{
|
|
str = tr("Cannot refine link %1->%2 (clouds empty!)").arg(from).arg(to);
|
|
}
|
|
|
|
_initProgressDialog->appendText(str, Qt::darkYellow);
|
|
UWARN("%s", str.toStdString().c_str());
|
|
warn = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
_initProgressDialog->appendText(tr("Refining links...done!"));
|
|
}
|
|
|
|
_initProgressDialog->appendText(tr("Optimizing graph with updated links (%1 nodes, %2 constraints)...")
|
|
.arg(odomPoses.size()).arg(_currentLinksMap.size()));
|
|
|
|
int fromId = optimizeFromGraphEnd?odomPoses.rbegin()->first:odomPoses.begin()->first;
|
|
std::map<int, rtabmap::Transform> posesOut;
|
|
std::multimap<int, rtabmap::Link> linksOut;
|
|
std::map<int, rtabmap::Transform> optimizedPoses;
|
|
optimizer->getConnectedGraph(
|
|
fromId,
|
|
odomPoses,
|
|
_currentLinksMap,
|
|
posesOut,
|
|
linksOut);
|
|
optimizedPoses = optimizer->optimize(fromId, posesOut, linksOut);
|
|
_initProgressDialog->appendText(tr("Optimizing graph with updated links... done!"));
|
|
_initProgressDialog->incrementStep();
|
|
|
|
if(sba)
|
|
{
|
|
UASSERT(OptimizerCVSBA::available());
|
|
_initProgressDialog->appendText(tr("SBA (%1 nodes, %2 constraints, %3 iterations)...")
|
|
.arg(optimizedPoses.size()).arg(linksOut.size()).arg(sbaIterations));
|
|
QApplication::processEvents();
|
|
uSleep(100);
|
|
QApplication::processEvents();
|
|
|
|
ParametersMap parametersSBA = _preferencesDialog->getAllParameters();
|
|
uInsert(parametersSBA, std::make_pair(Parameters::kOptimizerIterations(), uNumber2Str(sbaIterations)));
|
|
uInsert(parametersSBA, std::make_pair(Parameters::kOptimizerEpsilon(), uNumber2Str(sbaEpsilon)));
|
|
OptimizerCVSBA cvsba = OptimizerCVSBA(parametersSBA);
|
|
cvsba.setInlierDistance(sbaInlierDistance);
|
|
cvsba.setMinInliers(sbaMinInliers);
|
|
std::map<int, Transform> newPoses = cvsba.optimizeBA(0, optimizedPoses, linksOut, _cachedSignatures.toStdMap());
|
|
if(newPoses.size())
|
|
{
|
|
optimizedPoses = newPoses;
|
|
_initProgressDialog->appendText(tr("SBA... done!"));
|
|
}
|
|
else
|
|
{
|
|
_initProgressDialog->appendText(tr("SBA... failed!"));
|
|
_initProgressDialog->setAutoClose(false);
|
|
}
|
|
_initProgressDialog->incrementStep();
|
|
}
|
|
|
|
_initProgressDialog->appendText(tr("Updating map..."));
|
|
this->updateMapCloud(
|
|
optimizedPoses, Transform(),
|
|
std::multimap<int, Link>(_currentLinksMap),
|
|
std::map<int, int>(_currentMapIds),
|
|
std::map<int, std::string>(_curentLabels),
|
|
false);
|
|
_initProgressDialog->appendText(tr("Updating map... done!"));
|
|
|
|
if(warn)
|
|
{
|
|
_initProgressDialog->setAutoClose(false);
|
|
}
|
|
|
|
_initProgressDialog->setValue(_initProgressDialog->maximumSteps());
|
|
_initProgressDialog->appendText("Post-processing finished!");
|
|
|
|
delete optimizer;
|
|
}
|
|
|
|
void MainWindow::deleteMemory()
|
|
{
|
|
QMessageBox::StandardButton button;
|
|
if(_state == kMonitoring || _state == kMonitoringPaused)
|
|
{
|
|
button = QMessageBox::question(this,
|
|
tr("Deleting memory..."),
|
|
tr("The remote database and log files will be deleted. Are you sure you want to continue? (This cannot be reverted)"),
|
|
QMessageBox::Yes|QMessageBox::No,
|
|
QMessageBox::No);
|
|
}
|
|
else
|
|
{
|
|
button = QMessageBox::question(this,
|
|
tr("Deleting memory..."),
|
|
tr("The current database and log files will be deleted. Are you sure you want to continue? (This cannot be reverted)"),
|
|
QMessageBox::Yes|QMessageBox::No,
|
|
QMessageBox::No);
|
|
}
|
|
|
|
if(button != QMessageBox::Yes)
|
|
{
|
|
return;
|
|
}
|
|
|
|
this->post(new RtabmapEventCmd(RtabmapEventCmd::kCmdResetMemory));
|
|
if(_state!=kDetecting)
|
|
{
|
|
_databaseUpdated = false;
|
|
}
|
|
this->clearTheCache();
|
|
}
|
|
|
|
QString MainWindow::getWorkingDirectory() const
|
|
{
|
|
return _preferencesDialog->getWorkingDirectory();
|
|
}
|
|
|
|
void MainWindow::openWorkingDirectory()
|
|
{
|
|
QString filePath = _preferencesDialog->getWorkingDirectory();
|
|
#if defined(Q_WS_MAC)
|
|
QStringList args;
|
|
args << "-e";
|
|
args << "tell application \"Finder\"";
|
|
args << "-e";
|
|
args << "activate";
|
|
args << "-e";
|
|
args << "select POSIX file \""+filePath+"\"";
|
|
args << "-e";
|
|
args << "end tell";
|
|
QProcess::startDetached("osascript", args);
|
|
#elif defined(Q_WS_WIN)
|
|
QStringList args;
|
|
args << "/select," << QDir::toNativeSeparators(filePath);
|
|
QProcess::startDetached("explorer", args);
|
|
#else
|
|
UERROR("Only works on Mac and Windows");
|
|
#endif
|
|
}
|
|
|
|
void MainWindow::updateEditMenu()
|
|
{
|
|
// Update Memory delete database size
|
|
if(_state != kMonitoring && _state != kMonitoringPaused && (!_openedDatabasePath.isEmpty() || !_newDatabasePath.isEmpty()))
|
|
{
|
|
if(!_openedDatabasePath.isEmpty())
|
|
{
|
|
_ui->actionDelete_memory->setText(tr("Delete memory (%1 MB)").arg(UFile::length(_openedDatabasePath.toStdString())/1000000));
|
|
}
|
|
else
|
|
{
|
|
_ui->actionDelete_memory->setText(tr("Delete memory (%1 MB)").arg(UFile::length(_newDatabasePath.toStdString())/1000000));
|
|
}
|
|
}
|
|
}
|
|
|
|
void MainWindow::selectStream()
|
|
{
|
|
_preferencesDialog->selectSourceDriver(PreferencesDialog::kSrcUsbDevice);
|
|
}
|
|
|
|
void MainWindow::selectOpenni()
|
|
{
|
|
_preferencesDialog->selectSourceDriver(PreferencesDialog::kSrcOpenNI_PCL);
|
|
}
|
|
|
|
void MainWindow::selectFreenect()
|
|
{
|
|
_preferencesDialog->selectSourceDriver(PreferencesDialog::kSrcFreenect);
|
|
}
|
|
|
|
void MainWindow::selectOpenniCv()
|
|
{
|
|
_preferencesDialog->selectSourceDriver(PreferencesDialog::kSrcOpenNI_CV);
|
|
}
|
|
|
|
void MainWindow::selectOpenniCvAsus()
|
|
{
|
|
_preferencesDialog->selectSourceDriver(PreferencesDialog::kSrcOpenNI_CV_ASUS);
|
|
}
|
|
|
|
void MainWindow::selectOpenni2()
|
|
{
|
|
_preferencesDialog->selectSourceDriver(PreferencesDialog::kSrcOpenNI2);
|
|
}
|
|
|
|
void MainWindow::selectFreenect2()
|
|
{
|
|
_preferencesDialog->selectSourceDriver(PreferencesDialog::kSrcFreenect2);
|
|
}
|
|
|
|
void MainWindow::selectStereoDC1394()
|
|
{
|
|
_preferencesDialog->selectSourceDriver(PreferencesDialog::kSrcDC1394);
|
|
}
|
|
|
|
void MainWindow::selectStereoFlyCapture2()
|
|
{
|
|
_preferencesDialog->selectSourceDriver(PreferencesDialog::kSrcFlyCapture2);
|
|
}
|
|
|
|
|
|
void MainWindow::dumpTheMemory()
|
|
{
|
|
this->post(new RtabmapEventCmd(RtabmapEventCmd::kCmdDumpMemory));
|
|
}
|
|
|
|
void MainWindow::dumpThePrediction()
|
|
{
|
|
this->post(new RtabmapEventCmd(RtabmapEventCmd::kCmdDumpPrediction));
|
|
}
|
|
|
|
void MainWindow::sendGoal()
|
|
{
|
|
UINFO("Sending a goal...");
|
|
bool ok = false;
|
|
QString text = QInputDialog::getText(this, tr("Send a goal"), tr("Goal location ID or label: "), QLineEdit::Normal, "", &ok);
|
|
if(ok && !text.isEmpty())
|
|
{
|
|
_waypoints.clear();
|
|
_waypointsIndex = 0;
|
|
|
|
this->postGoal(text);
|
|
}
|
|
}
|
|
|
|
void MainWindow::sendWaypoints()
|
|
{
|
|
UINFO("Sending waypoints...");
|
|
bool ok = false;
|
|
QString text = QInputDialog::getText(this, tr("Send waypoints"), tr("Waypoint IDs or labels (separated by spaces): "), QLineEdit::Normal, "", &ok);
|
|
if(ok && !text.isEmpty())
|
|
{
|
|
QStringList wp = text.split(' ');
|
|
if(wp.size() < 2)
|
|
{
|
|
QMessageBox::warning(this, tr("Send waypoints"), tr("At least two waypoints should be set. For only one goal, use send goal action."));
|
|
}
|
|
else
|
|
{
|
|
_waypoints = wp;
|
|
_waypointsIndex = 0;
|
|
this->postGoal(_waypoints.at(_waypointsIndex));
|
|
}
|
|
}
|
|
}
|
|
|
|
void MainWindow::postGoal(const QString & goal)
|
|
{
|
|
if(!goal.isEmpty())
|
|
{
|
|
bool ok = false;
|
|
int id = goal.toInt(&ok);
|
|
_ui->graphicsView_graphView->setGlobalPath(std::vector<std::pair<int, Transform> >()); // clear
|
|
UINFO("Posting event with goal %s", goal.toStdString().c_str());
|
|
if(ok)
|
|
{
|
|
this->post(new RtabmapEventCmd(RtabmapEventCmd::kCmdGoal, id));
|
|
}
|
|
else
|
|
{
|
|
this->post(new RtabmapEventCmd(RtabmapEventCmd::kCmdGoal, goal.toStdString()));
|
|
}
|
|
}
|
|
}
|
|
|
|
void MainWindow::cancelGoal()
|
|
{
|
|
UINFO("Cancelling goal...");
|
|
_waypoints.clear();
|
|
_waypointsIndex = 0;
|
|
this->post(new RtabmapEventCmd(RtabmapEventCmd::kCmdCancelGoal));
|
|
}
|
|
|
|
void MainWindow::label()
|
|
{
|
|
UINFO("Labelling current location...");
|
|
bool ok = false;
|
|
QString label = QInputDialog::getText(this, tr("Label current location"), tr("Label: "), QLineEdit::Normal, "", &ok);
|
|
if(ok && !label.isEmpty())
|
|
{
|
|
this->post(new RtabmapEventCmd(RtabmapEventCmd::kCmdLabel, label.toStdString(), 0));
|
|
}
|
|
}
|
|
|
|
void MainWindow::updateCacheFromDatabase()
|
|
{
|
|
QString dir = getWorkingDirectory();
|
|
QString path = QFileDialog::getOpenFileName(this, tr("Select file"), dir, tr("RTAB-Map database files (*.db)"));
|
|
if(!path.isEmpty())
|
|
{
|
|
updateCacheFromDatabase(path);
|
|
}
|
|
}
|
|
|
|
void MainWindow::updateCacheFromDatabase(const QString & path)
|
|
{
|
|
if(!path.isEmpty())
|
|
{
|
|
DBDriver * driver = DBDriver::create();
|
|
if(driver->openConnection(path.toStdString()))
|
|
{
|
|
UINFO("Update cache...");
|
|
_initProgressDialog->resetProgress();
|
|
_initProgressDialog->show();
|
|
_initProgressDialog->appendText(tr("Downloading the map from \"%1\" (without poses and links)...")
|
|
.arg(path));
|
|
|
|
std::set<int> ids;
|
|
driver->getAllNodeIds(ids, true);
|
|
std::list<Signature*> signaturesList;
|
|
driver->loadSignatures(std::list<int>(ids.begin(), ids.end()), signaturesList);
|
|
std::map<int, Signature> signatures;
|
|
driver->loadNodeData(signaturesList);
|
|
for(std::list<Signature *>::iterator iter=signaturesList.begin(); iter!=signaturesList.end(); ++iter)
|
|
{
|
|
signatures.insert(std::make_pair((*iter)->id(), *(*iter)));
|
|
delete *iter;
|
|
}
|
|
RtabmapEvent3DMap event(signatures, _currentPosesMap, _currentLinksMap);
|
|
processRtabmapEvent3DMap(event);
|
|
}
|
|
else
|
|
{
|
|
QMessageBox::warning(this, tr("Update cache"), tr("Failed to open database \"%1\"").arg(path));
|
|
}
|
|
delete driver;
|
|
}
|
|
}
|
|
|
|
void MainWindow::downloadAllClouds()
|
|
{
|
|
QStringList items;
|
|
items.append("Local map optimized");
|
|
items.append("Local map not optimized");
|
|
items.append("Global map optimized");
|
|
items.append("Global map not optimized");
|
|
|
|
bool ok;
|
|
QString item = QInputDialog::getItem(this, tr("Download map"), tr("Options:"), items, 2, false, &ok);
|
|
if(ok)
|
|
{
|
|
bool optimized=false, global=false;
|
|
if(item.compare("Local map optimized") == 0)
|
|
{
|
|
optimized = true;
|
|
}
|
|
else if(item.compare("Local map not optimized") == 0)
|
|
{
|
|
|
|
}
|
|
else if(item.compare("Global map optimized") == 0)
|
|
{
|
|
global=true;
|
|
optimized=true;
|
|
}
|
|
else if(item.compare("Global map not optimized") == 0)
|
|
{
|
|
global=true;
|
|
}
|
|
else
|
|
{
|
|
UFATAL("Item \"%s\" not found?!?", item.toStdString().c_str());
|
|
}
|
|
|
|
UINFO("Download clouds...");
|
|
_initProgressDialog->resetProgress();
|
|
_initProgressDialog->show();
|
|
_initProgressDialog->appendText(tr("Downloading the map (global=%1 ,optimized=%2)...")
|
|
.arg(global?"true":"false").arg(optimized?"true":"false"));
|
|
this->post(new RtabmapEventCmd(RtabmapEventCmd::kCmdPublish3DMap, global, optimized, false));
|
|
}
|
|
}
|
|
|
|
void MainWindow::downloadPoseGraph()
|
|
{
|
|
QStringList items;
|
|
items.append("Local map optimized");
|
|
items.append("Local map not optimized");
|
|
items.append("Global map optimized");
|
|
items.append("Global map not optimized");
|
|
|
|
bool ok;
|
|
QString item = QInputDialog::getItem(this, tr("Download graph"), tr("Options:"), items, 2, false, &ok);
|
|
if(ok)
|
|
{
|
|
bool optimized=false, global=false;
|
|
if(item.compare("Local map optimized") == 0)
|
|
{
|
|
optimized = true;
|
|
}
|
|
else if(item.compare("Local map not optimized") == 0)
|
|
{
|
|
|
|
}
|
|
else if(item.compare("Global map optimized") == 0)
|
|
{
|
|
global=true;
|
|
optimized=true;
|
|
}
|
|
else if(item.compare("Global map not optimized") == 0)
|
|
{
|
|
global=true;
|
|
}
|
|
else
|
|
{
|
|
UFATAL("Item \"%s\" not found?!?", item.toStdString().c_str());
|
|
}
|
|
|
|
UINFO("Download the graph...");
|
|
_initProgressDialog->resetProgress();
|
|
_initProgressDialog->show();
|
|
_initProgressDialog->appendText(tr("Downloading the graph (global=%1 ,optimized=%2)...")
|
|
.arg(global?"true":"false").arg(optimized?"true":"false"));
|
|
|
|
this->post(new RtabmapEventCmd(RtabmapEventCmd::kCmdPublish3DMap, global, optimized, true));
|
|
}
|
|
}
|
|
|
|
void MainWindow::clearTheCache()
|
|
{
|
|
_cachedSignatures.clear();
|
|
_createdClouds.clear();
|
|
_createdScans.clear();
|
|
_gridLocalMaps.clear();
|
|
_projectionLocalMaps.clear();
|
|
_ui->widget_cloudViewer->clear();
|
|
_ui->widget_cloudViewer->setBackgroundColor(_ui->widget_cloudViewer->getDefaultBackgroundColor());
|
|
_ui->widget_cloudViewer->clearTrajectory();
|
|
_ui->widget_mapVisibility->clear();
|
|
_currentPosesMap.clear();
|
|
_currentLinksMap.clear();
|
|
_currentMapIds.clear();
|
|
_curentLabels.clear();
|
|
_odometryCorrection = Transform::getIdentity();
|
|
_lastOdomPose.setNull();
|
|
//disable save cloud action
|
|
_ui->actionExport_2D_Grid_map_bmp_png->setEnabled(false);
|
|
_ui->actionExport_2D_scans_ply_pcd->setEnabled(false);
|
|
_ui->actionPost_processing->setEnabled(false);
|
|
_ui->actionSave_point_cloud->setEnabled(false);
|
|
_ui->actionExport_cameras_in_Bundle_format_out->setEnabled(false);
|
|
_ui->actionExport_images_RGB_jpg_Depth_png->setEnabled(false);
|
|
_ui->actionView_scans->setEnabled(false);
|
|
_ui->actionView_high_res_point_cloud->setEnabled(false);
|
|
_likelihoodCurve->clear();
|
|
_rawLikelihoodCurve->clear();
|
|
_posteriorCurve->clear();
|
|
_lastId = 0;
|
|
_lastIds.clear();
|
|
_ui->label_stats_loopClosuresDetected->setText("0");
|
|
_ui->label_stats_loopClosuresReactivatedDetected->setText("0");
|
|
_ui->label_stats_loopClosuresRejected->setText("0");
|
|
_refIds.clear();
|
|
_loopClosureIds.clear();
|
|
_ui->label_refId->clear();
|
|
_ui->label_matchId->clear();
|
|
_ui->graphicsView_graphView->clearAll();
|
|
_ui->imageView_source->clear();
|
|
_ui->imageView_loopClosure->clear();
|
|
_ui->imageView_odometry->clear();
|
|
_ui->imageView_source->setBackgroundColor(Qt::black);
|
|
_ui->imageView_loopClosure->setBackgroundColor(Qt::black);
|
|
_ui->imageView_odometry->setBackgroundColor(Qt::black);
|
|
}
|
|
|
|
void MainWindow::updateElapsedTime()
|
|
{
|
|
if(_state == kDetecting || _state == kMonitoring)
|
|
{
|
|
QString format = "hh:mm:ss";
|
|
_ui->label_elapsedTime->setText((QTime().fromString(_ui->label_elapsedTime->text(), format).addMSecs(_elapsedTime->restart())).toString(format));
|
|
}
|
|
}
|
|
|
|
void MainWindow::saveFigures()
|
|
{
|
|
QList<int> curvesPerFigure;
|
|
QStringList curveNames;
|
|
_ui->statsToolBox->getFiguresSetup(curvesPerFigure, curveNames);
|
|
|
|
QStringList curvesPerFigureStr;
|
|
for(int i=0; i<curvesPerFigure.size(); ++i)
|
|
{
|
|
curvesPerFigureStr.append(QString::number(curvesPerFigure[i]));
|
|
}
|
|
for(int i=0; i<curveNames.size(); ++i)
|
|
{
|
|
curveNames[i].replace(' ', '_');
|
|
}
|
|
_preferencesDialog->saveCustomConfig("Figures", "counts", curvesPerFigureStr.join(" "));
|
|
_preferencesDialog->saveCustomConfig("Figures", "curves", curveNames.join(" "));
|
|
}
|
|
|
|
void MainWindow::loadFigures()
|
|
{
|
|
QString curvesPerFigure = _preferencesDialog->loadCustomConfig("Figures", "counts");
|
|
QString curveNames = _preferencesDialog->loadCustomConfig("Figures", "curves");
|
|
|
|
if(!curvesPerFigure.isEmpty())
|
|
{
|
|
QStringList curvesPerFigureList = curvesPerFigure.split(" ");
|
|
QStringList curvesNamesList = curveNames.split(" ");
|
|
|
|
int j=0;
|
|
for(int i=0; i<curvesPerFigureList.size(); ++i)
|
|
{
|
|
bool ok = false;
|
|
int count = curvesPerFigureList[i].toInt(&ok);
|
|
if(!ok)
|
|
{
|
|
QMessageBox::warning(this, "Loading failed", "Corrupted figures setup...");
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
_ui->statsToolBox->addCurve(curvesNamesList[j++].replace('_', ' '));
|
|
for(int k=1; k<count && j<curveNames.size(); ++k)
|
|
{
|
|
_ui->statsToolBox->addCurve(curvesNamesList[j++].replace('_', ' '), false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void MainWindow::openPreferences()
|
|
{
|
|
_preferencesDialog->setMonitoringState(_state == kMonitoring || _state == kMonitoringPaused);
|
|
_preferencesDialog->exec();
|
|
}
|
|
|
|
void MainWindow::openPreferencesSource()
|
|
{
|
|
_preferencesDialog->setCurrentPanelToSource();
|
|
openPreferences();
|
|
this->updateSelectSourceMenu();
|
|
}
|
|
|
|
void MainWindow::setDefaultViews()
|
|
{
|
|
_ui->dockWidget_posterior->setVisible(false);
|
|
_ui->dockWidget_likelihood->setVisible(false);
|
|
_ui->dockWidget_rawlikelihood->setVisible(false);
|
|
_ui->dockWidget_statsV2->setVisible(false);
|
|
_ui->dockWidget_console->setVisible(false);
|
|
_ui->dockWidget_loopClosureViewer->setVisible(false);
|
|
_ui->dockWidget_mapVisibility->setVisible(false);
|
|
_ui->dockWidget_graphViewer->setVisible(false);
|
|
_ui->dockWidget_odometry->setVisible(true);
|
|
_ui->dockWidget_cloudViewer->setVisible(true);
|
|
_ui->dockWidget_imageView->setVisible(true);
|
|
_ui->toolBar->setVisible(_state != kMonitoring && _state != kMonitoringPaused);
|
|
_ui->toolBar_2->setVisible(true);
|
|
_ui->statusbar->setVisible(false);
|
|
this->setAspectRatio720p();
|
|
_ui->widget_cloudViewer->resetCamera();
|
|
}
|
|
|
|
void MainWindow::selectScreenCaptureFormat(bool checked)
|
|
{
|
|
if(checked)
|
|
{
|
|
QStringList items;
|
|
items << QString("Synchronize with map update") << QString("Synchronize with odometry update");
|
|
bool ok;
|
|
QString item = QInputDialog::getItem(this, tr("Select synchronization behavior"), tr("Sync:"), items, 0, false, &ok);
|
|
if(ok && !item.isEmpty())
|
|
{
|
|
if(item.compare("Synchronize with map update") == 0)
|
|
{
|
|
_autoScreenCaptureOdomSync = false;
|
|
}
|
|
else
|
|
{
|
|
_autoScreenCaptureOdomSync = true;
|
|
}
|
|
|
|
if(_state != kMonitoring && _state != kMonitoringPaused)
|
|
{
|
|
int r = QMessageBox::question(this, tr("Hard drive or RAM?"), tr("Save in RAM? Images will be saved on disk when clicking auto screen capture again."), QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
|
|
if(r == QMessageBox::No || r == QMessageBox::Yes)
|
|
{
|
|
_autoScreenCaptureRAM = r == QMessageBox::Yes;
|
|
}
|
|
else
|
|
{
|
|
_ui->actionAuto_screen_capture->setChecked(false);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_ui->actionAuto_screen_capture->setChecked(false);
|
|
}
|
|
}
|
|
else if(_autoScreenCaptureCachedImages.size())
|
|
{
|
|
QString targetDir = _preferencesDialog->getWorkingDirectory() + QDir::separator() + "ScreensCaptured";
|
|
QDir dir;
|
|
if(!dir.exists(targetDir))
|
|
{
|
|
dir.mkdir(targetDir);
|
|
}
|
|
targetDir += QDir::separator();
|
|
targetDir += "Main_window";
|
|
if(!dir.exists(targetDir))
|
|
{
|
|
dir.mkdir(targetDir);
|
|
}
|
|
targetDir += QDir::separator();
|
|
|
|
_initProgressDialog->resetProgress();
|
|
_initProgressDialog->show();
|
|
_initProgressDialog->setMaximumSteps(_autoScreenCaptureCachedImages.size());
|
|
int i=0;
|
|
for(QMap<QString, QByteArray>::iterator iter=_autoScreenCaptureCachedImages.begin(); iter!=_autoScreenCaptureCachedImages.end(); ++iter)
|
|
{
|
|
QPixmap figure;
|
|
figure.loadFromData(iter.value(), "PNG");
|
|
figure.save(targetDir + iter.key(), "PNG");
|
|
_initProgressDialog->appendText(tr("Saved image \"%1\" (%2/%3).").arg(targetDir + iter.key()).arg(++i).arg(_autoScreenCaptureCachedImages.size()));
|
|
_initProgressDialog->incrementStep();
|
|
}
|
|
_autoScreenCaptureCachedImages.clear();
|
|
_initProgressDialog->setValue(_initProgressDialog->maximumSteps());
|
|
}
|
|
}
|
|
|
|
void MainWindow::takeScreenshot()
|
|
{
|
|
QDesktopServices::openUrl(QUrl::fromLocalFile(this->captureScreen()));
|
|
}
|
|
|
|
void MainWindow::setAspectRatio(int w, int h)
|
|
{
|
|
QRect rect = this->geometry();
|
|
if(h<100 && w<100)
|
|
{
|
|
// it is a ratio
|
|
if(float(rect.width())/float(rect.height()) > float(w)/float(h))
|
|
{
|
|
rect.setWidth(w*(rect.height()/h));
|
|
rect.setHeight((rect.height()/h)*h);
|
|
}
|
|
else
|
|
{
|
|
rect.setHeight(h*(rect.width()/w));
|
|
rect.setWidth((rect.width()/w)*w);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// it is absolute size
|
|
rect.setWidth(w);
|
|
rect.setHeight(h);
|
|
}
|
|
this->setGeometry(rect);
|
|
}
|
|
|
|
void MainWindow::setAspectRatio16_9()
|
|
{
|
|
this->setAspectRatio(16, 9);
|
|
}
|
|
|
|
void MainWindow::setAspectRatio16_10()
|
|
{
|
|
this->setAspectRatio(16, 10);
|
|
}
|
|
|
|
void MainWindow::setAspectRatio4_3()
|
|
{
|
|
this->setAspectRatio(4, 3);
|
|
}
|
|
|
|
void MainWindow::setAspectRatio240p()
|
|
{
|
|
this->setAspectRatio((240*16)/9, 240);
|
|
}
|
|
|
|
void MainWindow::setAspectRatio360p()
|
|
{
|
|
this->setAspectRatio((360*16)/9, 360);
|
|
}
|
|
|
|
void MainWindow::setAspectRatio480p()
|
|
{
|
|
this->setAspectRatio((480*16)/9, 480);
|
|
}
|
|
|
|
void MainWindow::setAspectRatio720p()
|
|
{
|
|
this->setAspectRatio((720*16)/9, 720);
|
|
}
|
|
|
|
void MainWindow::setAspectRatio1080p()
|
|
{
|
|
this->setAspectRatio((1080*16)/9, 1080);
|
|
}
|
|
|
|
void MainWindow::setAspectRatioCustom()
|
|
{
|
|
bool ok;
|
|
int width = QInputDialog::getInt(this, tr("Aspect ratio"), tr("Width (pixels):"), this->geometry().width(), 100, 10000, 100, &ok);
|
|
if(ok)
|
|
{
|
|
int height = QInputDialog::getInt(this, tr("Aspect ratio"), tr("Height (pixels):"), this->geometry().height(), 100, 10000, 100, &ok);
|
|
if(ok)
|
|
{
|
|
this->setAspectRatio(width, height);
|
|
}
|
|
}
|
|
}
|
|
|
|
void MainWindow::exportGridMap()
|
|
{
|
|
double gridCellSize = 0.05;
|
|
bool ok;
|
|
gridCellSize = QInputDialog::getDouble(this, tr("Grid cell size"), tr("Size (m):"), gridCellSize, 0.01, 1, 2, &ok);
|
|
if(!ok)
|
|
{
|
|
return;
|
|
}
|
|
|
|
std::map<int, Transform> poses = _ui->widget_mapVisibility->getVisiblePoses();
|
|
|
|
// create the map
|
|
float xMin=0.0f, yMin=0.0f;
|
|
cv::Mat pixels = util3d::create2DMapFromOccupancyLocalMaps(
|
|
poses,
|
|
_preferencesDialog->isGridMapFrom3DCloud()?_projectionLocalMaps:_gridLocalMaps,
|
|
gridCellSize,
|
|
xMin, yMin,
|
|
0,
|
|
_preferencesDialog->isGridMapEroded());
|
|
|
|
if(!pixels.empty())
|
|
{
|
|
cv::Mat map8U(pixels.rows, pixels.cols, CV_8U);
|
|
//convert to gray scaled map
|
|
for (int i = 0; i < pixels.rows; ++i)
|
|
{
|
|
for (int j = 0; j < pixels.cols; ++j)
|
|
{
|
|
char v = pixels.at<char>(i, j);
|
|
unsigned char gray;
|
|
if(v == 0)
|
|
{
|
|
gray = 178;
|
|
}
|
|
else if(v == 100)
|
|
{
|
|
gray = 0;
|
|
}
|
|
else // -1
|
|
{
|
|
gray = 89;
|
|
}
|
|
map8U.at<unsigned char>(i, j) = gray;
|
|
}
|
|
}
|
|
|
|
QImage image = uCvMat2QImage(map8U, false);
|
|
|
|
QString path = QFileDialog::getSaveFileName(this, tr("Save to ..."), "grid.png", tr("Image (*.png *.bmp)"));
|
|
if(!path.isEmpty())
|
|
{
|
|
if(QFileInfo(path).suffix() != "png" && QFileInfo(path).suffix() != "bmp")
|
|
{
|
|
//use png by default
|
|
path += ".png";
|
|
}
|
|
|
|
QImage img = image.mirrored(false, true).transformed(QTransform().rotate(-90));
|
|
QPixmap::fromImage(img).save(path);
|
|
|
|
QDesktopServices::openUrl(QUrl::fromLocalFile(path));
|
|
}
|
|
}
|
|
}
|
|
|
|
void MainWindow::exportScans()
|
|
{
|
|
std::map<int, pcl::PointCloud<pcl::PointXYZ>::Ptr> scans;
|
|
if(getExportedScans(scans))
|
|
{
|
|
if(scans.size())
|
|
{
|
|
QMessageBox::StandardButton b = QMessageBox::question(this,
|
|
tr("Binary file?"),
|
|
tr("Do you want to save in binary mode?"),
|
|
QMessageBox::No | QMessageBox::Yes,
|
|
QMessageBox::Yes);
|
|
|
|
if(b == QMessageBox::No || b == QMessageBox::Yes)
|
|
{
|
|
this->saveScans(scans, b == QMessageBox::Yes);
|
|
}
|
|
}
|
|
_initProgressDialog->setValue(_initProgressDialog->maximumSteps());
|
|
}
|
|
}
|
|
|
|
void MainWindow::viewScans()
|
|
{
|
|
std::map<int, pcl::PointCloud<pcl::PointXYZ>::Ptr> scans;
|
|
if(getExportedScans(scans))
|
|
{
|
|
QDialog * window = new QDialog(this, Qt::Window);
|
|
window->setWindowFlags(Qt::Dialog);
|
|
window->setWindowTitle(tr("Scans (%1 nodes)").arg(scans.size()));
|
|
window->setMinimumWidth(800);
|
|
window->setMinimumHeight(600);
|
|
|
|
CloudViewer * viewer = new CloudViewer(window);
|
|
viewer->setCameraLockZ(false);
|
|
|
|
QVBoxLayout *layout = new QVBoxLayout();
|
|
layout->addWidget(viewer);
|
|
window->setLayout(layout);
|
|
connect(window, SIGNAL(finished(int)), viewer, SLOT(clear()));
|
|
|
|
window->show();
|
|
|
|
uSleep(500);
|
|
|
|
for(std::map<int, pcl::PointCloud<pcl::PointXYZ>::Ptr>::iterator iter = scans.begin(); iter!=scans.end(); ++iter)
|
|
{
|
|
_initProgressDialog->appendText(tr("Viewing the scan %1 (%2 points)...").arg(iter->first).arg(iter->second->size()));
|
|
_initProgressDialog->incrementStep();
|
|
|
|
QColor color = Qt::red;
|
|
int mapId = uValue(_currentMapIds, iter->first, -1);
|
|
if(mapId >= 0)
|
|
{
|
|
color = (Qt::GlobalColor)(mapId % 12 + 7 );
|
|
}
|
|
viewer->addCloud(uFormat("cloud%d",iter->first), iter->second, iter->first>0?_currentPosesMap.at(iter->first):Transform::getIdentity());
|
|
_initProgressDialog->appendText(tr("Viewing the scan %1 (%2 points)... done.").arg(iter->first).arg(iter->second->size()));
|
|
}
|
|
|
|
_initProgressDialog->setValue(_initProgressDialog->maximumSteps());
|
|
}
|
|
}
|
|
|
|
bool MainWindow::getExportedScans(std::map<int, pcl::PointCloud<pcl::PointXYZ>::Ptr > & scans)
|
|
{
|
|
QMessageBox::StandardButton b = QMessageBox::question(this,
|
|
tr("Assemble scans?"),
|
|
tr("Do you want to assemble the scans in only one cloud?"),
|
|
QMessageBox::No | QMessageBox::Yes,
|
|
QMessageBox::Yes);
|
|
|
|
if(b != QMessageBox::No && b != QMessageBox::Yes)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
double voxel = 0.01;
|
|
bool assemble = b == QMessageBox::Yes;
|
|
|
|
if(assemble)
|
|
{
|
|
bool ok;
|
|
voxel = QInputDialog::getDouble(this, tr("Voxel size"), tr("Voxel size (m):"), voxel, 0.00, 0.1, 2, &ok);
|
|
if(!ok)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
pcl::PointCloud<pcl::PointXYZ>::Ptr assembledScans(new pcl::PointCloud<pcl::PointXYZ>());
|
|
std::map<int, Transform> poses = _ui->widget_mapVisibility->getVisiblePoses();
|
|
|
|
_initProgressDialog->resetProgress();
|
|
_initProgressDialog->show();
|
|
_initProgressDialog->setMaximumSteps(int(poses.size())*(assemble?1:2)+1);
|
|
|
|
int count = 1;
|
|
int i = 0;
|
|
for(std::map<int, Transform>::const_iterator iter = poses.begin(); iter!=poses.end(); ++iter)
|
|
{
|
|
bool inserted = false;
|
|
if(_createdScans.find(iter->first) != _createdScans.end())
|
|
{
|
|
pcl::PointCloud<pcl::PointXYZ>::Ptr scan = _createdScans.at(iter->first);
|
|
if(scan->size())
|
|
{
|
|
if(assemble)
|
|
{
|
|
*assembledScans += *util3d::transformPointCloud(scan, iter->second);;
|
|
|
|
if(count++ % 100 == 0)
|
|
{
|
|
if(assembledScans->size() && voxel)
|
|
{
|
|
assembledScans = util3d::voxelize(assembledScans, voxel);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
scans.insert(std::make_pair(iter->first, scan));
|
|
}
|
|
inserted = true;
|
|
}
|
|
}
|
|
if(inserted)
|
|
{
|
|
_initProgressDialog->appendText(tr("Generated scan %1 (%2/%3).").arg(iter->first).arg(++i).arg(poses.size()));
|
|
}
|
|
else
|
|
{
|
|
_initProgressDialog->appendText(tr("Ignored scan %1 (%2/%3).").arg(iter->first).arg(++i).arg(poses.size()));
|
|
}
|
|
_initProgressDialog->incrementStep();
|
|
QApplication::processEvents();
|
|
}
|
|
|
|
if(assemble)
|
|
{
|
|
if(voxel && assembledScans->size())
|
|
{
|
|
assembledScans = util3d::voxelize(assembledScans, voxel);
|
|
}
|
|
if(assembledScans->size())
|
|
{
|
|
scans.insert(std::make_pair(0, assembledScans));
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void MainWindow::exportClouds()
|
|
{
|
|
std::map<int, pcl::PointCloud<pcl::PointXYZRGBNormal>::Ptr> clouds;
|
|
std::map<int, pcl::PolygonMesh::Ptr> meshes;
|
|
std::map<int, pcl::TextureMesh::Ptr> textureMeshes;
|
|
|
|
if(getExportedClouds(clouds, meshes, textureMeshes, true))
|
|
{
|
|
if(textureMeshes.size())
|
|
{
|
|
saveTextureMeshes(textureMeshes);
|
|
}
|
|
else if(meshes.size())
|
|
{
|
|
saveMeshes(meshes, _exportDialog->getBinaryFile());
|
|
}
|
|
else
|
|
{
|
|
saveClouds(clouds, _exportDialog->getBinaryFile());
|
|
}
|
|
_initProgressDialog->setValue(_initProgressDialog->maximumSteps());
|
|
}
|
|
}
|
|
|
|
void MainWindow::viewClouds()
|
|
{
|
|
std::map<int, pcl::PointCloud<pcl::PointXYZRGBNormal>::Ptr> clouds;
|
|
std::map<int, pcl::PolygonMesh::Ptr> meshes;
|
|
std::map<int, pcl::TextureMesh::Ptr> textureMeshes;
|
|
|
|
if(getExportedClouds(clouds, meshes, textureMeshes, false))
|
|
{
|
|
QDialog * window = new QDialog(this, Qt::Window);
|
|
if(meshes.size())
|
|
{
|
|
window->setWindowTitle(tr("Meshes (%1 nodes)").arg(meshes.size()));
|
|
}
|
|
else
|
|
{
|
|
window->setWindowTitle(tr("Clouds (%1 nodes)").arg(clouds.size()));
|
|
}
|
|
window->setMinimumWidth(800);
|
|
window->setMinimumHeight(600);
|
|
|
|
CloudViewer * viewer = new CloudViewer(window);
|
|
viewer->setCameraLockZ(false);
|
|
|
|
QVBoxLayout *layout = new QVBoxLayout();
|
|
layout->addWidget(viewer);
|
|
window->setLayout(layout);
|
|
connect(window, SIGNAL(finished(int)), viewer, SLOT(clear()));
|
|
|
|
window->show();
|
|
|
|
uSleep(500);
|
|
|
|
if(meshes.size())
|
|
{
|
|
for(std::map<int, pcl::PolygonMesh::Ptr>::iterator iter = meshes.begin(); iter!=meshes.end(); ++iter)
|
|
{
|
|
_initProgressDialog->appendText(tr("Viewing the mesh %1 (%2 polygons)...").arg(iter->first).arg(iter->second->polygons.size()));
|
|
_initProgressDialog->incrementStep();
|
|
bool isRGB = false;
|
|
for(unsigned int i=0; i<iter->second->cloud.fields.size(); ++i)
|
|
{
|
|
if(iter->second->cloud.fields[i].name.compare("rgb") == 0)
|
|
{
|
|
isRGB=true;
|
|
break;
|
|
}
|
|
}
|
|
if(isRGB)
|
|
{
|
|
pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZRGB>);
|
|
pcl::fromPCLPointCloud2(iter->second->cloud, *cloud);
|
|
viewer->addCloudMesh(uFormat("mesh%d",iter->first), cloud, iter->second->polygons, iter->first>0?_currentPosesMap.at(iter->first):Transform::getIdentity());
|
|
}
|
|
else
|
|
{
|
|
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
|
|
pcl::fromPCLPointCloud2(iter->second->cloud, *cloud);
|
|
viewer->addCloudMesh(uFormat("mesh%d",iter->first), cloud, iter->second->polygons, iter->first>0?_currentPosesMap.at(iter->first):Transform::getIdentity());
|
|
}
|
|
_initProgressDialog->appendText(tr("Viewing the mesh %1 (%2 polygons)... done.").arg(iter->first).arg(iter->second->polygons.size()));
|
|
QApplication::processEvents();
|
|
}
|
|
}
|
|
else if(clouds.size())
|
|
{
|
|
for(std::map<int, pcl::PointCloud<pcl::PointXYZRGBNormal>::Ptr>::iterator iter = clouds.begin(); iter!=clouds.end(); ++iter)
|
|
{
|
|
_initProgressDialog->appendText(tr("Viewing the cloud %1 (%2 points)...").arg(iter->first).arg(iter->second->size()));
|
|
_initProgressDialog->incrementStep();
|
|
|
|
QColor color = Qt::gray;
|
|
int mapId = uValue(_currentMapIds, iter->first, -1);
|
|
if(mapId >= 0)
|
|
{
|
|
color = (Qt::GlobalColor)(mapId % 12 + 7 );
|
|
}
|
|
viewer->addCloud(uFormat("cloud%d",iter->first), iter->second, iter->first>0?_currentPosesMap.at(iter->first):Transform::getIdentity());
|
|
_initProgressDialog->appendText(tr("Viewing the cloud %1 (%2 points)... done.").arg(iter->first).arg(iter->second->size()));
|
|
}
|
|
}
|
|
|
|
_initProgressDialog->setValue(_initProgressDialog->maximumSteps());
|
|
}
|
|
}
|
|
|
|
bool removeDir(const QString & dirName)
|
|
{
|
|
bool result = true;
|
|
QDir dir(dirName);
|
|
|
|
if (dir.exists(dirName)) {
|
|
Q_FOREACH(QFileInfo info, dir.entryInfoList(QDir::NoDotAndDotDot | QDir::System | QDir::Hidden | QDir::AllDirs | QDir::Files, QDir::DirsFirst)) {
|
|
if (info.isDir()) {
|
|
result = removeDir(info.absoluteFilePath());
|
|
}
|
|
else {
|
|
result = QFile::remove(info.absoluteFilePath());
|
|
}
|
|
|
|
if (!result) {
|
|
return result;
|
|
}
|
|
}
|
|
result = dir.rmdir(dirName);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool MainWindow::getExportedClouds(
|
|
std::map<int, pcl::PointCloud<pcl::PointXYZRGBNormal>::Ptr> & cloudsWithNormals,
|
|
std::map<int, pcl::PolygonMesh::Ptr> & meshes,
|
|
std::map<int, pcl::TextureMesh::Ptr> & textureMeshes,
|
|
bool toSave)
|
|
{
|
|
if(_exportDialog->isVisible())
|
|
{
|
|
return false;
|
|
}
|
|
if(toSave)
|
|
{
|
|
_exportDialog->setSaveButton();
|
|
}
|
|
else
|
|
{
|
|
_exportDialog->setOkButton();
|
|
}
|
|
_exportDialog->enableRegeneration(_preferencesDialog->isImagesKept());
|
|
if(_exportDialog->exec() == QDialog::Accepted)
|
|
{
|
|
std::map<int, Transform> poses = _ui->widget_mapVisibility->getVisiblePoses();
|
|
|
|
_initProgressDialog->resetProgress();
|
|
_initProgressDialog->show();
|
|
int mul = 1;
|
|
if(_exportDialog->getMesh())
|
|
{
|
|
mul+=1;
|
|
}
|
|
if(_exportDialog->getAssemble())
|
|
{
|
|
mul+=1;
|
|
}
|
|
mul+=1; // normals
|
|
if(_exportDialog->getMeshTexture())
|
|
{
|
|
mul+=1;
|
|
}
|
|
_initProgressDialog->setMaximumSteps(int(poses.size())*mul+1);
|
|
|
|
if(_exportDialog->getMLS())
|
|
{
|
|
_initProgressDialog->appendText(tr("Smoothing the surface using Moving Least Squares (MLS) algorithm... "
|
|
"[search radius=%1m voxel=%2m]").arg(_exportDialog->getMLSRadius()).arg(_exportDialog->getGenerateVoxel()));
|
|
}
|
|
_initProgressDialog->appendText(tr("Computing surface normals... "
|
|
"[K neighbors=%1]").arg(_exportDialog->getNormalKSearch()));
|
|
|
|
std::map<int, pcl::PointCloud<pcl::PointXYZRGB>::Ptr> clouds = this->getClouds(
|
|
poses,
|
|
_exportDialog->getGenerate(),
|
|
_exportDialog->getGenerateDecimation(),
|
|
_exportDialog->getGenerateVoxel(),
|
|
_exportDialog->getGenerateMaxDepth(),
|
|
_exportDialog->getFiltering()?_exportDialog->getFilteringRadius():0.0f,
|
|
_exportDialog->getFiltering()?_exportDialog->getFilteringMinNeighbors():0.0f);
|
|
|
|
pcl::PointCloud<pcl::PointXYZ>::Ptr rawAssembledCloud(new pcl::PointCloud<pcl::PointXYZ>);
|
|
std::vector<int> rawCameraIndices;
|
|
if(_exportDialog->getAssemble())
|
|
{
|
|
_initProgressDialog->appendText(tr("Assembling %1 clouds...").arg(clouds.size()));
|
|
QApplication::processEvents();
|
|
|
|
int i =0;
|
|
pcl::PointCloud<pcl::PointXYZRGB>::Ptr assembledCloud(new pcl::PointCloud<pcl::PointXYZRGB>);
|
|
for(std::map<int, pcl::PointCloud<pcl::PointXYZRGB>::Ptr>::iterator iter=clouds.begin();
|
|
iter!= clouds.end();
|
|
++iter)
|
|
{
|
|
pcl::PointCloud<pcl::PointXYZRGB>::Ptr transformed = util3d::transformPointCloud(iter->second, poses.at(iter->first));
|
|
*assembledCloud += *transformed;
|
|
rawCameraIndices.resize(assembledCloud->size(), iter->first);
|
|
|
|
_initProgressDialog->appendText(tr("Assembled cloud %1 (%2/%3).").arg(iter->first).arg(++i).arg(clouds.size()));
|
|
_initProgressDialog->incrementStep();
|
|
QApplication::processEvents();
|
|
}
|
|
|
|
pcl::copyPointCloud(*assembledCloud, *rawAssembledCloud);
|
|
|
|
_initProgressDialog->appendText(tr("Voxelize assembled cloud (%1 points, voxel size = %2 m)...")
|
|
.arg(assembledCloud->size())
|
|
.arg(_exportDialog->getAssembleVoxel()));
|
|
QApplication::processEvents();
|
|
if(_exportDialog->getAssembleVoxel())
|
|
{
|
|
assembledCloud = util3d::voxelize(
|
|
assembledCloud,
|
|
_exportDialog->getAssembleVoxel());
|
|
}
|
|
|
|
if(_exportDialog->getFiltering() &&
|
|
_exportDialog->getFilteringRadius() > 0.0 &&
|
|
_exportDialog->getFilteringMinNeighbors() > 0)
|
|
{
|
|
_initProgressDialog->appendText(tr("Noise filtering (%1 points, radius = %2 m, min neighbors = %3)...")
|
|
.arg(assembledCloud->size())
|
|
.arg(_exportDialog->getFilteringRadius())
|
|
.arg(_exportDialog->getFilteringMinNeighbors()));
|
|
|
|
pcl::IndicesPtr indices = util3d::radiusFiltering(assembledCloud, (float)_exportDialog->getFilteringRadius(), _exportDialog->getFilteringMinNeighbors());
|
|
pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloudFiltered(new pcl::PointCloud<pcl::PointXYZRGB>);
|
|
pcl::copyPointCloud(*assembledCloud, *indices, *cloudFiltered);
|
|
assembledCloud = cloudFiltered;
|
|
}
|
|
|
|
clouds.clear();
|
|
clouds.insert(std::make_pair(0, assembledCloud));
|
|
}
|
|
|
|
// normals
|
|
for(std::map<int, pcl::PointCloud<pcl::PointXYZRGB>::Ptr>::iterator iter=clouds.begin();
|
|
iter!= clouds.end();
|
|
++iter)
|
|
{
|
|
pcl::PointCloud<pcl::PointXYZRGBNormal>::Ptr cloudWithNormals(new pcl::PointCloud<pcl::PointXYZRGBNormal>);
|
|
if(_exportDialog->getMLS())
|
|
{
|
|
_initProgressDialog->appendText(tr("Smoothing (MLS) the cloud (%1 points)...").arg(iter->second->size()));
|
|
QApplication::processEvents();
|
|
|
|
cloudWithNormals = util3d::mls(
|
|
iter->second,
|
|
(float)_exportDialog->getMLSRadius(),
|
|
_exportDialog->getMLSPolygonialOrder(),
|
|
_exportDialog->getMLSUpsamplingMethod(),
|
|
(float)_exportDialog->getMLSUpsamplingRadius(),
|
|
(float)_exportDialog->getMLSUpsamplingStep(),
|
|
_exportDialog->getMLSPointDensity(),
|
|
(float)_exportDialog->getMLSDilationVoxelSize(),
|
|
_exportDialog->getMLSDilationIterations());
|
|
|
|
// Re-voxelize to make sure to have uniform density
|
|
_initProgressDialog->appendText(tr("Voxelize cloud (%1 points, voxel size = %2 m)...")
|
|
.arg(cloudWithNormals->size())
|
|
.arg(_exportDialog->getAssemble()?_exportDialog->getAssembleVoxel():_exportDialog->getGenerateVoxel()));
|
|
QApplication::processEvents();
|
|
|
|
cloudWithNormals = util3d::voxelize(
|
|
cloudWithNormals,
|
|
_exportDialog->getAssemble()?_exportDialog->getAssembleVoxel():_exportDialog->getGenerateVoxel());
|
|
|
|
}
|
|
else
|
|
{
|
|
//compute normals
|
|
_initProgressDialog->appendText(tr("Computing normals (%1 points)...").arg(iter->second->size()));
|
|
cloudWithNormals = util3d::computeNormals(iter->second, _exportDialog->getNormalKSearch());
|
|
}
|
|
|
|
if(_exportDialog->getAssemble())
|
|
{
|
|
_initProgressDialog->appendText(tr("Update %1 normals with %2 camera views...").arg(cloudWithNormals->size()).arg(poses.size()));
|
|
util3d::adjustNormalsToViewPoints(
|
|
poses,
|
|
rawAssembledCloud,
|
|
rawCameraIndices,
|
|
cloudWithNormals);
|
|
}
|
|
cloudsWithNormals.insert(std::make_pair(iter->first, cloudWithNormals));
|
|
|
|
_initProgressDialog->incrementStep();
|
|
QApplication::processEvents();
|
|
}
|
|
|
|
//mesh
|
|
if(_exportDialog->getMesh())
|
|
{
|
|
_initProgressDialog->appendText(tr("Greedy projection triangulation... [radius=%1m]").arg(_exportDialog->getMeshGp3Radius()));
|
|
QApplication::processEvents();
|
|
|
|
int i=0;
|
|
for(std::map<int, pcl::PointCloud<pcl::PointXYZRGBNormal>::Ptr>::iterator iter=cloudsWithNormals.begin();
|
|
iter!= cloudsWithNormals.end();
|
|
++iter)
|
|
{
|
|
pcl::PolygonMesh::Ptr mesh = util3d::createMesh(iter->second, _exportDialog->getMeshGp3Radius(), _exportDialog->getMeshGp3Mu());
|
|
_initProgressDialog->appendText(tr("Mesh %1 created with %2 polygons (%3/%4).").arg(iter->first).arg(mesh->polygons.size()).arg(++i).arg(clouds.size()));
|
|
|
|
if(_exportDialog->getMeshDecimationFactor() > 0.0)
|
|
{
|
|
mesh = util3d::meshDecimation(mesh, (float)_exportDialog->getMeshDecimationFactor());
|
|
_initProgressDialog->appendText(tr("Mesh %1 decimation (factor=%2) to %3 polygons").arg(iter->first).arg(_exportDialog->getMeshDecimationFactor()).arg(mesh->polygons.size()));
|
|
}
|
|
|
|
meshes.insert(std::make_pair(iter->first, mesh));
|
|
|
|
_initProgressDialog->incrementStep();
|
|
QApplication::processEvents();
|
|
}
|
|
}
|
|
|
|
if(toSave && _exportDialog->getMeshTexture())
|
|
{
|
|
QDir dir(_preferencesDialog->getWorkingDirectory());
|
|
removeDir(_preferencesDialog->getWorkingDirectory()+QDir::separator()+"tmp_textures");
|
|
dir.mkdir("tmp_textures");
|
|
int i=0;
|
|
for(std::map<int, pcl::PolygonMesh::Ptr>::iterator iter=meshes.begin();
|
|
iter!= meshes.end();
|
|
++iter)
|
|
{
|
|
std::map<int, Transform> cameras;
|
|
if(iter->first == 0)
|
|
{
|
|
cameras = poses;
|
|
}
|
|
else
|
|
{
|
|
UASSERT(uContains(poses, iter->first));
|
|
cameras.insert(std::make_pair(iter->first, Transform::getIdentity()));
|
|
}
|
|
std::map<int, Transform> cameraPoses;
|
|
std::map<int, CameraModel> cameraModels;
|
|
std::map<int, cv::Mat> images;
|
|
for(std::map<int, Transform>::iterator jter=cameras.begin(); jter!=cameras.end(); ++jter)
|
|
{
|
|
if(_cachedSignatures.contains(jter->first))
|
|
{
|
|
const Signature & s = _cachedSignatures.value(jter->first);
|
|
CameraModel model;
|
|
if(s.sensorData().stereoCameraModel().isValid())
|
|
{
|
|
model = s.sensorData().stereoCameraModel().left();
|
|
}
|
|
else if(s.sensorData().cameraModels().size() == 1 && s.sensorData().cameraModels()[0].isValid())
|
|
{
|
|
model = s.sensorData().cameraModels()[0];
|
|
}
|
|
cv::Mat image = s.sensorData().imageRaw();
|
|
if(image.empty() && !s.sensorData().imageCompressed().empty())
|
|
{
|
|
s.sensorData().uncompressDataConst(&image, 0, 0, 0);
|
|
}
|
|
if(!jter->second.isNull() && model.isValid() && !image.empty())
|
|
{
|
|
cameraPoses.insert(std::make_pair(jter->first, jter->second));
|
|
cameraModels.insert(std::make_pair(jter->first, model));
|
|
images.insert(std::make_pair(jter->first, image));
|
|
}
|
|
}
|
|
}
|
|
if(cameraPoses.size())
|
|
{
|
|
pcl::TextureMesh::Ptr textureMesh = util3d::createTextureMesh(
|
|
iter->second,
|
|
cameraPoses,
|
|
cameraModels,
|
|
images,
|
|
dir.filePath("tmp_textures").toStdString());
|
|
|
|
textureMeshes.insert(std::make_pair(iter->first, textureMesh));
|
|
}
|
|
|
|
_initProgressDialog->appendText(tr("TextureMesh %1 created [cameras=%2] (%3/%4).").arg(iter->first).arg(cameraPoses.size()).arg(++i).arg(clouds.size()));
|
|
_initProgressDialog->incrementStep();
|
|
QApplication::processEvents();
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void MainWindow::exportImages()
|
|
{
|
|
if(_cachedSignatures.empty())
|
|
{
|
|
QMessageBox::warning(this, tr("Export images..."), tr("Cannot export images, the cache is empty!"));
|
|
return;
|
|
}
|
|
std::map<int, Transform> poses = _ui->widget_mapVisibility->getVisiblePoses();
|
|
|
|
if(poses.empty())
|
|
{
|
|
QMessageBox::warning(this, tr("Export images..."), tr("There is no map!"));
|
|
return;
|
|
}
|
|
|
|
QString path = QFileDialog::getExistingDirectory(this, tr("Select directory where to save images..."), this->getWorkingDirectory());
|
|
if(!path.isNull())
|
|
{
|
|
SensorData data;
|
|
if(_cachedSignatures.contains(poses.rbegin()->first))
|
|
{
|
|
data = _cachedSignatures.value(poses.rbegin()->first).sensorData();
|
|
data.uncompressData();
|
|
}
|
|
if(!data.imageRaw().empty() && !data.rightRaw().empty())
|
|
{
|
|
QDir dir;
|
|
dir.mkdir(QString("%1/left").arg(path));
|
|
dir.mkdir(QString("%1/right").arg(path));
|
|
if(data.stereoCameraModel().isValid())
|
|
{
|
|
std::string cameraName = "calibration";
|
|
StereoCameraModel model(
|
|
cameraName,
|
|
data.imageRaw().size(),
|
|
data.stereoCameraModel().left().K(),
|
|
data.stereoCameraModel().left().D(),
|
|
data.stereoCameraModel().left().R(),
|
|
data.stereoCameraModel().left().P(),
|
|
data.rightRaw().size(),
|
|
data.stereoCameraModel().right().K(),
|
|
data.stereoCameraModel().right().D(),
|
|
data.stereoCameraModel().right().R(),
|
|
data.stereoCameraModel().right().P(),
|
|
data.stereoCameraModel().R(),
|
|
data.stereoCameraModel().T(),
|
|
data.stereoCameraModel().E(),
|
|
data.stereoCameraModel().F(),
|
|
data.stereoCameraModel().left().localTransform());
|
|
if(model.save(path.toStdString()))
|
|
{
|
|
UINFO("Saved stereo calibration \"%s\"", (path.toStdString()+"/"+cameraName).c_str());
|
|
}
|
|
else
|
|
{
|
|
UERROR("Failed saving calibration \"%s\"", (path.toStdString()+"/"+cameraName).c_str());
|
|
}
|
|
}
|
|
}
|
|
else if(!data.imageRaw().empty())
|
|
{
|
|
if(!data.depthRaw().empty())
|
|
{
|
|
QDir dir;
|
|
dir.mkdir(QString("%1/rgb").arg(path));
|
|
dir.mkdir(QString("%1/depth").arg(path));
|
|
}
|
|
|
|
if(data.cameraModels().size() > 1)
|
|
{
|
|
UERROR("Only one camera calibration can be saved at this time (%d detected)", (int)data.cameraModels().size());
|
|
}
|
|
else if(data.cameraModels().size() == 1 && data.cameraModels().front().isValid())
|
|
{
|
|
std::string cameraName = "calibration";
|
|
CameraModel model(cameraName,
|
|
data.imageRaw().size(),
|
|
data.cameraModels().front().K(),
|
|
data.cameraModels().front().D(),
|
|
data.cameraModels().front().R(),
|
|
data.cameraModels().front().P(),
|
|
data.cameraModels().front().localTransform());
|
|
if(model.save(path.toStdString()))
|
|
{
|
|
UINFO("Saved calibration \"%s\"", (path.toStdString()+"/"+cameraName).c_str());
|
|
}
|
|
else
|
|
{
|
|
UERROR("Failed saving calibration \"%s\"", (path.toStdString()+"/"+cameraName).c_str());
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
QMessageBox::warning(this,
|
|
tr("Export images..."),
|
|
tr("Data in the cache don't seem to have images (tested node %1). Calibration file will not be saved. Try refreshing the cache (with clouds).").arg(poses.rbegin()->first));
|
|
}
|
|
}
|
|
|
|
_initProgressDialog->resetProgress();
|
|
_initProgressDialog->show();
|
|
_initProgressDialog->setMaximumSteps(_cachedSignatures.size());
|
|
|
|
unsigned int saved = 0;
|
|
for(std::map<int, Transform>::iterator iter=poses.begin(); iter!=poses.end(); ++iter)
|
|
{
|
|
int id = iter->first;
|
|
SensorData data;
|
|
if(_cachedSignatures.contains(iter->first))
|
|
{
|
|
data = _cachedSignatures.value(iter->first).sensorData();
|
|
data.uncompressData();
|
|
}
|
|
QString info;
|
|
bool warn = false;
|
|
if(!data.imageRaw().empty() && !data.rightRaw().empty())
|
|
{
|
|
cv::imwrite(QString("%1/left/%2.jpg").arg(path).arg(id).toStdString(), data.imageRaw());
|
|
cv::imwrite(QString("%1/right/%2.jpg").arg(path).arg(id).toStdString(), data.rightRaw());
|
|
info = tr("Saved left/%1.jpg and right/%1.jpg.").arg(id);
|
|
}
|
|
else if(!data.imageRaw().empty() && !data.depthRaw().empty())
|
|
{
|
|
cv::imwrite(QString("%1/rgb/%2.jpg").arg(path).arg(id).toStdString(), data.imageRaw());
|
|
cv::imwrite(QString("%1/depth/%2.png").arg(path).arg(id).toStdString(), data.depthRaw());
|
|
info = tr("Saved rgb/%1.jpg and depth/%1.png.").arg(id);
|
|
}
|
|
else if(!data.imageRaw().empty())
|
|
{
|
|
cv::imwrite(QString("%1/%2.jpg").arg(path).arg(id).toStdString(), data.imageRaw());
|
|
info = tr("Saved %1.jpg.").arg(id);
|
|
}
|
|
else
|
|
{
|
|
info = tr("No images saved for node %1!").arg(id);
|
|
warn = true;
|
|
}
|
|
saved += warn?0:1;
|
|
_initProgressDialog->appendText(info, !warn?Qt::black:Qt::darkYellow);
|
|
_initProgressDialog->incrementStep();
|
|
QApplication::processEvents();
|
|
|
|
}
|
|
if(saved!=poses.size())
|
|
{
|
|
_initProgressDialog->setAutoClose(false);
|
|
_initProgressDialog->appendText(tr("%1 images of %2 saved to \"%3\".").arg(saved).arg(poses.size()).arg(path));
|
|
}
|
|
else
|
|
{
|
|
_initProgressDialog->appendText(tr("%1 images saved to \"%2\".").arg(saved).arg(path));
|
|
}
|
|
|
|
_initProgressDialog->setValue(_initProgressDialog->maximumSteps());
|
|
}
|
|
|
|
void MainWindow::exportBundlerFormat()
|
|
{
|
|
std::map<int, Transform> posesIn = _ui->widget_mapVisibility->getVisiblePoses();
|
|
|
|
std::map<int, Transform> poses;
|
|
for(std::map<int, Transform>::iterator iter=posesIn.begin(); iter!=posesIn.end(); ++iter)
|
|
{
|
|
if(_cachedSignatures.contains(iter->first))
|
|
{
|
|
if(_cachedSignatures[iter->first].sensorData().imageRaw().empty() &&
|
|
_cachedSignatures[iter->first].sensorData().imageCompressed().empty())
|
|
{
|
|
UWARN("Missing image in cache for node %d", iter->first);
|
|
}
|
|
else if((_cachedSignatures[iter->first].sensorData().cameraModels().size() == 1 && _cachedSignatures[iter->first].sensorData().cameraModels().at(0).isValid()) ||
|
|
_cachedSignatures[iter->first].sensorData().stereoCameraModel().isValid())
|
|
{
|
|
poses.insert(*iter);
|
|
}
|
|
else
|
|
{
|
|
UWARN("Missing calibration for node %d", iter->first);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UWARN("Did not find node %d in cache", iter->first);
|
|
}
|
|
}
|
|
|
|
if(poses.size())
|
|
{
|
|
QString path = QFileDialog::getExistingDirectory(this, tr("Exporting cameras in Bundler format..."), _preferencesDialog->getWorkingDirectory());
|
|
if(!path.isEmpty())
|
|
{
|
|
// export cameras and images
|
|
QFile fileOut(path+QDir::separator()+"cameras.out");
|
|
QFile fileList(path+QDir::separator()+"list.txt");
|
|
QDir(path).mkdir("images");
|
|
if(fileOut.open(QIODevice::WriteOnly | QIODevice::Text))
|
|
{
|
|
if(fileList.open(QIODevice::WriteOnly | QIODevice::Text))
|
|
{
|
|
QTextStream out(&fileOut);
|
|
QTextStream list(&fileList);
|
|
out << "# Bundle file v0.3\n";
|
|
out << poses.size() << " 0\n";
|
|
|
|
for(std::map<int, Transform>::iterator iter=poses.begin(); iter!=poses.end(); ++iter)
|
|
{
|
|
QString p = QString("images")+QDir::separator()+tr("%1.jpg").arg(iter->first);
|
|
list << p << "\n";
|
|
p = path+QDir::separator()+p;
|
|
cv::Mat image = _cachedSignatures[iter->first].sensorData().imageRaw();
|
|
if(image.empty())
|
|
{
|
|
_cachedSignatures[iter->first].sensorData().uncompressDataConst(&image, 0, 0, 0);
|
|
}
|
|
|
|
if(cv::imwrite(p.toStdString(), image))
|
|
{
|
|
UINFO("saved image %s", p.toStdString().c_str());
|
|
}
|
|
else
|
|
{
|
|
UERROR("Failed to save image %s", p.toStdString().c_str());
|
|
}
|
|
|
|
Transform localTransform;
|
|
if(_cachedSignatures[iter->first].sensorData().cameraModels().size())
|
|
{
|
|
out << _cachedSignatures[iter->first].sensorData().cameraModels().at(0).fx() << " 0 0\n";
|
|
localTransform = _cachedSignatures[iter->first].sensorData().cameraModels().at(0).localTransform();
|
|
}
|
|
else
|
|
{
|
|
out << _cachedSignatures[iter->first].sensorData().stereoCameraModel().left().fx() << " 0 0\n";
|
|
localTransform = _cachedSignatures[iter->first].sensorData().stereoCameraModel().left().localTransform();
|
|
}
|
|
|
|
Transform rotation(0,-1,0,0,
|
|
0,0,1,0,
|
|
-1,0,0,0);
|
|
|
|
Transform R = rotation*iter->second.rotation().inverse();
|
|
|
|
out << R.r11() << " " << R.r12() << " " << R.r13() << "\n";
|
|
out << R.r21() << " " << R.r22() << " " << R.r23() << "\n";
|
|
out << R.r31() << " " << R.r32() << " " << R.r33() << "\n";
|
|
|
|
Transform t = R * iter->second.translation();
|
|
t.x() *= -1.0f;
|
|
t.y() *= -1.0f;
|
|
t.z() *= -1.0f;
|
|
out << t.x() << " " << t.y() << " " << t.z() << "\n";
|
|
}
|
|
|
|
QMessageBox::question(this,
|
|
tr("Exporting cameras in Bundler format..."),
|
|
tr("%1 cameras/images exported to directory \"%2\".").arg(poses.size()).arg(path));
|
|
fileList.close();
|
|
}
|
|
fileOut.close();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
QMessageBox::warning(this, tr("Exporting cameras..."), tr("No poses exported because of missing images. Try refreshing the cache (with clouds)."));
|
|
}
|
|
}
|
|
|
|
void MainWindow::resetOdometry()
|
|
{
|
|
UINFO("reset odometry");
|
|
this->post(new OdometryResetEvent());
|
|
}
|
|
|
|
void MainWindow::triggerNewMap()
|
|
{
|
|
UINFO("trigger a new map");
|
|
this->post(new RtabmapEventCmd(RtabmapEventCmd::kCmdTriggerNewMap));
|
|
}
|
|
|
|
void MainWindow::dataRecorder()
|
|
{
|
|
if(_dataRecorder == 0)
|
|
{
|
|
QString path = QFileDialog::getSaveFileName(this, tr("Save to..."), _preferencesDialog->getWorkingDirectory()+"/output.db", "RTAB-Map database (*.db)");
|
|
if(!path.isEmpty())
|
|
{
|
|
int r = QMessageBox::question(this, tr("Hard drive or RAM?"), tr("Save in RAM?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
|
|
|
|
if(r == QMessageBox::No || r == QMessageBox::Yes)
|
|
{
|
|
bool recordInRAM = r == QMessageBox::Yes;
|
|
|
|
_dataRecorder = new DataRecorder(this);
|
|
_dataRecorder->setWindowFlags(Qt::Dialog);
|
|
_dataRecorder->setAttribute(Qt::WA_DeleteOnClose, true);
|
|
_dataRecorder->setWindowTitle(tr("Data recorder (%1)").arg(path));
|
|
|
|
if(_dataRecorder->init(path, recordInRAM))
|
|
{
|
|
this->connect(_dataRecorder, SIGNAL(destroyed(QObject*)), this, SLOT(dataRecorderDestroyed()));
|
|
_dataRecorder->show();
|
|
_dataRecorder->registerToEventsManager();
|
|
if(_camera)
|
|
{
|
|
UEventsManager::createPipe(_camera, _dataRecorder, "CameraEvent");
|
|
}
|
|
_ui->actionData_recorder->setEnabled(false);
|
|
}
|
|
else
|
|
{
|
|
QMessageBox::warning(this, tr(""), tr("Cannot initialize the data recorder!"));
|
|
UERROR("Cannot initialize the data recorder!");
|
|
delete _dataRecorder;
|
|
_dataRecorder = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UERROR("Only one recorder at the same time.");
|
|
}
|
|
}
|
|
|
|
void MainWindow::dataRecorderDestroyed()
|
|
{
|
|
_ui->actionData_recorder->setEnabled(true);
|
|
_dataRecorder = 0;
|
|
}
|
|
|
|
//END ACTIONS
|
|
|
|
void MainWindow::saveClouds(const std::map<int, pcl::PointCloud<pcl::PointXYZRGBNormal>::Ptr> & clouds, bool binaryMode)
|
|
{
|
|
if(clouds.size() == 1)
|
|
{
|
|
QString path = QFileDialog::getSaveFileName(this, tr("Save to ..."), _preferencesDialog->getWorkingDirectory()+QDir::separator()+"cloud.ply", tr("Point cloud data (*.ply *.pcd)"));
|
|
if(!path.isEmpty())
|
|
{
|
|
if(clouds.begin()->second->size())
|
|
{
|
|
_initProgressDialog->appendText(tr("Saving the cloud (%1 points)...").arg(clouds.begin()->second->size()));
|
|
|
|
bool success =false;
|
|
if(QFileInfo(path).suffix() == "pcd")
|
|
{
|
|
success = pcl::io::savePCDFile(path.toStdString(), *clouds.begin()->second, binaryMode) == 0;
|
|
}
|
|
else if(QFileInfo(path).suffix() == "ply")
|
|
{
|
|
success = pcl::io::savePLYFile(path.toStdString(), *clouds.begin()->second, binaryMode) == 0;
|
|
}
|
|
else if(QFileInfo(path).suffix() == "")
|
|
{
|
|
//use ply by default
|
|
path += ".ply";
|
|
success = pcl::io::savePLYFile(path.toStdString(), *clouds.begin()->second, binaryMode) == 0;
|
|
}
|
|
else
|
|
{
|
|
UERROR("Extension not recognized! (%s) Should be one of (*.ply *.pcd).", QFileInfo(path).suffix().toStdString().c_str());
|
|
}
|
|
if(success)
|
|
{
|
|
_initProgressDialog->incrementStep();
|
|
_initProgressDialog->appendText(tr("Saving the cloud (%1 points)... done.").arg(clouds.begin()->second->size()));
|
|
|
|
QMessageBox::information(this, tr("Save successful!"), tr("Cloud saved to \"%1\"").arg(path));
|
|
}
|
|
else
|
|
{
|
|
QMessageBox::warning(this, tr("Save failed!"), tr("Failed to save to \"%1\"").arg(path));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
QMessageBox::warning(this, tr("Save failed!"), tr("Cloud is empty..."));
|
|
}
|
|
}
|
|
}
|
|
else if(clouds.size())
|
|
{
|
|
QString path = QFileDialog::getExistingDirectory(this, tr("Save to (*.ply *.pcd)..."), _preferencesDialog->getWorkingDirectory(), 0);
|
|
if(!path.isEmpty())
|
|
{
|
|
bool ok = false;
|
|
QStringList items;
|
|
items.push_back("ply");
|
|
items.push_back("pcd");
|
|
QString suffix = QInputDialog::getItem(this, tr("File format"), tr("Which format?"), items, 0, false, &ok);
|
|
|
|
if(ok)
|
|
{
|
|
QString prefix = QInputDialog::getText(this, tr("File prefix"), tr("Prefix:"), QLineEdit::Normal, "cloud", &ok);
|
|
|
|
if(ok)
|
|
{
|
|
for(std::map<int, pcl::PointCloud<pcl::PointXYZRGBNormal>::Ptr >::const_iterator iter=clouds.begin(); iter!=clouds.end(); ++iter)
|
|
{
|
|
if(iter->second->size())
|
|
{
|
|
pcl::PointCloud<pcl::PointXYZRGBNormal>::Ptr transformedCloud;
|
|
transformedCloud = util3d::transformPointCloud(iter->second, _currentPosesMap.at(iter->first));
|
|
|
|
QString pathFile = path+QDir::separator()+QString("%1%2.%3").arg(prefix).arg(iter->first).arg(suffix);
|
|
bool success =false;
|
|
if(suffix == "pcd")
|
|
{
|
|
success = pcl::io::savePCDFile(pathFile.toStdString(), *transformedCloud, binaryMode) == 0;
|
|
}
|
|
else if(suffix == "ply")
|
|
{
|
|
success = pcl::io::savePLYFile(pathFile.toStdString(), *transformedCloud, binaryMode) == 0;
|
|
}
|
|
else
|
|
{
|
|
UFATAL("Extension not recognized! (%s)", suffix.toStdString().c_str());
|
|
}
|
|
if(success)
|
|
{
|
|
_initProgressDialog->appendText(tr("Saved cloud %1 (%2 points) to %3.").arg(iter->first).arg(iter->second->size()).arg(pathFile));
|
|
}
|
|
else
|
|
{
|
|
_initProgressDialog->appendText(tr("Failed saving cloud %1 (%2 points) to %3.").arg(iter->first).arg(iter->second->size()).arg(pathFile));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_initProgressDialog->appendText(tr("Cloud %1 is empty!").arg(iter->first));
|
|
}
|
|
_initProgressDialog->incrementStep();
|
|
QApplication::processEvents();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void MainWindow::saveMeshes(const std::map<int, pcl::PolygonMesh::Ptr> & meshes, bool binaryMode)
|
|
{
|
|
if(meshes.size() == 1)
|
|
{
|
|
QString path = QFileDialog::getSaveFileName(this, tr("Save to ..."), _preferencesDialog->getWorkingDirectory()+QDir::separator()+"mesh.ply", tr("Mesh (*.ply)"));
|
|
if(!path.isEmpty())
|
|
{
|
|
if(meshes.begin()->second->polygons.size())
|
|
{
|
|
_initProgressDialog->appendText(tr("Saving the mesh (%1 polygons)...").arg(meshes.begin()->second->polygons.size()));
|
|
|
|
bool success =false;
|
|
if(QFileInfo(path).suffix() == "")
|
|
{
|
|
path += ".ply";
|
|
}
|
|
else if(QFileInfo(path).suffix() == "ply")
|
|
{
|
|
if(binaryMode)
|
|
{
|
|
success = pcl::io::savePLYFileBinary(path.toStdString(), *meshes.begin()->second) == 0;
|
|
}
|
|
else
|
|
{
|
|
success = pcl::io::savePLYFile(path.toStdString(), *meshes.begin()->second) == 0;
|
|
}
|
|
}
|
|
else if(QFileInfo(path).suffix() == "obj")
|
|
{
|
|
success = pcl::io::saveOBJFile(path.toStdString(), *meshes.begin()->second) == 0;
|
|
}
|
|
else
|
|
{
|
|
UERROR("Extension not recognized! (%s) Should be (*.ply).", QFileInfo(path).suffix().toStdString().c_str());
|
|
}
|
|
if(success)
|
|
{
|
|
_initProgressDialog->incrementStep();
|
|
_initProgressDialog->appendText(tr("Saving the mesh (%1 polygons)... done.").arg(meshes.begin()->second->polygons.size()));
|
|
|
|
QMessageBox::information(this, tr("Save successful!"), tr("Mesh saved to \"%1\"").arg(path));
|
|
}
|
|
else
|
|
{
|
|
QMessageBox::warning(this, tr("Save failed!"), tr("Failed to save to \"%1\"").arg(path));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
QMessageBox::warning(this, tr("Save failed!"), tr("Cloud is empty..."));
|
|
}
|
|
}
|
|
}
|
|
else if(meshes.size())
|
|
{
|
|
QString path = QFileDialog::getExistingDirectory(this, tr("Save to (*.ply *.obj)..."), _preferencesDialog->getWorkingDirectory(), 0);
|
|
if(!path.isEmpty())
|
|
{
|
|
bool ok = false;
|
|
QStringList items;
|
|
items.push_back("ply");
|
|
items.push_back("obj");
|
|
QString suffix = QInputDialog::getItem(this, tr("File format"), tr("Which format?"), items, 0, false, &ok);
|
|
|
|
if(ok)
|
|
{
|
|
QString prefix = QInputDialog::getText(this, tr("File prefix"), tr("Prefix:"), QLineEdit::Normal, "mesh", &ok);
|
|
|
|
if(ok)
|
|
{
|
|
for(std::map<int, pcl::PolygonMesh::Ptr>::const_iterator iter=meshes.begin(); iter!=meshes.end(); ++iter)
|
|
{
|
|
if(iter->second->polygons.size())
|
|
{
|
|
pcl::PolygonMesh mesh;
|
|
mesh.polygons = iter->second->polygons;
|
|
bool isRGB = false;
|
|
for(unsigned int i=0; i<iter->second->cloud.fields.size(); ++i)
|
|
{
|
|
if(iter->second->cloud.fields[i].name.compare("rgb") == 0)
|
|
{
|
|
isRGB=true;
|
|
break;
|
|
}
|
|
}
|
|
if(isRGB)
|
|
{
|
|
pcl::PointCloud<pcl::PointXYZRGB>::Ptr tmp(new pcl::PointCloud<pcl::PointXYZRGB>);
|
|
pcl::fromPCLPointCloud2(iter->second->cloud, *tmp);
|
|
tmp = util3d::transformPointCloud(tmp, _currentPosesMap.at(iter->first));
|
|
pcl::toPCLPointCloud2(*tmp, mesh.cloud);
|
|
}
|
|
else
|
|
{
|
|
pcl::PointCloud<pcl::PointXYZ>::Ptr tmp(new pcl::PointCloud<pcl::PointXYZ>);
|
|
pcl::fromPCLPointCloud2(iter->second->cloud, *tmp);
|
|
tmp = util3d::transformPointCloud(tmp, _currentPosesMap.at(iter->first));
|
|
pcl::toPCLPointCloud2(*tmp, mesh.cloud);
|
|
}
|
|
|
|
QString pathFile = path+QDir::separator()+QString("%1%2.%3").arg(prefix).arg(iter->first).arg(suffix);
|
|
bool success =false;
|
|
if(suffix == "ply")
|
|
{
|
|
if(binaryMode)
|
|
{
|
|
success = pcl::io::savePLYFileBinary(pathFile.toStdString(), mesh) == 0;
|
|
}
|
|
else
|
|
{
|
|
success = pcl::io::savePLYFile(pathFile.toStdString(), mesh) == 0;
|
|
}
|
|
}
|
|
else if(suffix == "obj")
|
|
{
|
|
success = pcl::io::saveOBJFile(pathFile.toStdString(), mesh) == 0;
|
|
}
|
|
else
|
|
{
|
|
UFATAL("Extension not recognized! (%s)", suffix.toStdString().c_str());
|
|
}
|
|
if(success)
|
|
{
|
|
_initProgressDialog->appendText(tr("Saved mesh %1 (%2 polygons) to %3.")
|
|
.arg(iter->first).arg(iter->second->polygons.size()).arg(pathFile));
|
|
}
|
|
else
|
|
{
|
|
_initProgressDialog->appendText(tr("Failed saving mesh %1 (%2 polygons) to %3.")
|
|
.arg(iter->first).arg(iter->second->polygons.size()).arg(pathFile));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_initProgressDialog->appendText(tr("Mesh %1 is empty!").arg(iter->first));
|
|
}
|
|
_initProgressDialog->incrementStep();
|
|
QApplication::processEvents();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void MainWindow::saveTextureMeshes(const std::map<int, pcl::TextureMesh::Ptr> & meshes)
|
|
{
|
|
if(meshes.size() == 1)
|
|
{
|
|
QString path = QFileDialog::getSaveFileName(this, tr("Save to ..."), _preferencesDialog->getWorkingDirectory()+QDir::separator()+"mesh.obj", tr("Mesh (*.obj)"));
|
|
if(!path.isEmpty())
|
|
{
|
|
if(meshes.begin()->second->tex_materials.size())
|
|
{
|
|
_initProgressDialog->appendText(tr("Saving the mesh (with %1 textures)...").arg(meshes.begin()->second->tex_materials.size()));
|
|
|
|
bool success =false;
|
|
if(QFileInfo(path).suffix() == "")
|
|
{
|
|
path += ".obj";
|
|
}
|
|
|
|
pcl::TextureMesh mesh;
|
|
mesh.tex_coordinates = meshes.begin()->second->tex_coordinates;
|
|
mesh.tex_materials = meshes.begin()->second->tex_materials;
|
|
removeDir(QFileInfo(path).absoluteDir().absolutePath()+QDir::separator()+QFileInfo(path).baseName());
|
|
QDir(QFileInfo(path).absoluteDir().absolutePath()).mkdir(QFileInfo(path).baseName());
|
|
for(unsigned int i=0;i<meshes.begin()->second->tex_materials.size(); ++i)
|
|
{
|
|
QFileInfo info(mesh.tex_materials[i].tex_file.c_str());
|
|
QString fullPath = QFileInfo(path).absoluteDir().absolutePath()+QDir::separator()+QFileInfo(path).baseName()+QDir::separator()+info.fileName();
|
|
// relative path
|
|
mesh.tex_materials[i].tex_file=(QFileInfo(path).baseName()+QDir::separator()+info.fileName()).toStdString();
|
|
if(!QFile::copy(meshes.begin()->second->tex_materials[i].tex_file.c_str(), fullPath))
|
|
{
|
|
_initProgressDialog->appendText(tr("Failed copying texture \"%1\" to \"%2\".")
|
|
.arg(meshes.begin()->second->tex_materials[i].tex_file.c_str()).arg(fullPath), Qt::darkRed);
|
|
_initProgressDialog->setAutoClose(false);
|
|
}
|
|
}
|
|
mesh.tex_polygons = meshes.begin()->second->tex_polygons;
|
|
mesh.cloud = meshes.begin()->second->cloud;
|
|
|
|
success = pcl::io::saveOBJFile(path.toStdString(), mesh) == 0;
|
|
if(success)
|
|
{
|
|
_initProgressDialog->incrementStep();
|
|
_initProgressDialog->appendText(tr("Saving the mesh (with %1 textures)... done.").arg(mesh.tex_materials.size()));
|
|
|
|
QMessageBox::information(this, tr("Save successful!"), tr("Mesh saved to \"%1\"").arg(path));
|
|
}
|
|
else
|
|
{
|
|
QMessageBox::warning(this, tr("Save failed!"), tr("Failed to save to \"%1\"").arg(path));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
QMessageBox::warning(this, tr("Save failed!"), tr("No textures..."));
|
|
}
|
|
}
|
|
}
|
|
else if(meshes.size())
|
|
{
|
|
QString path = QFileDialog::getExistingDirectory(this, tr("Save to (*.obj)..."), _preferencesDialog->getWorkingDirectory(), 0);
|
|
if(!path.isEmpty())
|
|
{
|
|
bool ok = false;
|
|
QString prefix = QInputDialog::getText(this, tr("File prefix"), tr("Prefix:"), QLineEdit::Normal, "mesh", &ok);
|
|
QString suffix = "obj";
|
|
|
|
if(ok)
|
|
{
|
|
for(std::map<int, pcl::TextureMesh::Ptr>::const_iterator iter=meshes.begin(); iter!=meshes.end(); ++iter)
|
|
{
|
|
QString currentPrefix=prefix+QString::number(iter->first);
|
|
if(iter->second->tex_materials.size())
|
|
{
|
|
pcl::TextureMesh mesh;
|
|
mesh.tex_coordinates = iter->second->tex_coordinates;
|
|
mesh.tex_materials = iter->second->tex_materials;
|
|
QDir(path).rmdir(currentPrefix);
|
|
QDir(path).mkdir(currentPrefix);
|
|
for(unsigned int i=0;i<iter->second->tex_materials.size(); ++i)
|
|
{
|
|
QFileInfo info(mesh.tex_materials[i].tex_file.c_str());
|
|
QString fullPath = path+QDir::separator()+currentPrefix+QDir::separator()+info.fileName();
|
|
// relative path
|
|
mesh.tex_materials[i].tex_file=(currentPrefix+QDir::separator()+info.fileName()).toStdString();
|
|
if(!QFile::copy(iter->second->tex_materials[i].tex_file.c_str(), fullPath))
|
|
{
|
|
_initProgressDialog->appendText(tr("Failed copying texture \"%1\" to \"%2\".")
|
|
.arg(iter->second->tex_materials[i].tex_file.c_str()).arg(fullPath), Qt::darkRed);
|
|
_initProgressDialog->setAutoClose(false);
|
|
}
|
|
}
|
|
mesh.tex_polygons = iter->second->tex_polygons;
|
|
pcl::PointCloud<pcl::PointNormal>::Ptr tmp(new pcl::PointCloud<pcl::PointNormal>);
|
|
pcl::fromPCLPointCloud2(iter->second->cloud, *tmp);
|
|
tmp = util3d::transformPointCloud(tmp, _currentPosesMap.at(iter->first));
|
|
pcl::toPCLPointCloud2(*tmp, mesh.cloud);
|
|
|
|
QString pathFile = path+QDir::separator()+QString("%1.%3").arg(currentPrefix).arg(suffix);
|
|
bool success =false;
|
|
if(suffix == "obj")
|
|
{
|
|
success = pcl::io::saveOBJFile(pathFile.toStdString(), mesh) == 0;
|
|
}
|
|
else
|
|
{
|
|
UFATAL("Extension not recognized! (%s)", suffix.toStdString().c_str());
|
|
}
|
|
if(success)
|
|
{
|
|
_initProgressDialog->appendText(tr("Saved mesh %1 (%2 textures) to %3.")
|
|
.arg(iter->first).arg(iter->second->tex_materials.size()-1).arg(pathFile));
|
|
}
|
|
else
|
|
{
|
|
_initProgressDialog->appendText(tr("Failed saving mesh %1 (%2 textures) to %3.")
|
|
.arg(iter->first).arg(iter->second->tex_materials.size()-1).arg(pathFile), Qt::darkRed);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_initProgressDialog->appendText(tr("Mesh %1 is empty!").arg(iter->first));
|
|
}
|
|
_initProgressDialog->incrementStep();
|
|
QApplication::processEvents();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void MainWindow::saveScans(const std::map<int, pcl::PointCloud<pcl::PointXYZ>::Ptr> & scans, bool binaryMode)
|
|
{
|
|
if(scans.size() == 1)
|
|
{
|
|
QString path = QFileDialog::getSaveFileName(this, tr("Save to ..."), _preferencesDialog->getWorkingDirectory()+QDir::separator()+"scan.ply", tr("Point cloud data (*.ply *.pcd)"));
|
|
if(!path.isEmpty())
|
|
{
|
|
if(scans.begin()->second->size())
|
|
{
|
|
_initProgressDialog->appendText(tr("Saving the scan (%1 points)...").arg(scans.begin()->second->size()));
|
|
|
|
bool success =false;
|
|
if(QFileInfo(path).suffix() == "pcd")
|
|
{
|
|
success = pcl::io::savePCDFile(path.toStdString(), *scans.begin()->second, binaryMode) == 0;
|
|
}
|
|
else if(QFileInfo(path).suffix() == "ply")
|
|
{
|
|
success = pcl::io::savePLYFile(path.toStdString(), *scans.begin()->second, binaryMode) == 0;
|
|
}
|
|
else if(QFileInfo(path).suffix() == "")
|
|
{
|
|
//use ply by default
|
|
path += ".ply";
|
|
success = pcl::io::savePLYFile(path.toStdString(), *scans.begin()->second, binaryMode) == 0;
|
|
}
|
|
else
|
|
{
|
|
UERROR("Extension not recognized! (%s) Should be one of (*.ply *.pcd).", QFileInfo(path).suffix().toStdString().c_str());
|
|
}
|
|
if(success)
|
|
{
|
|
_initProgressDialog->incrementStep();
|
|
_initProgressDialog->appendText(tr("Saving the scan (%1 points)... done.").arg(scans.begin()->second->size()));
|
|
|
|
QMessageBox::information(this, tr("Save successful!"), tr("Scan saved to \"%1\"").arg(path));
|
|
}
|
|
else
|
|
{
|
|
QMessageBox::warning(this, tr("Save failed!"), tr("Failed to save to \"%1\"").arg(path));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
QMessageBox::warning(this, tr("Save failed!"), tr("Scan is empty..."));
|
|
}
|
|
}
|
|
}
|
|
else if(scans.size())
|
|
{
|
|
QString path = QFileDialog::getExistingDirectory(this, tr("Save to (*.ply *.pcd)..."), _preferencesDialog->getWorkingDirectory(), 0);
|
|
if(!path.isEmpty())
|
|
{
|
|
bool ok = false;
|
|
QStringList items;
|
|
items.push_back("ply");
|
|
items.push_back("pcd");
|
|
QString suffix = QInputDialog::getItem(this, tr("File format"), tr("Which format?"), items, 0, false, &ok);
|
|
|
|
if(ok)
|
|
{
|
|
QString prefix = QInputDialog::getText(this, tr("File prefix"), tr("Prefix:"), QLineEdit::Normal, "scan", &ok);
|
|
|
|
if(ok)
|
|
{
|
|
for(std::map<int, pcl::PointCloud<pcl::PointXYZ>::Ptr >::const_iterator iter=scans.begin(); iter!=scans.end(); ++iter)
|
|
{
|
|
if(iter->second->size())
|
|
{
|
|
pcl::PointCloud<pcl::PointXYZ>::Ptr transformedCloud;
|
|
transformedCloud = util3d::transformPointCloud(iter->second, _currentPosesMap.at(iter->first));
|
|
|
|
QString pathFile = path+QDir::separator()+QString("%1%2.%3").arg(prefix).arg(iter->first).arg(suffix);
|
|
bool success =false;
|
|
if(suffix == "pcd")
|
|
{
|
|
success = pcl::io::savePCDFile(pathFile.toStdString(), *transformedCloud, binaryMode) == 0;
|
|
}
|
|
else if(suffix == "ply")
|
|
{
|
|
success = pcl::io::savePLYFile(pathFile.toStdString(), *transformedCloud, binaryMode) == 0;
|
|
}
|
|
else
|
|
{
|
|
UFATAL("Extension not recognized! (%s)", suffix.toStdString().c_str());
|
|
}
|
|
if(success)
|
|
{
|
|
_initProgressDialog->appendText(tr("Saved scan %1 (%2 points) to %3.").arg(iter->first).arg(iter->second->size()).arg(pathFile));
|
|
}
|
|
else
|
|
{
|
|
_initProgressDialog->appendText(tr("Failed saving scan %1 (%2 points) to %3.").arg(iter->first).arg(iter->second->size()).arg(pathFile));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_initProgressDialog->appendText(tr("Scan %1 is empty!").arg(iter->first));
|
|
}
|
|
_initProgressDialog->incrementStep();
|
|
QApplication::processEvents();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
std::map<int, pcl::PointCloud<pcl::PointXYZRGB>::Ptr > MainWindow::getClouds(
|
|
const std::map<int, Transform> & poses,
|
|
bool regenerateClouds,
|
|
int regenerateDecimation,
|
|
float regenerateVoxelSize,
|
|
float regenerateMaxDepth,
|
|
float filteringRadius,
|
|
float filteringMinNeighbors) const
|
|
{
|
|
std::map<int, pcl::PointCloud<pcl::PointXYZRGB>::Ptr> clouds;
|
|
int i=0;
|
|
for(std::map<int, Transform>::const_iterator iter = poses.begin(); iter!=poses.end(); ++iter)
|
|
{
|
|
bool inserted = false;
|
|
if(!iter->second.isNull())
|
|
{
|
|
pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZRGB>);
|
|
if(regenerateClouds)
|
|
{
|
|
if(_cachedSignatures.contains(iter->first))
|
|
{
|
|
const Signature & s = _cachedSignatures.find(iter->first).value();
|
|
SensorData d = s.sensorData();
|
|
cv::Mat image, depth;
|
|
d.uncompressData(&image, &depth, 0);
|
|
if(!image.empty() && !depth.empty())
|
|
{
|
|
UASSERT(iter->first == d.id());
|
|
cloud = util3d::cloudRGBFromSensorData(
|
|
d,
|
|
regenerateDecimation,
|
|
regenerateMaxDepth,
|
|
regenerateVoxelSize);
|
|
}
|
|
else if(s.getWords3().size())
|
|
{
|
|
cloud->resize(s.getWords3().size());
|
|
int oi=0;
|
|
for(std::multimap<int, pcl::PointXYZ>::const_iterator jter=s.getWords3().begin(); jter!=s.getWords3().end(); ++jter)
|
|
{
|
|
(*cloud)[oi].x = jter->second.x;
|
|
(*cloud)[oi].y = jter->second.y;
|
|
(*cloud)[oi].z = jter->second.z;
|
|
(*cloud)[oi].r = 255;
|
|
(*cloud)[oi].g = 255;
|
|
(*cloud)[oi++].b = 255;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UERROR("Cloud %d not found in cache!", iter->first);
|
|
}
|
|
}
|
|
else if(uContains(_createdClouds, iter->first))
|
|
{
|
|
cloud = _createdClouds.at(iter->first);
|
|
}
|
|
|
|
if(cloud->size())
|
|
{
|
|
if(filteringRadius > 0.0f && filteringMinNeighbors > 0)
|
|
{
|
|
pcl::IndicesPtr indices = util3d::radiusFiltering(cloud, filteringRadius, filteringMinNeighbors);
|
|
pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloudFiltered(new pcl::PointCloud<pcl::PointXYZRGB>);
|
|
pcl::copyPointCloud(*cloud, *indices, *cloudFiltered);
|
|
cloud = cloudFiltered;
|
|
}
|
|
|
|
clouds.insert(std::make_pair(iter->first, cloud));
|
|
inserted = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UERROR("transform is null!?");
|
|
}
|
|
|
|
if(inserted)
|
|
{
|
|
_initProgressDialog->appendText(tr("Generated cloud %1 (%2/%3).").arg(iter->first).arg(++i).arg(poses.size()));
|
|
}
|
|
else
|
|
{
|
|
_initProgressDialog->appendText(tr("Ignored cloud %1 (%2/%3).").arg(iter->first).arg(++i).arg(poses.size()));
|
|
}
|
|
_initProgressDialog->incrementStep();
|
|
QApplication::processEvents();
|
|
}
|
|
|
|
return clouds;
|
|
}
|
|
|
|
// STATES
|
|
|
|
// in monitoring state, only some actions are enabled
|
|
void MainWindow::setMonitoringState(bool pauseChecked)
|
|
{
|
|
this->changeState(pauseChecked?kMonitoringPaused:kMonitoring);
|
|
}
|
|
|
|
// Must be called by the GUI thread, use signal StateChanged()
|
|
void MainWindow::changeState(MainWindow::State newState)
|
|
{
|
|
bool monitoring = newState==kMonitoring || newState == kMonitoringPaused;
|
|
_ui->actionNew_database->setVisible(!monitoring);
|
|
_ui->actionOpen_database->setVisible(!monitoring);
|
|
_ui->actionClose_database->setVisible(!monitoring);
|
|
_ui->actionEdit_database->setVisible(!monitoring);
|
|
_ui->actionStart->setVisible(!monitoring);
|
|
_ui->actionStop->setVisible(!monitoring);
|
|
_ui->actionDump_the_memory->setVisible(!monitoring);
|
|
_ui->actionDump_the_prediction_matrix->setVisible(!monitoring);
|
|
_ui->actionGenerate_map->setVisible(!monitoring);
|
|
_ui->actionUpdate_cache_from_database->setVisible(monitoring);
|
|
_ui->actionOpen_working_directory->setVisible(!monitoring);
|
|
_ui->actionData_recorder->setVisible(!monitoring);
|
|
_ui->menuSelect_source->menuAction()->setVisible(!monitoring);
|
|
_ui->doubleSpinBox_stats_imgRate->setVisible(!monitoring);
|
|
_ui->doubleSpinBox_stats_imgRate_label->setVisible(!monitoring);
|
|
bool wasMonitoring = _state==kMonitoring || _state == kMonitoringPaused;
|
|
if(wasMonitoring != monitoring)
|
|
{
|
|
_ui->toolBar->setVisible(!monitoring);
|
|
_ui->toolBar->toggleViewAction()->setVisible(!monitoring);
|
|
}
|
|
QList<QAction*> actions = _ui->menuTools->actions();
|
|
for(int i=0; i<actions.size(); ++i)
|
|
{
|
|
if(actions.at(i)->isSeparator())
|
|
{
|
|
actions.at(i)->setVisible(!monitoring);
|
|
}
|
|
}
|
|
actions = _ui->menuFile->actions();
|
|
if(actions.size()==15)
|
|
{
|
|
if(actions.at(2)->isSeparator())
|
|
{
|
|
actions.at(2)->setVisible(!monitoring);
|
|
}
|
|
else
|
|
{
|
|
UWARN("Menu File separators have not the same order.");
|
|
}
|
|
if(actions.at(11)->isSeparator())
|
|
{
|
|
actions.at(11)->setVisible(!monitoring);
|
|
}
|
|
else
|
|
{
|
|
UWARN("Menu File separators have not the same order.");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UWARN("Menu File actions size has changed (%d)", actions.size());
|
|
}
|
|
actions = _ui->menuProcess->actions();
|
|
if(actions.size()>=2)
|
|
{
|
|
if(actions.at(1)->isSeparator())
|
|
{
|
|
actions.at(1)->setVisible(!monitoring);
|
|
}
|
|
else
|
|
{
|
|
UWARN("Menu File separators have not the same order.");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UWARN("Menu File separators have not the same order.");
|
|
}
|
|
|
|
switch (newState)
|
|
{
|
|
case kIdle: // RTAB-Map is not initialized yet
|
|
_ui->actionNew_database->setEnabled(true);
|
|
_ui->actionOpen_database->setEnabled(true);
|
|
_ui->actionClose_database->setEnabled(false);
|
|
_ui->actionEdit_database->setEnabled(true);
|
|
_ui->actionStart->setEnabled(false);
|
|
_ui->actionPause->setEnabled(false);
|
|
_ui->actionPause->setChecked(false);
|
|
_ui->actionPause->setToolTip(tr("Pause"));
|
|
_ui->actionStop->setEnabled(false);
|
|
_ui->actionPause_on_match->setEnabled(true);
|
|
_ui->actionPause_on_local_loop_detection->setEnabled(true);
|
|
_ui->actionPause_when_a_loop_hypothesis_is_rejected->setEnabled(true);
|
|
_ui->actionDump_the_memory->setEnabled(false);
|
|
_ui->actionDump_the_prediction_matrix->setEnabled(false);
|
|
_ui->actionDelete_memory->setEnabled(false);
|
|
_ui->actionPost_processing->setEnabled(_cachedSignatures.size() >= 2 && _currentPosesMap.size() >= 2 && _currentLinksMap.size() >= 1);
|
|
_ui->actionExport_images_RGB_jpg_Depth_png->setEnabled(!_cachedSignatures.empty() && !_currentPosesMap.empty());
|
|
_ui->actionGenerate_map->setEnabled(false);
|
|
_ui->menuExport_poses->setEnabled(!_currentPosesMap.empty());
|
|
_ui->actionSave_point_cloud->setEnabled(!_createdClouds.empty());
|
|
_ui->actionView_high_res_point_cloud->setEnabled(!_createdClouds.empty());
|
|
_ui->actionExport_2D_scans_ply_pcd->setEnabled(!_createdScans.empty());
|
|
_ui->actionExport_2D_Grid_map_bmp_png->setEnabled(!_gridLocalMaps.empty() || !_projectionLocalMaps.empty());
|
|
_ui->actionView_scans->setEnabled(!_createdScans.empty());
|
|
_ui->actionExport_cameras_in_Bundle_format_out->setEnabled(!_cachedSignatures.empty() && !_currentPosesMap.empty());
|
|
_ui->actionDownload_all_clouds->setEnabled(false);
|
|
_ui->actionDownload_graph->setEnabled(false);
|
|
_ui->menuSelect_source->setEnabled(true);
|
|
_ui->actionLabel_current_location->setEnabled(false);
|
|
_ui->actionSend_goal->setEnabled(false);
|
|
_ui->actionCancel_goal->setEnabled(false);
|
|
_ui->toolBar->findChild<QAction*>("toolbar_source")->setEnabled(true);
|
|
_ui->actionTrigger_a_new_map->setEnabled(false);
|
|
_ui->doubleSpinBox_stats_imgRate->setEnabled(true);
|
|
_ui->statusbar->clearMessage();
|
|
_state = newState;
|
|
_oneSecondTimer->stop();
|
|
break;
|
|
|
|
case kApplicationClosing:
|
|
case kClosing:
|
|
_ui->actionStart->setEnabled(false);
|
|
_ui->actionPause->setEnabled(false);
|
|
_ui->actionStop->setEnabled(false);
|
|
_state = newState;
|
|
break;
|
|
|
|
case kInitializing:
|
|
_ui->actionNew_database->setEnabled(false);
|
|
_ui->actionOpen_database->setEnabled(false);
|
|
_ui->actionClose_database->setEnabled(false);
|
|
_ui->actionEdit_database->setEnabled(false);
|
|
_state = newState;
|
|
break;
|
|
|
|
case kInitialized:
|
|
_ui->actionNew_database->setEnabled(false);
|
|
_ui->actionOpen_database->setEnabled(false);
|
|
_ui->actionClose_database->setEnabled(true);
|
|
_ui->actionEdit_database->setEnabled(false);
|
|
_ui->actionStart->setEnabled(true);
|
|
_ui->actionPause->setEnabled(false);
|
|
_ui->actionPause->setChecked(false);
|
|
_ui->actionPause->setToolTip(tr("Pause"));
|
|
_ui->actionStop->setEnabled(false);
|
|
_ui->actionPause_on_match->setEnabled(true);
|
|
_ui->actionPause_on_local_loop_detection->setEnabled(true);
|
|
_ui->actionPause_when_a_loop_hypothesis_is_rejected->setEnabled(true);
|
|
_ui->actionDump_the_memory->setEnabled(true);
|
|
_ui->actionDump_the_prediction_matrix->setEnabled(true);
|
|
_ui->actionDelete_memory->setEnabled(true);
|
|
_ui->actionPost_processing->setEnabled(_cachedSignatures.size() >= 2 && _currentPosesMap.size() >= 2 && _currentLinksMap.size() >= 1);
|
|
_ui->actionExport_images_RGB_jpg_Depth_png->setEnabled(!_cachedSignatures.empty() && !_currentPosesMap.empty());
|
|
_ui->actionGenerate_map->setEnabled(true);
|
|
_ui->menuExport_poses->setEnabled(!_currentPosesMap.empty());
|
|
_ui->actionSave_point_cloud->setEnabled(!_createdClouds.empty());
|
|
_ui->actionView_high_res_point_cloud->setEnabled(!_createdClouds.empty());
|
|
_ui->actionExport_2D_scans_ply_pcd->setEnabled(!_createdScans.empty());
|
|
_ui->actionExport_2D_Grid_map_bmp_png->setEnabled(!_gridLocalMaps.empty() || !_projectionLocalMaps.empty());
|
|
_ui->actionView_scans->setEnabled(!_createdScans.empty());
|
|
_ui->actionExport_cameras_in_Bundle_format_out->setEnabled(!_cachedSignatures.empty() && !_currentPosesMap.empty());
|
|
_ui->actionDownload_all_clouds->setEnabled(true);
|
|
_ui->actionDownload_graph->setEnabled(true);
|
|
_ui->menuSelect_source->setEnabled(true);
|
|
_ui->actionLabel_current_location->setEnabled(true);
|
|
_ui->actionSend_goal->setEnabled(true);
|
|
_ui->actionCancel_goal->setEnabled(true);
|
|
_ui->toolBar->findChild<QAction*>("toolbar_source")->setEnabled(true);
|
|
_ui->actionTrigger_a_new_map->setEnabled(true);
|
|
_ui->doubleSpinBox_stats_imgRate->setEnabled(true);
|
|
_ui->statusbar->clearMessage();
|
|
_state = newState;
|
|
_oneSecondTimer->stop();
|
|
break;
|
|
|
|
case kStartingDetection:
|
|
_ui->actionStart->setEnabled(false);
|
|
_state = newState;
|
|
break;
|
|
|
|
case kDetecting:
|
|
_ui->actionNew_database->setEnabled(false);
|
|
_ui->actionOpen_database->setEnabled(false);
|
|
_ui->actionClose_database->setEnabled(false);
|
|
_ui->actionEdit_database->setEnabled(false);
|
|
_ui->actionStart->setEnabled(false);
|
|
_ui->actionPause->setEnabled(true);
|
|
_ui->actionPause->setChecked(false);
|
|
_ui->actionPause->setToolTip(tr("Pause"));
|
|
_ui->actionStop->setEnabled(true);
|
|
_ui->actionPause_on_match->setEnabled(true);
|
|
_ui->actionPause_on_local_loop_detection->setEnabled(true);
|
|
_ui->actionPause_when_a_loop_hypothesis_is_rejected->setEnabled(true);
|
|
_ui->actionDump_the_memory->setEnabled(false);
|
|
_ui->actionDump_the_prediction_matrix->setEnabled(false);
|
|
_ui->actionDelete_memory->setEnabled(false);
|
|
_ui->actionPost_processing->setEnabled(false);
|
|
_ui->actionExport_images_RGB_jpg_Depth_png->setEnabled(false);
|
|
_ui->actionGenerate_map->setEnabled(false);
|
|
_ui->menuExport_poses->setEnabled(false);
|
|
_ui->actionSave_point_cloud->setEnabled(false);
|
|
_ui->actionView_high_res_point_cloud->setEnabled(false);
|
|
_ui->actionExport_2D_scans_ply_pcd->setEnabled(false);
|
|
_ui->actionExport_2D_Grid_map_bmp_png->setEnabled(false);
|
|
_ui->actionView_scans->setEnabled(false);
|
|
_ui->actionExport_cameras_in_Bundle_format_out->setEnabled(false);
|
|
_ui->actionDownload_all_clouds->setEnabled(false);
|
|
_ui->actionDownload_graph->setEnabled(false);
|
|
_ui->menuSelect_source->setEnabled(false);
|
|
_ui->actionLabel_current_location->setEnabled(true);
|
|
_ui->actionSend_goal->setEnabled(true);
|
|
_ui->actionCancel_goal->setEnabled(true);
|
|
_ui->toolBar->findChild<QAction*>("toolbar_source")->setEnabled(false);
|
|
_ui->actionTrigger_a_new_map->setEnabled(true);
|
|
_ui->doubleSpinBox_stats_imgRate->setEnabled(true);
|
|
_ui->statusbar->showMessage(tr("Detecting..."));
|
|
_state = newState;
|
|
_ui->label_elapsedTime->setText("00:00:00");
|
|
_elapsedTime->start();
|
|
_oneSecondTimer->start();
|
|
|
|
_databaseUpdated = true; // if a new database is used, it won't be empty anymore...
|
|
|
|
if(_camera)
|
|
{
|
|
_camera->start();
|
|
}
|
|
|
|
if(_dbReader)
|
|
{
|
|
_dbReader->start();
|
|
}
|
|
break;
|
|
|
|
case kPaused:
|
|
if(_state == kPaused)
|
|
{
|
|
_ui->actionPause->setToolTip(tr("Pause"));
|
|
_ui->actionPause->setChecked(false);
|
|
_ui->statusbar->showMessage(tr("Detecting..."));
|
|
_ui->actionDump_the_memory->setEnabled(false);
|
|
_ui->actionDump_the_prediction_matrix->setEnabled(false);
|
|
_ui->actionDelete_memory->setEnabled(false);
|
|
_ui->actionPost_processing->setEnabled(false);
|
|
_ui->actionExport_images_RGB_jpg_Depth_png->setEnabled(false);
|
|
_ui->actionGenerate_map->setEnabled(false);
|
|
_ui->menuExport_poses->setEnabled(false);
|
|
_ui->actionSave_point_cloud->setEnabled(false);
|
|
_ui->actionView_high_res_point_cloud->setEnabled(false);
|
|
_ui->actionExport_2D_scans_ply_pcd->setEnabled(false);
|
|
_ui->actionExport_2D_Grid_map_bmp_png->setEnabled(false);
|
|
_ui->actionView_scans->setEnabled(false);
|
|
_ui->actionExport_cameras_in_Bundle_format_out->setEnabled(false);
|
|
_ui->actionDownload_all_clouds->setEnabled(false);
|
|
_ui->actionDownload_graph->setEnabled(false);
|
|
_state = kDetecting;
|
|
_elapsedTime->start();
|
|
_oneSecondTimer->start();
|
|
|
|
if(_camera)
|
|
{
|
|
_camera->start();
|
|
}
|
|
|
|
if(_dbReader)
|
|
{
|
|
_dbReader->start();
|
|
}
|
|
}
|
|
else if(_state == kDetecting)
|
|
{
|
|
_ui->actionPause->setToolTip(tr("Continue (shift-click for step-by-step)"));
|
|
_ui->actionPause->setChecked(true);
|
|
_ui->statusbar->showMessage(tr("Paused..."));
|
|
_ui->actionDump_the_memory->setEnabled(true);
|
|
_ui->actionDump_the_prediction_matrix->setEnabled(true);
|
|
_ui->actionDelete_memory->setEnabled(false);
|
|
_ui->actionPost_processing->setEnabled(_cachedSignatures.size() >= 2 && _currentPosesMap.size() >= 2 && _currentLinksMap.size() >= 1);
|
|
_ui->actionExport_images_RGB_jpg_Depth_png->setEnabled(!_cachedSignatures.empty() && !_currentPosesMap.empty());
|
|
_ui->actionGenerate_map->setEnabled(true);
|
|
_ui->menuExport_poses->setEnabled(!_currentPosesMap.empty());
|
|
_ui->actionSave_point_cloud->setEnabled(!_createdClouds.empty());
|
|
_ui->actionView_high_res_point_cloud->setEnabled(!_createdClouds.empty());
|
|
_ui->actionExport_2D_scans_ply_pcd->setEnabled(!_createdScans.empty());
|
|
_ui->actionExport_2D_Grid_map_bmp_png->setEnabled(!_gridLocalMaps.empty() || !_projectionLocalMaps.empty());
|
|
_ui->actionView_scans->setEnabled(!_createdScans.empty());
|
|
_ui->actionExport_cameras_in_Bundle_format_out->setEnabled(!_cachedSignatures.empty() && !_currentPosesMap.empty());
|
|
_ui->actionDownload_all_clouds->setEnabled(true);
|
|
_ui->actionDownload_graph->setEnabled(true);
|
|
_state = kPaused;
|
|
_oneSecondTimer->stop();
|
|
|
|
// kill sensors
|
|
if(_camera)
|
|
{
|
|
_camera->join(true);
|
|
}
|
|
|
|
if(_dbReader)
|
|
{
|
|
_dbReader->join(true);
|
|
}
|
|
}
|
|
break;
|
|
case kMonitoring:
|
|
_ui->actionPause->setEnabled(true);
|
|
_ui->actionPause->setChecked(false);
|
|
_ui->actionPause->setToolTip(tr("Pause"));
|
|
_ui->actionPause_on_match->setEnabled(true);
|
|
_ui->actionPause_on_local_loop_detection->setEnabled(true);
|
|
_ui->actionPause_when_a_loop_hypothesis_is_rejected->setEnabled(true);
|
|
_ui->actionReset_Odometry->setEnabled(true);
|
|
_ui->actionPost_processing->setEnabled(false);
|
|
_ui->actionExport_images_RGB_jpg_Depth_png->setEnabled(false);
|
|
_ui->menuExport_poses->setEnabled(false);
|
|
_ui->actionSave_point_cloud->setEnabled(false);
|
|
_ui->actionView_high_res_point_cloud->setEnabled(false);
|
|
_ui->actionExport_2D_scans_ply_pcd->setEnabled(false);
|
|
_ui->actionExport_2D_Grid_map_bmp_png->setEnabled(false);
|
|
_ui->actionView_scans->setEnabled(false);
|
|
_ui->actionExport_cameras_in_Bundle_format_out->setEnabled(false);
|
|
_ui->actionDelete_memory->setEnabled(true);
|
|
_ui->actionDownload_all_clouds->setEnabled(true);
|
|
_ui->actionDownload_graph->setEnabled(true);
|
|
_ui->actionTrigger_a_new_map->setEnabled(true);
|
|
_ui->actionLabel_current_location->setEnabled(true);
|
|
_ui->actionSend_goal->setEnabled(true);
|
|
_ui->actionCancel_goal->setEnabled(true);
|
|
_ui->statusbar->showMessage(tr("Monitoring..."));
|
|
_state = newState;
|
|
_elapsedTime->start();
|
|
_oneSecondTimer->start();
|
|
this->post(new RtabmapEventCmd(RtabmapEventCmd::kCmdResume));
|
|
break;
|
|
case kMonitoringPaused:
|
|
_ui->actionPause->setToolTip(tr("Continue"));
|
|
_ui->actionPause->setChecked(true);
|
|
_ui->actionPause->setEnabled(true);
|
|
_ui->actionPause_on_match->setEnabled(true);
|
|
_ui->actionPause_on_local_loop_detection->setEnabled(true);
|
|
_ui->actionPause_when_a_loop_hypothesis_is_rejected->setEnabled(true);
|
|
_ui->actionReset_Odometry->setEnabled(true);
|
|
_ui->actionPost_processing->setEnabled(_cachedSignatures.size() >= 2 && _currentPosesMap.size() >= 2 && _currentLinksMap.size() >= 1);
|
|
_ui->actionExport_images_RGB_jpg_Depth_png->setEnabled(!_cachedSignatures.empty() && !_currentPosesMap.empty());
|
|
_ui->menuExport_poses->setEnabled(!_currentPosesMap.empty());
|
|
_ui->actionSave_point_cloud->setEnabled(!_createdClouds.empty());
|
|
_ui->actionView_high_res_point_cloud->setEnabled(!_createdClouds.empty());
|
|
_ui->actionExport_2D_scans_ply_pcd->setEnabled(!_createdScans.empty());
|
|
_ui->actionExport_2D_Grid_map_bmp_png->setEnabled(!_gridLocalMaps.empty() || !_projectionLocalMaps.empty());
|
|
_ui->actionView_scans->setEnabled(!_createdScans.empty());
|
|
_ui->actionExport_cameras_in_Bundle_format_out->setEnabled(!_cachedSignatures.empty() && !_currentPosesMap.empty());
|
|
_ui->actionDelete_memory->setEnabled(true);
|
|
_ui->actionDownload_all_clouds->setEnabled(true);
|
|
_ui->actionDownload_graph->setEnabled(true);
|
|
_ui->actionTrigger_a_new_map->setEnabled(true);
|
|
_ui->actionLabel_current_location->setEnabled(true);
|
|
_ui->actionSend_goal->setEnabled(true);
|
|
_ui->actionCancel_goal->setEnabled(true);
|
|
_ui->statusbar->showMessage(tr("Monitoring paused..."));
|
|
_state = newState;
|
|
_oneSecondTimer->stop();
|
|
this->post(new RtabmapEventCmd(RtabmapEventCmd::kCmdPause));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
}
|