From e3b15a710619267266936a5761e5ee981309a5bb Mon Sep 17 00:00:00 2001 From: matlabbe Date: Tue, 28 Aug 2012 13:44:57 +0000 Subject: [PATCH] Modified parameters' name to reduce the number of pages in the parameters toolbox. Added a way to sort (with a prefix number) parameters in toolbox. Can now switch between nearest neighbor strategies (kd-trees, k-means, linear). Added parameters for all FLANN nearest neighbor approaches. Added likelihood plot (including UPlot from UtiLite library and PdfPlot from RTAB-Map library). Added general parameter "MirrorView". Added general parameter "InvertedSearch" to create the vocabulary from objects descriptor instead of the scene descriptors. We can then compute likelihood of the current scene with all other objects (or another scenes). Fixed a crash (in AddObjectDialog) when selecting outside the image. Added OpenCV version used in AboutDialog. git-svn-id: http://find-object.googlecode.com/svn/trunk/find_object@109 620bd6b2-0a58-f614-fd9a-1bd335dccda9 --- app/CMakeLists.txt | 4 + src/AboutDialog.cpp | 2 + src/AddObjectDialog.cpp | 31 + src/Camera.cpp | 24 +- src/MainWindow.cpp | 418 +++--- src/MainWindow.h | 17 +- src/ObjWidget.cpp | 30 + src/ObjWidget.h | 2 + src/ParametersToolBox.cpp | 65 +- src/Settings.cpp | 332 +++-- src/Settings.h | 157 ++- src/rtabmap/PdfPlot.cpp | 163 +++ src/rtabmap/PdfPlot.h | 69 + src/ui/aboutDialog.ui | 21 +- src/ui/mainWindow.ui | 439 ++++--- src/utilite/UPlot.cpp | 2559 +++++++++++++++++++++++++++++++++++++ src/utilite/UPlot.h | 598 +++++++++ 17 files changed, 4453 insertions(+), 478 deletions(-) create mode 100644 src/rtabmap/PdfPlot.cpp create mode 100644 src/rtabmap/PdfPlot.h create mode 100644 src/utilite/UPlot.cpp create mode 100644 src/utilite/UPlot.h diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 647b0e00..47a991db 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -8,6 +8,8 @@ SET(headers_ui ../src/Camera.h ../src/ParametersToolBox.h ../src/AboutDialog.h + ../src/utilite/UPlot.h + ../src/rtabmap/PdfPlot.h ) SET(uis @@ -42,6 +44,8 @@ SET(SRC_FILES ../src/Settings.cpp ../src/ObjWidget.cpp ../src/AboutDialog.cpp + ../src/utilite/UPlot.cpp + ../src/rtabmap/PdfPlot.cpp ${moc_srcs} ${moc_uis} ${srcs_qrc} diff --git a/src/AboutDialog.cpp b/src/AboutDialog.cpp index c9476c54..d4bb9fe8 100644 --- a/src/AboutDialog.cpp +++ b/src/AboutDialog.cpp @@ -4,6 +4,7 @@ #include "AboutDialog.h" #include "ui_aboutDialog.h" +#include AboutDialog::AboutDialog(QWidget * parent) : QDialog(parent) @@ -11,6 +12,7 @@ AboutDialog::AboutDialog(QWidget * parent) : ui_ = new Ui_aboutDialog(); ui_->setupUi(this); ui_->label_version->setText(PROJECT_VERSION); + ui_->label_version_opencv->setText(CV_VERSION); } AboutDialog::~AboutDialog() diff --git a/src/AddObjectDialog.cpp b/src/AddObjectDialog.cpp index 8f28f324..ec3e4146 100644 --- a/src/AddObjectDialog.cpp +++ b/src/AddObjectDialog.cpp @@ -91,6 +91,37 @@ void AddObjectDialog::updateNextButton() void AddObjectDialog::updateNextButton(const QRect & rect) { roi_ = rect; + if(roi_.isValid() && ui_->cameraView->cvImage().cols) + { + //clip roi + if( roi_.x() >= ui_->cameraView->cvImage().cols || + roi_.x()+roi_.width() <= 0 || + roi_.y() >= ui_->cameraView->cvImage().rows || + roi_.y()+roi_.height() <= 0) + { + //Not valid... + roi_ = QRect(); + } + else + { + if(roi_.x() < 0) + { + roi_.setX(0); + } + if(roi_.x() + roi_.width() > ui_->cameraView->cvImage().cols) + { + roi_.setWidth(ui_->cameraView->cvImage().cols - roi_.x()); + } + if(roi_.y() < 0) + { + roi_.setY(0); + } + if(roi_.y() + roi_.height() > ui_->cameraView->cvImage().rows) + { + roi_.setHeight(ui_->cameraView->cvImage().rows - roi_.y()); + } + } + } if(state_ == kSelectFeatures) { if(ui_->comboBox_selection->currentIndex() == 1) diff --git a/src/Camera.cpp b/src/Camera.cpp index 5a4a2656..0a82af54 100644 --- a/src/Camera.cpp +++ b/src/Camera.cpp @@ -44,13 +44,13 @@ void Camera::takeImage() else { //resize - if( Settings::getCamera_imageWidth() && - Settings::getCamera_imageHeight() && - Settings::getCamera_imageWidth() != img.cols && - Settings::getCamera_imageHeight() != img.rows) + if( Settings::getCamera_2imageWidth() && + Settings::getCamera_3imageHeight() && + Settings::getCamera_2imageWidth() != img.cols && + Settings::getCamera_3imageHeight() != img.rows) { cv::Mat resampled; - cv::resize(img, resampled, cv::Size(Settings::getCamera_imageWidth(), Settings::getCamera_imageHeight())); + cv::resize(img, resampled, cv::Size(Settings::getCamera_2imageWidth(), Settings::getCamera_3imageHeight())); emit imageReceived(resampled); } else @@ -65,7 +65,7 @@ bool Camera::start() { if(!capture_.isOpened()) { - QString videoFile = Settings::getCamera_videoFilePath(); + QString videoFile = Settings::getCamera_5videoFilePath(); if(!videoFile.isEmpty()) { capture_.open(videoFile.toStdString().c_str()); @@ -77,11 +77,11 @@ bool Camera::start() if(!capture_.isOpened()) { //set camera device - capture_.open(Settings::getCamera_deviceId()); - if(Settings::getCamera_imageWidth() && Settings::getCamera_imageHeight()) + capture_.open(Settings::getCamera_1deviceId()); + if(Settings::getCamera_2imageWidth() && Settings::getCamera_3imageHeight()) { - capture_.set(CV_CAP_PROP_FRAME_WIDTH, double(Settings::getCamera_imageWidth())); - capture_.set(CV_CAP_PROP_FRAME_HEIGHT, double(Settings::getCamera_imageHeight())); + capture_.set(CV_CAP_PROP_FRAME_WIDTH, double(Settings::getCamera_2imageWidth())); + capture_.set(CV_CAP_PROP_FRAME_HEIGHT, double(Settings::getCamera_3imageHeight())); } } } @@ -108,9 +108,9 @@ void Camera::stopTimer() void Camera::updateImageRate() { - if(Settings::getCamera_imageRate()) + if(Settings::getCamera_4imageRate()) { - cameraTimer_.setInterval(1000/Settings::getCamera_imageRate()); + cameraTimer_.setInterval(1000/Settings::getCamera_4imageRate()); } else { diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 0f5075c8..4860cb68 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -12,6 +12,7 @@ #include "Settings.h" #include "ParametersToolBox.h" #include "AboutDialog.h" +#include "rtabmap/PdfPlot.h" #include #include @@ -20,6 +21,7 @@ #include #include +#include #include #include @@ -29,11 +31,13 @@ #include #include #include +#include // Camera ownership transferred MainWindow::MainWindow(Camera * camera, QWidget * parent) : QMainWindow(parent), camera_(camera), + likelihoodCurve_(0), lowestRefreshRate_(99), objectsModified_(false) { @@ -42,6 +46,10 @@ MainWindow::MainWindow(Camera * camera, QWidget * parent) : aboutDialog_ = new AboutDialog(this); this->setStatusBar(new QStatusBar()); + likelihoodCurve_ = new rtabmap::PdfPlotCurve("Likelihood", &imagesMap_); + ui_->likelihoodPlot->addCurve(likelihoodCurve_); // ownership transfered + ui_->likelihoodPlot->setGraphicsView(true); + if(!camera_) { camera_ = new Camera(this); @@ -51,27 +59,37 @@ MainWindow::MainWindow(Camera * camera, QWidget * parent) : camera_->setParent(this); } + ui_->dockWidget_parameters->setVisible(false); + ui_->dockWidget_plot->setVisible(false); + QByteArray geometry; - Settings::loadSettings(Settings::iniDefaultPath(), &geometry); + QByteArray state; + Settings::loadSettings(Settings::iniDefaultPath(), &geometry, &state); this->restoreGeometry(geometry); + this->restoreState(state); ui_->toolBox->setupUi(); - connect((QSpinBox*)ui_->toolBox->getParameterWidget(Settings::kCamera_imageRate()), + connect((QSpinBox*)ui_->toolBox->getParameterWidget(Settings::kCamera_4imageRate()), SIGNAL(editingFinished()), camera_, SLOT(updateImageRate())); - ui_->dockWidget_parameters->hide(); ui_->menuView->addAction(ui_->dockWidget_parameters->toggleViewAction()); ui_->menuView->addAction(ui_->dockWidget_objects->toggleViewAction()); + ui_->menuView->addAction(ui_->dockWidget_plot->toggleViewAction()); connect(ui_->toolBox, SIGNAL(parametersChanged()), this, SLOT(notifyParametersChanged())); ui_->imageView_source->setGraphicsViewMode(false); ui_->imageView_source->setTextLabel(tr("Press \"space\" to start camera...")); + ui_->imageView_source->setMirrorView(Settings::getGeneral_mirrorView()); + connect((QCheckBox*)ui_->toolBox->getParameterWidget(Settings::kGeneral_mirrorView()), + SIGNAL(stateChanged(int)), + this, + SLOT(updateMirrorView())); - //reset button + //buttons connect(ui_->pushButton_restoreDefaults, SIGNAL(clicked()), ui_->toolBox, SLOT(resetCurrentPage())); - // udpate objects button connect(ui_->pushButton_updateObjects, SIGNAL(clicked()), this, SLOT(updateObjects())); + connect(ui_->horizontalSlider_objectsSize, SIGNAL(valueChanged(int)), this, SLOT(updateObjectsSize())); ui_->actionStop_camera->setEnabled(false); ui_->actionPause_camera->setEnabled(false); @@ -100,7 +118,7 @@ MainWindow::MainWindow(Camera * camera, QWidget * parent) : ui_->actionPause_camera->setShortcut(Qt::Key_Space); ui_->actionCamera_from_video_file->setCheckable(true); - ui_->actionCamera_from_video_file->setChecked(!Settings::getCamera_videoFilePath().isEmpty()); + ui_->actionCamera_from_video_file->setChecked(!Settings::getCamera_5videoFilePath().isEmpty()); if(Settings::getGeneral_autoStartCamera()) { @@ -113,7 +131,7 @@ MainWindow::~MainWindow() { disconnect(camera_, SIGNAL(imageReceived(const cv::Mat &)), this, SLOT(update(const cv::Mat &))); camera_->stop(); - dataTree_ = cv::Mat(); + objectsDescriptors_ = cv::Mat(); qDeleteAll(objects_.begin(), objects_.end()); objects_.clear(); delete ui_; @@ -141,7 +159,7 @@ void MainWindow::closeEvent(QCloseEvent * event) } if(quit) { - Settings::saveSettings(Settings::iniDefaultPath(), this->saveGeometry()); + Settings::saveSettings(Settings::iniDefaultPath(), this->saveGeometry(), this->saveState()); event->accept(); } else @@ -240,6 +258,42 @@ void MainWindow::removeAllObjects() } } +void MainWindow::updateObjectsSize() +{ + for(int i=0; ihorizontalSlider_objectsSize->value(); + if((obj->pixmap().width()*value)/100 > 4 && (obj->pixmap().height()*value)/100 > 4) + { + obj->setVisible(true); + obj->setMinimumSize((obj->pixmap().width()*value)/100, (obj->pixmap().height())*value/100); + } + else + { + obj->setVisible(false); + } + obj->setFeaturesShown(value<=50?false:true); + } +} + +void MainWindow::updateMirrorView() +{ + bool mirrorView = Settings::getGeneral_mirrorView(); + ui_->imageView_source->setMirrorView(mirrorView); + for(int i=0; isetMirrorView(mirrorView); + } +} + void MainWindow::addObjectFromScene() { disconnect(camera_, SIGNAL(imageReceived(const cv::Mat &)), this, SLOT(update(const cv::Mat &))); @@ -353,16 +407,16 @@ void MainWindow::setupCameraFromVideoFile() { if(!ui_->actionCamera_from_video_file->isChecked()) { - Settings::setCamera_videoFilePath(""); - ui_->toolBox->updateParameter(Settings::kCamera_videoFilePath()); + Settings::setCamera_5videoFilePath(""); + ui_->toolBox->updateParameter(Settings::kCamera_5videoFilePath()); } else { QString fileName = QFileDialog::getOpenFileName(this, tr("Setup camera from video file..."), Settings::workingDirectory(), tr("Video Files (%1)").arg(Settings::getGeneral_videoFormats())); if(!fileName.isEmpty()) { - Settings::setCamera_videoFilePath(fileName); - ui_->toolBox->updateParameter(Settings::kCamera_videoFilePath()); + Settings::setCamera_5videoFilePath(fileName); + ui_->toolBox->updateParameter(Settings::kCamera_5videoFilePath()); if(camera_->isRunning()) { this->stopProcessing(); @@ -370,7 +424,7 @@ void MainWindow::setupCameraFromVideoFile() } } } - ui_->actionCamera_from_video_file->setChecked(!Settings::getCamera_videoFilePath().isEmpty()); + ui_->actionCamera_from_video_file->setChecked(!Settings::getCamera_5videoFilePath().isEmpty()); } void MainWindow::showObject(ObjWidget * obj) @@ -381,7 +435,6 @@ void MainWindow::showObject(ObjWidget * obj) obj->setMirrorView(ui_->imageView_source->isMirrorView()); QList objs = ui_->objects_area->findChildren(); QVBoxLayout * vLayout = new QVBoxLayout(); - obj->setMinimumSize(obj->pixmap().width(), obj->pixmap().height()); int id = Settings::getGeneral_nextObjID(); if(obj->id() == 0) { @@ -403,9 +456,10 @@ void MainWindow::showObject(ObjWidget * obj) detectedLabel->setObjectName(QString("%1detection").arg(obj->id())); QHBoxLayout * hLayout = new QHBoxLayout(); hLayout->addWidget(title); - hLayout->addWidget(detectorDescriptorType); - hLayout->addWidget(detectedLabel); hLayout->addStretch(1); + hLayout->addWidget(detectorDescriptorType); + hLayout->addStretch(1); + hLayout->addWidget(detectedLabel); vLayout->addLayout(hLayout); vLayout->addWidget(obj); objects_.last()->setDeletable(true); @@ -415,6 +469,14 @@ void MainWindow::showObject(ObjWidget * obj) connect(obj, SIGNAL(destroyed(QObject *)), detectorDescriptorType, SLOT(deleteLater())); connect(obj, SIGNAL(destroyed(QObject *)), vLayout, SLOT(deleteLater())); ui_->verticalLayout_objects->insertLayout(ui_->verticalLayout_objects->count()-1, vLayout); + + QByteArray ba; + QBuffer buffer(&ba); + buffer.open(QIODevice::WriteOnly); + obj->pixmap().scaledToWidth(128).save(&buffer, "JPEG"); // writes image into JPEG format + imagesMap_.insert(obj->id(), ba); + + updateObjectSize(obj); } } @@ -473,7 +535,7 @@ void MainWindow::updateData() ui_->actionSave_objects->setEnabled(false); } - dataTree_ = cv::Mat(); + objectsDescriptors_ = cv::Mat(); dataRange_.clear(); int count = 0; int dim = -1; @@ -489,7 +551,7 @@ void MainWindow::updateData() } else { - printf("ERROR: Descriptors of the objects are not all the same size! Objects opened must have all the same size (and from the same descriptor extractor)."); + printf("ERROR: Descriptors of the objects are not all the same size! Objects opened must have all the same size (and from the same descriptor extractor).\n"); } return; } @@ -502,7 +564,7 @@ void MainWindow::updateData() } else { - printf("ERROR: Descriptors of the objects are not all the same type! Objects opened must have been processed by the same descriptor extractor."); + printf("ERROR: Descriptors of the objects are not all the same type! Objects opened must have been processed by the same descriptor extractor.\n"); } return; } @@ -513,17 +575,32 @@ void MainWindow::updateData() // Copy data if(count) { - cv::Mat data(count, dim, type); + objectsDescriptors_ = cv::Mat(count, dim, type); printf("Total descriptors=%d, dim=%d, type=%d\n",count, dim, type); int row = 0; for(int i=0; idescriptors().rows)); + cv::Mat dest(objectsDescriptors_, cv::Range(row, row+objects_.at(i)->descriptors().rows)); objects_.at(i)->descriptors().copyTo(dest); row += objects_.at(i)->descriptors().rows; - dataRange_.append(row); + // dataRange contains the upper_bound for each + // object (the last descriptors position in the + // global object descriptors matrix) + if(objects_.at(i)->descriptors().rows) + { + dataRange_.insert(row-1, i); + } + } + if(Settings::getGeneral_invertedSearch()) + { + // CREATE INDEX + QTime time; + time.start(); + cv::flann::IndexParams * params = Settings::createFlannIndexParams(); + flannIndex_.build(objectsDescriptors_, *params, Settings::getFlannDistanceType()); + delete params; + ui_->label_timeIndexing->setNum(time.restart()); } - data.convertTo(dataTree_, CV_32F); } } @@ -555,11 +632,11 @@ void MainWindow::startProcessing() } if(this->isVisible()) { - QMessageBox::critical(this, tr("Camera error"), tr("Camera initialization failed! (with device %1)").arg(Settings::getCamera_deviceId())); + QMessageBox::critical(this, tr("Camera error"), tr("Camera initialization failed! (with device %1)").arg(Settings::getCamera_1deviceId())); } else { - printf("ERROR: Camera initialization failed! (with device %d)", Settings::getCamera_deviceId()); + printf("ERROR: Camera initialization failed! (with device %d)", Settings::getCamera_1deviceId()); } } } @@ -594,6 +671,7 @@ void MainWindow::pauseProcessing() void MainWindow::update(const cv::Mat & image) { + updateRate_.start(); // reset objects color for(int i=0; i keypoints; detector->detect(img, keypoints); delete detector; - ui_->label_timeDetection->setText(QString::number(time.restart())); + ui_->label_timeDetection->setNum(time.restart()); cv::Mat descriptors; if(keypoints.size()) @@ -646,62 +724,77 @@ void MainWindow::update(const cv::Mat & image) { cvReleaseImage(&imageGrayScale); } - ui_->label_timeExtraction->setText(QString::number(time.restart())); + ui_->label_timeExtraction->setNum(time.restart()); } else { printf("WARNING: no features detected !?!\n"); - ui_->label_timeExtraction->setText(QString::number(0)); + ui_->label_timeExtraction->setNum(0); } // COMPARE - if(!dataTree_.empty() && keypoints.size() && (Settings::getNearestNeighbor_nndrRatioUsed() || Settings::getNearestNeighbor_minDistanceUsed())) + if(!objectsDescriptors_.empty() && + keypoints.size() && + (Settings::getNearestNeighbor_3nndrRatioUsed() || Settings::getNearestNeighbor_5minDistanceUsed()) && + objectsDescriptors_.type() == descriptors.type()) // binary descriptor issue, if the dataTree is not yet updated with modified settings { - // CREATE INDEX - cv::Mat environment(descriptors.rows, descriptors.cols, CV_32F); - descriptors.convertTo(environment, CV_32F); - cv::flann::Index treeFlannIndex(environment, cv::flann::KDTreeIndexParams()); - ui_->label_timeIndexing->setText(QString::number(time.restart())); - + if(!Settings::getGeneral_invertedSearch()) + { + // CREATE INDEX + cv::flann::IndexParams * params = Settings::createFlannIndexParams(); + flannIndex_.build(descriptors, *params, Settings::getFlannDistanceType()); + delete params; + ui_->label_timeIndexing->setNum(time.restart()); + } + // DO NEAREST NEIGHBOR int k = 1; - if(Settings::getNearestNeighbor_nndrRatioUsed()) + if(Settings::getNearestNeighbor_3nndrRatioUsed()) { k = 2; } - int emax = 64; - cv::Mat results(dataTree_.rows, k, CV_32SC1); // results index - cv::Mat dists(dataTree_.rows, k, CV_32FC1); // Distance results are CV_32FC1 - treeFlannIndex.knnSearch(dataTree_, results, dists, k, cv::flann::SearchParams(emax) ); // maximum number of leafs checked - ui_->label_timeMatching->setText(QString::number(time.restart())); + cv::Mat results; + cv::Mat dists; + if(!Settings::getGeneral_invertedSearch()) + { + results = cv::Mat(objectsDescriptors_.rows, k, CV_32SC1); // results index + dists = cv::Mat(objectsDescriptors_.rows, k, CV_32FC1); // Distance results are CV_32FC1 + flannIndex_.knnSearch(objectsDescriptors_, results, dists, k, Settings::getFlannSearchParams() ); // maximum number of leafs checked + } + else + { + results = cv::Mat(descriptors.rows, k, CV_32SC1); // results index + dists = cv::Mat(descriptors.rows, k, CV_32FC1); // Distance results are CV_32FC1 + flannIndex_.knnSearch(descriptors, results, dists, k, Settings::getFlannSearchParams() ); // maximum number of leafs checked + } + + ui_->label_timeMatching->setNum(time.restart()); // PROCESS RESULTS if(this->isVisible()) { ui_->imageView_source->setData(keypoints, cv::Mat(), &iplImage, Settings::currentDetectorType(), Settings::currentDescriptorType()); } - int j=0; - std::vector mpts_1, mpts_2; - std::vector indexes_1, indexes_2; - std::vector outlier_mask; + + QVector > matches(objects_.size()); // Map< ObjectDescriptorIndex, SceneDescriptorIndex > QMap > objectsDetected; float minMatchedDistance = -1.0f; float maxMatchedDistance = -1.0f; - for(int i=0; i(i,0) <= Settings::getNearestNeighbor_nndrRatio() * dists.at(i,1)) + bool matched = false; + + if(Settings::getNearestNeighbor_3nndrRatioUsed() && + dists.at(i,0) <= Settings::getNearestNeighbor_4nndrRatio() * dists.at(i,1)) { matched = true; } - if((matched || !Settings::getNearestNeighbor_nndrRatioUsed()) && - Settings::getNearestNeighbor_minDistanceUsed()) + if((matched || !Settings::getNearestNeighbor_3nndrRatioUsed()) && + Settings::getNearestNeighbor_5minDistanceUsed()) { - if(dists.at(i,0) <= Settings::getNearestNeighbor_minDistance()) + if(dists.at(i,0) <= Settings::getNearestNeighbor_6minDistance()) { matched = true; } @@ -710,128 +803,165 @@ void MainWindow::update(const cv::Mat & image) matched = false; } } - if(minMatchedDistance == -1 || minMatchedDistance>dists.at(i,0)) + if(minMatchedDistance == -1 || minMatchedDistance > dists.at(i,0)) { minMatchedDistance = dists.at(i,0); } - if(maxMatchedDistance == -1 || maxMatchedDistance(i,0)) + if(maxMatchedDistance == -1 || maxMatchedDistance < dists.at(i,0)) { maxMatchedDistance = dists.at(i,0); } if(matched) { - if(j>0) + if(Settings::getGeneral_invertedSearch()) { - mpts_1.push_back(objects_.at(j)->keypoints().at(i-dataRange_.at(j-1)).pt); - indexes_1.push_back(i-dataRange_.at(j-1)); + QMap::iterator iter = dataRange_.lowerBound(results.at(i,0)); + int objectIndex = iter.value(); + int previousDescriptorIndex = (iter == dataRange_.begin())?0:(--iter).key()+1; + int objectDescriptorIndex = results.at(i,0) - previousDescriptorIndex; + matches[objectIndex].insert(objectDescriptorIndex, i); } else { - mpts_1.push_back(objects_.at(j)->keypoints().at(i).pt); - indexes_1.push_back(i); - } - mpts_2.push_back(keypoints.at(results.at(i,0)).pt); - indexes_2.push_back(results.at(i,0)); - - // colorize all matched if homography is not computed - if(!Settings::getHomography_homographyComputed()) - { - objects_.at(j)->setKptColor(indexes_1.back(), color); - ui_->imageView_source->setKptColor(results.at(i,0), color); + QMap::iterator iter = dataRange_.lowerBound(i); + int objectIndex = iter.value(); + int fisrtObjectDescriptorIndex = (iter == dataRange_.begin())?0:(--iter).key()+1; + int objectDescriptorIndex = i - fisrtObjectDescriptorIndex; + matches[objectIndex].insert(objectDescriptorIndex, results.at(i,0)); } } + } - if(i+1 >= dataRange_.at(j)) + QMap scores; + // For each object + for(int i=0; idockWidget_objects->findChild(QString("%1detection").arg(objects_.at(j)->id())); - if(Settings::getHomography_homographyComputed()) + // HOMOGRAPHY + std::vector mpts_1(matches[i].size()), mpts_2(matches[i].size()); + std::vector indexes_1(matches[i].size()), indexes_2(matches[i].size()); + std::vector outlier_mask; + int nColor = i % 11 + 7; + QColor color((Qt::GlobalColor)(nColor==Qt::yellow?Qt::gray:nColor)); + + int j=0; + for(QMultiMap::iterator iter = matches[i].begin(); iter!=matches[i].end(); ++iter) { - if(mpts_1.size() >= Settings::getHomography_minimumInliers()) + mpts_1[j] = objects_.at(i)->keypoints().at(iter.key()).pt; + indexes_1[j] = iter.key(); + mpts_2[j] = keypoints.at(iter.value()).pt; + indexes_2[j] = iter.value(); + ++j; + } + + QLabel * label = ui_->dockWidget_objects->findChild(QString("%1detection").arg(objects_.at(i)->id())); + + if(mpts_1.size() >= Settings::getHomography_minimumInliers()) + { + cv::Mat H = findHomography(mpts_1, + mpts_2, + cv::RANSAC, + Settings::getHomography_ransacReprojThr(), + outlier_mask); + uint inliers=0, outliers=0; + for(unsigned int k=0; k= Settings::getHomography_minimumInliers()) - { - if(this->isVisible()) - { - for(unsigned int k=0; ksetKptColor(indexes_1.at(k), color); - ui_->imageView_source->setKptColor(indexes_2.at(k), color); - } - else - { - objects_.at(j)->setKptColor(indexes_1.at(k), QColor(0,0,0)); - } - } - } - - QTransform hTransform( - H.at(0,0), H.at(1,0), H.at(2,0), - H.at(0,1), H.at(1,1), H.at(2,1), - H.at(0,2), H.at(1,2), H.at(2,2)); - - // find center of object - QRect rect = objects_.at(j)->pixmap().rect(); - objectsDetected.insert(objects_.at(j)->id(), QPair(rect, hTransform)); - // Example getting the center of the object in the scene using the homography - //QPoint pos(rect.width()/2, rect.height()/2); - //hTransform.map(pos) - - // add rectangle - if(this->isVisible()) - { - label->setText(QString("%1 in %2 out").arg(inliers).arg(outliers)); - QPen rectPen(color); - rectPen.setWidth(4); - QGraphicsRectItem * rectItem = new QGraphicsRectItem(rect); - rectItem->setPen(rectPen); - rectItem->setTransform(hTransform); - ui_->imageView_source->addRect(rectItem); - } + ++inliers; } else { - label->setText(QString("Too low inliers (%1)").arg(inliers)); + ++outliers; + } + } + + // COLORIZE + if(inliers >= Settings::getHomography_minimumInliers()) + { + if(this->isVisible()) + { + for(unsigned int k=0; ksetKptColor(indexes_1.at(k), color); + ui_->imageView_source->setKptColor(indexes_2.at(k), color); + } + else + { + objects_.at(i)->setKptColor(indexes_1.at(k), QColor(0,0,0)); + } + } + } + + QTransform hTransform( + H.at(0,0), H.at(1,0), H.at(2,0), + H.at(0,1), H.at(1,1), H.at(2,1), + H.at(0,2), H.at(1,2), H.at(2,2)); + + // find center of object + QRect rect = objects_.at(i)->pixmap().rect(); + objectsDetected.insert(objects_.at(i)->id(), QPair(rect, hTransform)); + // Example getting the center of the object in the scene using the homography + //QPoint pos(rect.width()/2, rect.height()/2); + //hTransform.map(pos) + + // add rectangle + if(this->isVisible()) + { + label->setText(QString("%1 in %2 out").arg(inliers).arg(outliers)); + QPen rectPen(color); + rectPen.setWidth(4); + QGraphicsRectItem * rectItem = new QGraphicsRectItem(rect); + rectItem->setPen(rectPen); + rectItem->setTransform(hTransform); + ui_->imageView_source->addRect(rectItem); } } else { - label->setText(QString("Too low matches (%1)").arg(mpts_1.size())); + label->setText(QString("Too low inliers (%1 in %2 out)").arg(inliers).arg(outliers)); } } else { - label->setText(QString("%1 matches").arg(mpts_1.size())); + label->setText(QString("Too low matches (%1)").arg(mpts_1.size())); } mpts_1.clear(); mpts_2.clear(); indexes_1.clear(); indexes_2.clear(); outlier_mask.clear(); - ++j; } + else + { + // colorize all matches if homography is not computed + int nColor = i % 11 + 7; + QColor color((Qt::GlobalColor)(nColor==Qt::yellow?Qt::gray:nColor)); + for(QMultiMap::iterator iter = matches[i].begin(); iter!=matches[i].end(); ++iter) + { + printf("iter.key()=%d, iter.value()=%d\n", iter.key(), iter.value()); + printf("objects_[%d].keypoints=%d\n", i, (int)objects_[i]->keypoints().size()); + objects_[i]->setKptColor(iter.key(), color); + ui_->imageView_source->setKptColor(iter.value(), color); + } + QLabel * label = ui_->dockWidget_objects->findChild(QString("%1detection").arg(objects_.at(i)->id())); + label->setText(QString("%1 matches").arg(matches[i].size())); + } + + scores.insert(objects_.at(i)->id(), matches[i].size()); } + + //update likelihood plot + likelihoodCurve_->setData(scores, QMap()); + if(ui_->likelihoodPlot->isVisible()) + { + ui_->likelihoodPlot->update(); + } + ui_->label_minMatchedDistance->setNum(minMatchedDistance); ui_->label_maxMatchedDistance->setNum(maxMatchedDistance); @@ -854,13 +984,15 @@ void MainWindow::update(const cv::Mat & image) } } - ui_->label_nfeatures->setText(QString::number(keypoints.size())); + ui_->label_nfeatures->setNum((int)keypoints.size()); ui_->imageView_source->update(); - ui_->label_timeGui->setText(QString::number(time.restart())); + ui_->label_timeGui->setNum(time.restart()); } ui_->label_detectorDescriptorType->setText(QString("%1/%2").arg(Settings::currentDetectorType()).arg(Settings::currentDescriptorType())); - int refreshRate = qRound(1000.0f/float(updateRate_.restart())); + int ms = updateRate_.restart(); + ui_->label_timeTotal->setNum(ms); + int refreshRate = qRound(1000.0f/float(ms)); if(refreshRate > 0 && refreshRate < lowestRefreshRate_) { lowestRefreshRate_ = refreshRate; @@ -868,9 +1000,9 @@ void MainWindow::update(const cv::Mat & image) // Refresh the label only after each 1000 ms if(refreshStartTime_.elapsed() > 1000) { - if(Settings::getCamera_imageRate()>0) + if(Settings::getCamera_4imageRate()>0) { - ui_->label_timeRefreshRate->setText(QString("(%1 Hz - %2 Hz)").arg(QString::number(Settings::getCamera_imageRate())).arg(QString::number(lowestRefreshRate_))); + ui_->label_timeRefreshRate->setText(QString("(%1 Hz - %2 Hz)").arg(QString::number(Settings::getCamera_4imageRate())).arg(QString::number(lowestRefreshRate_))); } else { @@ -898,5 +1030,5 @@ void MainWindow::notifyParametersChanged() ui_->label_timeRefreshRate->setVisible(false); } - ui_->actionCamera_from_video_file->setChecked(!Settings::getCamera_videoFilePath().isEmpty()); + ui_->actionCamera_from_video_file->setChecked(!Settings::getCamera_5videoFilePath().isEmpty()); } diff --git a/src/MainWindow.h b/src/MainWindow.h index 60d80a30..048dea48 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -9,6 +9,8 @@ #include #include #include +#include +#include #include #include @@ -21,6 +23,11 @@ class ParametersToolBox; class QLabel; class AboutDialog; +namespace rtabmap +{ +class PdfPlotCurve; +} + class MainWindow : public QMainWindow { Q_OBJECT @@ -52,6 +59,8 @@ private slots: void setupCameraFromVideoFile(); void removeObject(ObjWidget * object); void removeAllObjects(); + void updateObjectsSize(); + void updateMirrorView(); void update(const cv::Mat & image); void updateObjects(); void notifyParametersChanged(); @@ -63,18 +72,22 @@ private: void addObjectFromFile(const QString & filePath); void showObject(ObjWidget * obj); void updateData(); + void updateObjectSize(ObjWidget * obj); private: Ui_mainWindow * ui_; Camera * camera_; + rtabmap::PdfPlotCurve * likelihoodCurve_; AboutDialog * aboutDialog_; QList objects_; - cv::Mat dataTree_; - QList dataRange_; + cv::Mat objectsDescriptors_; + cv::flann::Index flannIndex_; + QMap dataRange_; // QTime updateRate_; QTime refreshStartTime_; int lowestRefreshRate_; bool objectsModified_; + QMap imagesMap_; }; #endif /* MainWindow_H_ */ diff --git a/src/ObjWidget.cpp b/src/ObjWidget.cpp index 759b4245..7d02baa5 100644 --- a/src/ObjWidget.cpp +++ b/src/ObjWidget.cpp @@ -287,6 +287,10 @@ void ObjWidget::setKptColor(int index, const QColor & color) { kptColors_[index] = color; } + else + { + printf("PROBLEM index =%d > size=%d\n", index, kptColors_.size()); + } if(graphicsViewMode_->isChecked()) { @@ -344,6 +348,32 @@ void ObjWidget::setDeletable(bool deletable) delete_->setEnabled(deletable); } +void ObjWidget::setImageShown(bool shown) +{ + showImage_->setChecked(shown); + if(graphicsViewMode_->isChecked()) + { + this->updateItemsShown(); + } + else + { + this->update(); + } +} + +void ObjWidget::setFeaturesShown(bool shown) +{ + showFeatures_->setChecked(shown); + if(graphicsViewMode_->isChecked()) + { + this->updateItemsShown(); + } + else + { + this->update(); + } +} + void ObjWidget::save(QDataStream & streamPtr) const { streamPtr << id_ << detectorType_ << descriptorType_; diff --git a/src/ObjWidget.h b/src/ObjWidget.h index 712c035f..8535cb12 100644 --- a/src/ObjWidget.h +++ b/src/ObjWidget.h @@ -48,6 +48,8 @@ public: void setMirrorView(bool on); void setAlpha(int alpha); void setDeletable(bool deletable); + void setImageShown(bool shown); + void setFeaturesShown(bool shown); void addRect(QGraphicsRectItem * rect); void clearRoiSelection() {mousePressedPos_ = mouseCurrentPos_ = QPoint();update();} diff --git a/src/ParametersToolBox.cpp b/src/ParametersToolBox.cpp index 53454a3b..eb407229 100644 --- a/src/ParametersToolBox.cpp +++ b/src/ParametersToolBox.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include ParametersToolBox::ParametersToolBox(QWidget *parent) : @@ -86,6 +87,7 @@ void ParametersToolBox::resetAllPages() void ParametersToolBox::setupUi() { + this->removeItem(0); // remove dummy page used in .ui QWidget * currentItem = 0; const ParametersMap & parameters = Settings::getParameters(); for(ParametersMap::const_iterator iter=parameters.constBegin(); @@ -119,7 +121,17 @@ void ParametersToolBox::updateParameter(const QString & key) QString type = Settings::getParametersType().value(key); if(type.compare("QString") == 0) { - ((QLineEdit*)widget)->setText(Settings::getParameter(key).toString()); + QString value = Settings::getParameter(key).toString(); + if(value.contains(';')) + { + // It's a list, just change the index + QStringList splitted = value.split(':'); + ((QComboBox*)widget)->setCurrentIndex(splitted.first().toInt()); + } + else + { + ((QLineEdit*)widget)->setText(value); + } } else if(type.compare("int") == 0) { @@ -272,7 +284,12 @@ void ParametersToolBox::addParameter(QVBoxLayout * layout, const QString & name, { QHBoxLayout * hLayout = new QHBoxLayout(); layout->insertLayout(layout->count()-1, hLayout); - hLayout->addWidget(new QLabel(name, this)); + QString tmp = name; + if(tmp.at(0).isDigit()) + { + tmp.remove(0,1); + } + hLayout->addWidget(new QLabel(tmp, this)); hLayout->addWidget(widget); } @@ -314,6 +331,50 @@ void ParametersToolBox::changeParameter(const int & value) QCheckBox * checkBox = qobject_cast(sender()); if(comboBox) { + if(comboBox->objectName().compare(Settings::kDetector_Descriptor_2Descriptor()) == 0 || + comboBox->objectName().compare(Settings::kNearestNeighbor_1Strategy()) == 0) + { + //verify binary issue + QComboBox * descriptorBox = (QComboBox*)this->getParameterWidget(Settings::kDetector_Descriptor_2Descriptor()); + QComboBox * nnBox = (QComboBox*)this->getParameterWidget(Settings::kNearestNeighbor_1Strategy()); + bool isBinaryDescriptor = descriptorBox->currentText().compare("ORB") == 0 || descriptorBox->currentText().compare("Brief") == 0; + if(isBinaryDescriptor && nnBox->currentText().compare("Lsh") != 0) + { + QMessageBox::warning(this, + tr("Error"), + tr("Current selected descriptor type (\"%1\") is binary while nearest neighbor strategy is not (\"%2\").\n" + "Falling back to \"Lsh\" nearest neighbor strategy (by default).") + .arg(descriptorBox->currentText()) + .arg(nnBox->currentText())); + QString tmp = Settings::getNearestNeighbor_1Strategy(); + *tmp.begin() = '5'; // set index + Settings::setNearestNeighbor_1Strategy(tmp); + this->updateParameter(Settings::kNearestNeighbor_1Strategy()); + if(sender() == nnBox) + { + return; + } + + } + else if(!isBinaryDescriptor && nnBox->currentText().compare("Lsh") == 0) + { + QMessageBox::warning(this, + tr("Error"), + tr("Current selected descriptor type (\"%1\") is not binary while nearest neighbor strategy is (\"%2\").\n" + "Falling back to \"KDTree\" nearest neighbor strategy (by default).") + .arg(descriptorBox->currentText()) + .arg(nnBox->currentText())); + QString tmp = Settings::getNearestNeighbor_1Strategy(); + *tmp.begin() = '1'; // set index + Settings::setNearestNeighbor_1Strategy(tmp); + this->updateParameter(Settings::kNearestNeighbor_1Strategy()); + if(sender() == nnBox) + { + return; + } + } + } + QStringList items; for(int i=0; icount(); ++i) { diff --git a/src/Settings.cpp b/src/Settings.cpp index 7aaca267..c41cb94c 100644 --- a/src/Settings.cpp +++ b/src/Settings.cpp @@ -10,6 +10,8 @@ #include #include +#define VERBOSE 0 + ParametersMap Settings::defaultParameters_; ParametersMap Settings::parameters_; ParametersType Settings::parametersType_; @@ -33,7 +35,7 @@ QString Settings::iniDefaultPath() #endif } -void Settings::loadSettings(const QString & fileName, QByteArray * windowGeometry) +void Settings::loadSettings(const QString & fileName, QByteArray * windowGeometry, QByteArray * windowState) { QString path = fileName; if(fileName.isEmpty()) @@ -59,11 +61,19 @@ void Settings::loadSettings(const QString & fileName, QByteArray * windowGeometr *windowGeometry = value.toByteArray(); } } + if(windowState) + { + QVariant value = ini.value("windowState", QVariant()); + if(value.isValid()) + { + *windowState = value.toByteArray(); + } + } printf("Settings loaded from %s\n", path.toStdString().c_str()); } -void Settings::saveSettings(const QString & fileName, const QByteArray & windowGeometry) +void Settings::saveSettings(const QString & fileName, const QByteArray & windowGeometry, const QByteArray & windowState) { QString path = fileName; if(fileName.isEmpty()) @@ -87,13 +97,17 @@ void Settings::saveSettings(const QString & fileName, const QByteArray & windowG { ini.setValue("windowGeometry", windowGeometry); } + if(!windowState.isEmpty()) + { + ini.setValue("windowState", windowState); + } printf("Settings saved to %s\n", path.toStdString().c_str()); } cv::FeatureDetector * Settings::createFeaturesDetector() { cv::FeatureDetector * detector = 0; - QString str = getDetector_Type(); + QString str = getDetector_Descriptor_1Detector(); QStringList split = str.split(':'); if(split.size()==2) { @@ -110,95 +124,103 @@ cv::FeatureDetector * Settings::createFeaturesDetector() if(strategies.at(index).compare("Dense") == 0) { detector = new cv::DenseFeatureDetector( - getDense_initFeatureScale(), - getDense_featureScaleLevels(), - getDense_featureScaleMul(), - getDense_initXyStep(), - getDense_initImgBound(), - getDense_varyXyStepWithScale(), - getDense_varyImgBoundWithScale()); + getDetector_Descriptor_Dense_initFeatureScale(), + getDetector_Descriptor_Dense_featureScaleLevels(), + getDetector_Descriptor_Dense_featureScaleMul(), + getDetector_Descriptor_Dense_initXyStep(), + getDetector_Descriptor_Dense_initImgBound(), + getDetector_Descriptor_Dense_varyXyStepWithScale(), + getDetector_Descriptor_Dense_varyImgBoundWithScale()); + if(VERBOSE)printf("Settings::createFeaturesDetector() type=%s\n", "Dense"); } break; case 1: if(strategies.at(index).compare("Fast") == 0) { detector = new cv::FastFeatureDetector( - getFast_threshold(), - getFast_nonmaxSuppression()); + getDetector_Descriptor_Fast_threshold(), + getDetector_Descriptor_Fast_nonmaxSuppression()); + if(VERBOSE)printf("Settings::createFeaturesDetector() type=%s\n", "Fast"); } break; case 2: if(strategies.at(index).compare("GFTT") == 0) { detector = new cv::GFTTDetector( - getGFTT_maxCorners(), - getGFTT_qualityLevel(), - getGFTT_minDistance(), - getGFTT_blockSize(), - getGFTT_useHarrisDetector(), - getGFTT_k()); + getDetector_Descriptor_GFTT_maxCorners(), + getDetector_Descriptor_GFTT_qualityLevel(), + getDetector_Descriptor_GFTT_minDistance(), + getDetector_Descriptor_GFTT_blockSize(), + getDetector_Descriptor_GFTT_useHarrisDetector(), + getDetector_Descriptor_GFTT_k()); + if(VERBOSE)printf("Settings::createFeaturesDetector() type=%s\n", "GFTT"); } break; case 3: if(strategies.at(index).compare("MSER") == 0) { detector = new cv::MSER( - getMSER_delta(), - getMSER_minArea(), - getMSER_maxArea(), - getMSER_maxVariation(), - getMSER_minDiversity(), - getMSER_maxEvolution(), - getMSER_areaThreshold(), - getMSER_minMargin(), - getMSER_edgeBlurSize()); + getDetector_Descriptor_MSER_delta(), + getDetector_Descriptor_MSER_minArea(), + getDetector_Descriptor_MSER_maxArea(), + getDetector_Descriptor_MSER_maxVariation(), + getDetector_Descriptor_MSER_minDiversity(), + getDetector_Descriptor_MSER_maxEvolution(), + getDetector_Descriptor_MSER_areaThreshold(), + getDetector_Descriptor_MSER_minMargin(), + getDetector_Descriptor_MSER_edgeBlurSize()); + if(VERBOSE)printf("Settings::createFeaturesDetector() type=%s\n", "MSER"); } break; case 4: if(strategies.at(index).compare("ORB") == 0) { detector = new cv::ORB( - getORB_nFeatures(), - getORB_scaleFactor(), - getORB_nLevels(), - getORB_edgeThreshold(), - getORB_firstLevel(), - getORB_WTA_K(), - getORB_scoreType(), - getORB_patchSize()); + getDetector_Descriptor_ORB_nFeatures(), + getDetector_Descriptor_ORB_scaleFactor(), + getDetector_Descriptor_ORB_nLevels(), + getDetector_Descriptor_ORB_edgeThreshold(), + getDetector_Descriptor_ORB_firstLevel(), + getDetector_Descriptor_ORB_WTA_K(), + getDetector_Descriptor_ORB_scoreType(), + getDetector_Descriptor_ORB_patchSize()); + if(VERBOSE)printf("Settings::createFeaturesDetector() type=%s\n", "ORB"); } break; case 5: if(strategies.at(index).compare("SIFT") == 0) { detector = new cv::SIFT( - getSIFT_nfeatures(), - getSIFT_nOctaveLayers(), - getSIFT_contrastThreshold(), - getSIFT_edgeThreshold(), - getSIFT_sigma()); + getDetector_Descriptor_SIFT_nfeatures(), + getDetector_Descriptor_SIFT_nOctaveLayers(), + getDetector_Descriptor_SIFT_contrastThreshold(), + getDetector_Descriptor_SIFT_edgeThreshold(), + getDetector_Descriptor_SIFT_sigma()); + if(VERBOSE)printf("Settings::createFeaturesDetector() type=%s\n", "SIFT"); } break; case 6: if(strategies.at(index).compare("Star") == 0) { detector = new cv::StarFeatureDetector( - getStar_maxSize(), - getStar_responseThreshold(), - getStar_lineThresholdProjected(), - getStar_lineThresholdBinarized(), - getStar_suppressNonmaxSize()); + getDetector_Descriptor_Star_maxSize(), + getDetector_Descriptor_Star_responseThreshold(), + getDetector_Descriptor_Star_lineThresholdProjected(), + getDetector_Descriptor_Star_lineThresholdBinarized(), + getDetector_Descriptor_Star_suppressNonmaxSize()); + if(VERBOSE)printf("Settings::createFeaturesDetector() type=%s\n", "Star"); } break; case 7: if(strategies.at(index).compare("SURF") == 0) { detector = new cv::SURF( - getSURF_hessianThreshold(), - getSURF_nOctaves(), - getSURF_nOctaveLayers(), - getSURF_extended(), - getSURF_upright()); + getDetector_Descriptor_SURF_hessianThreshold(), + getDetector_Descriptor_SURF_nOctaves(), + getDetector_Descriptor_SURF_nOctaveLayers(), + getDetector_Descriptor_SURF_extended(), + getDetector_Descriptor_SURF_upright()); + if(VERBOSE)printf("Settings::createFeaturesDetector() type=%s\n", "SURF"); } break; default: @@ -207,13 +229,18 @@ cv::FeatureDetector * Settings::createFeaturesDetector() } } } + if(!detector) + { + printf("ERROR: detector strategy not found !? Using default SURF...\n"); + detector = new cv::SURF(); + } return detector; } cv::DescriptorExtractor * Settings::createDescriptorsExtractor() { cv::DescriptorExtractor * extractor = 0; - QString str = getDescriptor_Type(); + QString str = getDetector_Descriptor_2Descriptor(); QStringList split = str.split(':'); if(split.size()==2) { @@ -230,43 +257,47 @@ cv::DescriptorExtractor * Settings::createDescriptorsExtractor() if(strategies.at(index).compare("Brief") == 0) { extractor = new cv::BriefDescriptorExtractor( - getBrief_bytes()); + getDetector_Descriptor_Brief_bytes()); + if(VERBOSE)printf("Settings::createDescriptorsExtractor() type=%s\n", "Brief"); } break; case 1: if(strategies.at(index).compare("ORB") == 0) { extractor = new cv::ORB( - getORB_nFeatures(), - getORB_scaleFactor(), - getORB_nLevels(), - getORB_edgeThreshold(), - getORB_firstLevel(), - getORB_WTA_K(), - getORB_scoreType(), - getORB_patchSize()); + getDetector_Descriptor_ORB_nFeatures(), + getDetector_Descriptor_ORB_scaleFactor(), + getDetector_Descriptor_ORB_nLevels(), + getDetector_Descriptor_ORB_edgeThreshold(), + getDetector_Descriptor_ORB_firstLevel(), + getDetector_Descriptor_ORB_WTA_K(), + getDetector_Descriptor_ORB_scoreType(), + getDetector_Descriptor_ORB_patchSize()); + if(VERBOSE)printf("Settings::createDescriptorsExtractor() type=%s\n", "ORB"); } break; case 2: if(strategies.at(index).compare("SIFT") == 0) { extractor = new cv::SIFT( - getSIFT_nfeatures(), - getSIFT_nOctaveLayers(), - getSIFT_contrastThreshold(), - getSIFT_edgeThreshold(), - getSIFT_sigma()); + getDetector_Descriptor_SIFT_nfeatures(), + getDetector_Descriptor_SIFT_nOctaveLayers(), + getDetector_Descriptor_SIFT_contrastThreshold(), + getDetector_Descriptor_SIFT_edgeThreshold(), + getDetector_Descriptor_SIFT_sigma()); + if(VERBOSE)printf("Settings::createDescriptorsExtractor() type=%s\n", "SIFT"); } break; case 3: if(strategies.at(index).compare("SURF") == 0) { extractor = new cv::SURF( - getSURF_hessianThreshold(), - getSURF_nOctaves(), - getSURF_nOctaveLayers(), - getSURF_extended(), - getSURF_upright()); + getDetector_Descriptor_SURF_hessianThreshold(), + getDetector_Descriptor_SURF_nOctaves(), + getDetector_Descriptor_SURF_nOctaveLayers(), + getDetector_Descriptor_SURF_extended(), + getDetector_Descriptor_SURF_upright()); + if(VERBOSE)printf("Settings::createDescriptorsExtractor() type=%s\n", "SURF"); } break; default: @@ -275,19 +306,170 @@ cv::DescriptorExtractor * Settings::createDescriptorsExtractor() } } } - + if(!extractor) + { + printf("ERROR: descriptor strategy not found !? Using default SURF...\n"); + extractor = new cv::SURF(); + } return extractor; } QString Settings::currentDetectorType() { - int index = Settings::getDetector_Type().split(':').first().toInt(); - return getDetector_Type().split(':').last().split(';').at(index); + int index = getDetector_Descriptor_1Detector().split(':').first().toInt(); + return getDetector_Descriptor_1Detector().split(':').last().split(';').at(index); } QString Settings::currentDescriptorType() { - int index = Settings::getDescriptor_Type().split(':').first().toInt(); - return getDescriptor_Type().split(':').last().split(';').at(index); + int index = getDetector_Descriptor_2Descriptor().split(':').first().toInt(); + return getDetector_Descriptor_2Descriptor().split(':').last().split(';').at(index); +} + +QString Settings::currentNearestNeighborType() +{ + int index = getNearestNeighbor_1Strategy().split(':').first().toInt(); + return getNearestNeighbor_1Strategy().split(':').last().split(';').at(index); +} + +cv::flann::IndexParams * Settings::createFlannIndexParams() +{ + cv::flann::IndexParams * params = 0; + QString str = getNearestNeighbor_1Strategy(); + QStringList split = str.split(':'); + if(split.size()==2) + { + bool ok = false; + int index = split.first().toInt(&ok); + if(ok) + { + QStringList strategies = split.last().split(';'); + if(strategies.size() == 6 && index>=0 && index<6) + { + switch(index) + { + case 0: + if(strategies.at(index).compare("Linear") == 0) + { + if(VERBOSE)printf("Settings::getFlannIndexParams() type=%s\n", "Linear"); + params = new cv::flann::LinearIndexParams(); + } + break; + case 1: + if(strategies.at(index).compare("KDTree") == 0) + { + if(VERBOSE)printf("Settings::getFlannIndexParams() type=%s\n", "KDTree"); + params = new cv::flann::KDTreeIndexParams( + getNearestNeighbor_KDTree_trees()); + } + break; + case 2: + if(strategies.at(index).compare("KMeans") == 0) + { + cvflann::flann_centers_init_t centers_init = cvflann::FLANN_CENTERS_RANDOM; + QString str = getNearestNeighbor_KMeans_centers_init(); + QStringList split = str.split(':'); + if(split.size()==2) + { + bool ok = false; + int index = split.first().toInt(&ok); + if(ok) + { + centers_init = (cvflann::flann_centers_init_t)index; + } + } + if(VERBOSE)printf("Settings::getFlannIndexParams() type=%s\n", "KMeans"); + params = new cv::flann::KMeansIndexParams( + getNearestNeighbor_KMeans_branching(), + getNearestNeighbor_KMeans_iterations(), + centers_init, + getNearestNeighbor_KMeans_cb_index()); + } + break; + case 3: + if(strategies.at(index).compare("Composite") == 0) + { + cvflann::flann_centers_init_t centers_init = cvflann::FLANN_CENTERS_RANDOM; + QString str = getNearestNeighbor_Composite_centers_init(); + QStringList split = str.split(':'); + if(split.size()==2) + { + bool ok = false; + int index = split.first().toInt(&ok); + if(ok) + { + centers_init = (cvflann::flann_centers_init_t)index; + } + } + if(VERBOSE)printf("Settings::getFlannIndexParams() type=%s\n", "Composite"); + params = new cv::flann::CompositeIndexParams( + getNearestNeighbor_Composite_trees(), + getNearestNeighbor_Composite_branching(), + getNearestNeighbor_Composite_iterations(), + centers_init, + getNearestNeighbor_Composite_cb_index()); + } + break; + case 4: + if(strategies.at(index).compare("Autotuned") == 0) + { + if(VERBOSE)printf("Settings::getFlannIndexParams() type=%s\n", "Autotuned"); + params = new cv::flann::AutotunedIndexParams( + getNearestNeighbor_Autotuned_target_precision(), + getNearestNeighbor_Autotuned_build_weight(), + getNearestNeighbor_Autotuned_memory_weight(), + getNearestNeighbor_Autotuned_sample_fraction()); + } + break; + case 5: + if(strategies.at(index).compare("Lsh") == 0) + { + if(VERBOSE)printf("Settings::getFlannIndexParams() type=%s\n", "Lsh"); + params = new cv::flann::LshIndexParams( + getNearestNeighbor_Lsh_table_number(), + getNearestNeighbor_Lsh_key_size(), + getNearestNeighbor_Lsh_multi_probe_level()); + + } + break; + default: + break; + } + } + } + } + if(!params) + { + printf("ERROR: NN strategy not found !? Using default KDTRee...\n"); + params = new cv::flann::KDTreeIndexParams(); + } + return params ; +} + +cvflann::flann_distance_t Settings::getFlannDistanceType() +{ + cvflann::flann_distance_t distance = cvflann::FLANN_DIST_L2; + QString str = getNearestNeighbor_2Distance_type(); + QStringList split = str.split(':'); + if(split.size()==2) + { + bool ok = false; + int index = split.first().toInt(&ok); + if(ok) + { + QStringList strategies = split.last().split(';'); + if(strategies.size() == 8 && index>=0 && index<8) + { + distance = (cvflann::flann_distance_t)(index+1); + } + } + } + if(VERBOSE)printf("Settings::getFlannDistanceType() distance=%d\n", distance); + return distance; +} + +cv::flann::SearchParams Settings::getFlannSearchParams() +{ + return cv::flann::SearchParams(); } diff --git a/src/Settings.h b/src/Settings.h index 93e2186c..a1b1bda8 100644 --- a/src/Settings.h +++ b/src/Settings.h @@ -52,83 +52,109 @@ typedef unsigned int uint; class Settings { - PARAMETER(Camera, deviceId, int, 0); - PARAMETER(Camera, imageWidth, int, 640); - PARAMETER(Camera, imageHeight, int, 480); - PARAMETER(Camera, imageRate, int, 2); // Hz - PARAMETER(Camera, videoFilePath, QString, ""); + PARAMETER(Camera, 1deviceId, int, 0); + PARAMETER(Camera, 2imageWidth, int, 640); + PARAMETER(Camera, 3imageHeight, int, 480); + PARAMETER(Camera, 4imageRate, int, 2); // Hz + PARAMETER(Camera, 5videoFilePath, QString, ""); //List format : [Index:item0;item1;item3;...] - PARAMETER(Detector, Type, QString, "7:Dense;Fast;GFTT;MSER;ORB;SIFT;Star;SURF"); - PARAMETER(Descriptor, Type, QString, "3:Brief;ORB;SIFT;SURF"); + PARAMETER(Detector_Descriptor, 1Detector, QString, "7:Dense;Fast;GFTT;MSER;ORB;SIFT;Star;SURF"); + PARAMETER(Detector_Descriptor, 2Descriptor, QString, "3:Brief;ORB;SIFT;SURF"); - PARAMETER(Brief, bytes, int, 32); + PARAMETER(Detector_Descriptor, Brief_bytes, int, 32); - PARAMETER(Dense, initFeatureScale, float, 1.f); - PARAMETER(Dense, featureScaleLevels, int, 1); - PARAMETER(Dense, featureScaleMul, float, 0.1f); - PARAMETER(Dense, initXyStep, int, 6); - PARAMETER(Dense, initImgBound, int, 0); - PARAMETER(Dense, varyXyStepWithScale, bool, true); - PARAMETER(Dense, varyImgBoundWithScale, bool, false); + PARAMETER(Detector_Descriptor, Dense_initFeatureScale, float, 1.f); + PARAMETER(Detector_Descriptor, Dense_featureScaleLevels, int, 1); + PARAMETER(Detector_Descriptor, Dense_featureScaleMul, float, 0.1f); + PARAMETER(Detector_Descriptor, Dense_initXyStep, int, 6); + PARAMETER(Detector_Descriptor, Dense_initImgBound, int, 0); + PARAMETER(Detector_Descriptor, Dense_varyXyStepWithScale, bool, true); + PARAMETER(Detector_Descriptor, Dense_varyImgBoundWithScale, bool, false); - PARAMETER(Fast, threshold, int, 10); - PARAMETER(Fast, nonmaxSuppression, bool, true); + PARAMETER(Detector_Descriptor, Fast_threshold, int, 10); + PARAMETER(Detector_Descriptor, Fast_nonmaxSuppression, bool, true); - PARAMETER(GFTT, maxCorners, int, 1000); - PARAMETER(GFTT, qualityLevel, double, 0.01); - PARAMETER(GFTT, minDistance, double, 1); - PARAMETER(GFTT, blockSize, int, 3); - PARAMETER(GFTT, useHarrisDetector, bool, false); - PARAMETER(GFTT, k, double, 0.04); + PARAMETER(Detector_Descriptor, GFTT_maxCorners, int, 1000); + PARAMETER(Detector_Descriptor, GFTT_qualityLevel, double, 0.01); + PARAMETER(Detector_Descriptor, GFTT_minDistance, double, 1); + PARAMETER(Detector_Descriptor, GFTT_blockSize, int, 3); + PARAMETER(Detector_Descriptor, GFTT_useHarrisDetector, bool, false); + PARAMETER(Detector_Descriptor, GFTT_k, double, 0.04); - PARAMETER(ORB, nFeatures, int, 500); - PARAMETER(ORB, scaleFactor, float, 1.2f); - PARAMETER(ORB, nLevels, int, 8); - PARAMETER(ORB, edgeThreshold, int, 31); - PARAMETER(ORB, firstLevel, int, 0); - PARAMETER(ORB, WTA_K, int, 2); - PARAMETER(ORB, scoreType, int, 0); - PARAMETER(ORB, patchSize, int, 31); + PARAMETER(Detector_Descriptor, ORB_nFeatures, int, 500); + PARAMETER(Detector_Descriptor, ORB_scaleFactor, float, 1.2f); + PARAMETER(Detector_Descriptor, ORB_nLevels, int, 8); + PARAMETER(Detector_Descriptor, ORB_edgeThreshold, int, 31); + PARAMETER(Detector_Descriptor, ORB_firstLevel, int, 0); + PARAMETER(Detector_Descriptor, ORB_WTA_K, int, 2); + PARAMETER(Detector_Descriptor, ORB_scoreType, int, 0); + PARAMETER(Detector_Descriptor, ORB_patchSize, int, 31); - PARAMETER(MSER, delta, int, 5); - PARAMETER(MSER, minArea, int, 60); - PARAMETER(MSER, maxArea, int, 14400); - PARAMETER(MSER, maxVariation, double, 0.25); - PARAMETER(MSER, minDiversity, double, 0.2); - PARAMETER(MSER, maxEvolution, int, 200); - PARAMETER(MSER, areaThreshold, double, 1.01); - PARAMETER(MSER, minMargin, double, 0.003); - PARAMETER(MSER, edgeBlurSize, int, 5); + PARAMETER(Detector_Descriptor, MSER_delta, int, 5); + PARAMETER(Detector_Descriptor, MSER_minArea, int, 60); + PARAMETER(Detector_Descriptor, MSER_maxArea, int, 14400); + PARAMETER(Detector_Descriptor, MSER_maxVariation, double, 0.25); + PARAMETER(Detector_Descriptor, MSER_minDiversity, double, 0.2); + PARAMETER(Detector_Descriptor, MSER_maxEvolution, int, 200); + PARAMETER(Detector_Descriptor, MSER_areaThreshold, double, 1.01); + PARAMETER(Detector_Descriptor, MSER_minMargin, double, 0.003); + PARAMETER(Detector_Descriptor, MSER_edgeBlurSize, int, 5); - PARAMETER(SIFT, nfeatures, int, 0); - PARAMETER(SIFT, nOctaveLayers, int, 3); - PARAMETER(SIFT, contrastThreshold, double, 0.04); - PARAMETER(SIFT, edgeThreshold, double, 10); - PARAMETER(SIFT, sigma, double, 1.6); + PARAMETER(Detector_Descriptor, SIFT_nfeatures, int, 0); + PARAMETER(Detector_Descriptor, SIFT_nOctaveLayers, int, 3); + PARAMETER(Detector_Descriptor, SIFT_contrastThreshold, double, 0.04); + PARAMETER(Detector_Descriptor, SIFT_edgeThreshold, double, 10); + PARAMETER(Detector_Descriptor, SIFT_sigma, double, 1.6); - PARAMETER(Star, maxSize, int, 45); - PARAMETER(Star, responseThreshold, int, 30); - PARAMETER(Star, lineThresholdProjected, int, 10); - PARAMETER(Star, lineThresholdBinarized, int, 8); - PARAMETER(Star, suppressNonmaxSize, int, 5); + PARAMETER(Detector_Descriptor, Star_maxSize, int, 45); + PARAMETER(Detector_Descriptor, Star_responseThreshold, int, 30); + PARAMETER(Detector_Descriptor, Star_lineThresholdProjected, int, 10); + PARAMETER(Detector_Descriptor, Star_lineThresholdBinarized, int, 8); + PARAMETER(Detector_Descriptor, Star_suppressNonmaxSize, int, 5); - PARAMETER(SURF, hessianThreshold, double, 600.0); - PARAMETER(SURF, nOctaves, int, 4); - PARAMETER(SURF, nOctaveLayers, int, 2); - PARAMETER(SURF, extended, bool, true); - PARAMETER(SURF, upright, bool, false); + PARAMETER(Detector_Descriptor, SURF_hessianThreshold, double, 600.0); + PARAMETER(Detector_Descriptor, SURF_nOctaves, int, 4); + PARAMETER(Detector_Descriptor, SURF_nOctaveLayers, int, 2); + PARAMETER(Detector_Descriptor, SURF_extended, bool, true); + PARAMETER(Detector_Descriptor, SURF_upright, bool, false); - PARAMETER(NearestNeighbor, nndrRatioUsed, bool, true); - PARAMETER(NearestNeighbor, nndrRatio, float, 0.8f); - PARAMETER(NearestNeighbor, minDistanceUsed, bool, false); - PARAMETER(NearestNeighbor, minDistance, float, 1.6f); + PARAMETER(NearestNeighbor, 1Strategy, QString, "1:Linear;KDTree;KMeans;Composite;Autotuned;Lsh"); + PARAMETER(NearestNeighbor, 2Distance_type, QString, "0:EUCLIDEAN_L2;MANHATTAN_L1;MINKOWSKI;MAX;HIST_INTERSECT;HELLINGER;CHI_SQUARE_CS;KULLBACK_LEIBLER_KL"); + PARAMETER(NearestNeighbor, 3nndrRatioUsed, bool, true); + PARAMETER(NearestNeighbor, 4nndrRatio, float, 0.8f); + PARAMETER(NearestNeighbor, 5minDistanceUsed, bool, false); + PARAMETER(NearestNeighbor, 6minDistance, float, 1.6f); + + PARAMETER(NearestNeighbor, KDTree_trees, int, 4); + + PARAMETER(NearestNeighbor, Composite_trees, int, 4); + PARAMETER(NearestNeighbor, Composite_branching, int, 32); + PARAMETER(NearestNeighbor, Composite_iterations, int, 11); + PARAMETER(NearestNeighbor, Composite_centers_init, QString, "0:RANDOM;GONZALES;KMEANSPP"); + PARAMETER(NearestNeighbor, Composite_cb_index, double, 0.2); + + PARAMETER(NearestNeighbor, Autotuned_target_precision, double, 0.8); + PARAMETER(NearestNeighbor, Autotuned_build_weight, double, 0.01); + PARAMETER(NearestNeighbor, Autotuned_memory_weight, double, 0); + PARAMETER(NearestNeighbor, Autotuned_sample_fraction, double, 0.1); + + PARAMETER(NearestNeighbor, KMeans_branching, int, 32); + PARAMETER(NearestNeighbor, KMeans_iterations, int, 11); + PARAMETER(NearestNeighbor, KMeans_centers_init, QString, "0:RANDOM;GONZALES;KMEANSPP"); + PARAMETER(NearestNeighbor, KMeans_cb_index, double, 0.2); + + PARAMETER(NearestNeighbor, Lsh_table_number, int, 20); + PARAMETER(NearestNeighbor, Lsh_key_size, int, 10); + PARAMETER(NearestNeighbor, Lsh_multi_probe_level, int, 2); PARAMETER(General, autoStartCamera, bool, false); PARAMETER(General, autoUpdateObjects, bool, true); PARAMETER(General, nextObjID, uint, 1); - PARAMETER(General, imageFormats, QString, "*.png *.jpg *.bmp *.tiff") - PARAMETER(General, videoFormats, QString, "*.avi *.m4v *.mp4") + PARAMETER(General, imageFormats, QString, "*.png *.jpg *.bmp *.tiff *.ppm"); + PARAMETER(General, videoFormats, QString, "*.avi *.m4v *.mp4"); + PARAMETER(General, mirrorView, bool, true); + PARAMETER(General, invertedSearch, bool, false); PARAMETER(Homography, homographyComputed, bool, true); PARAMETER(Homography, ransacReprojThr, double, 1.0); @@ -141,8 +167,8 @@ public: static QString iniDefaultPath(); static QString iniDefaultFileName() {return "config.ini";} - static void loadSettings(const QString & fileName = QString(), QByteArray * windowGeometry = 0); - static void saveSettings(const QString & fileName = QString(), const QByteArray & windowGeometry = QByteArray()); + static void loadSettings(const QString & fileName = QString(), QByteArray * windowGeometry = 0, QByteArray * windowState = 0); + static void saveSettings(const QString & fileName = QString(), const QByteArray & windowGeometry = QByteArray(), const QByteArray & windowState = QByteArray()); static const ParametersMap & getDefaultParameters() {return defaultParameters_;} static const ParametersMap & getParameters() {return parameters_;} @@ -156,6 +182,11 @@ public: static QString currentDescriptorType(); static QString currentDetectorType(); + static QString currentNearestNeighborType(); + + static cv::flann::IndexParams * createFlannIndexParams(); + static cvflann::flann_distance_t getFlannDistanceType(); + static cv::flann::SearchParams getFlannSearchParams(); private: Settings(){} diff --git a/src/rtabmap/PdfPlot.cpp b/src/rtabmap/PdfPlot.cpp new file mode 100644 index 00000000..2703d846 --- /dev/null +++ b/src/rtabmap/PdfPlot.cpp @@ -0,0 +1,163 @@ +// Taken from RTAB-Map library r605 [www.rtabmap.googlecode.com] + +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#include "PdfPlot.h" + +#define ULOGGER_DEBUG(A, ...) + +namespace rtabmap { + +PdfPlotItem::PdfPlotItem(float dataX, float dataY, float width, int childCount) : + UPlotItem(dataX, dataY, width), + _img(0), + _imagesRef(0) +{ + setLikelihood(dataX, dataY, childCount); + _text = new QGraphicsTextItem(this); + _text->setVisible(false); +} + +PdfPlotItem::~PdfPlotItem() +{ + +} + +void PdfPlotItem::setLikelihood(int id, float value, int childCount) +{ + if(_img && id != this->data().x()) + { + delete _img; + _img = 0; + } + this->setData(QPointF(id, value)); + _childCount = childCount; +} + +void PdfPlotItem::showDescription(bool shown) +{ + if(shown) + { + if(!_img && _imagesRef) + { + QImage img; + QMap::const_iterator iter = _imagesRef->find(int(this->data().x())); + if(iter != _imagesRef->constEnd()) + { + if(img.loadFromData(iter.value(), "JPEG")) + { + QPixmap scaled = QPixmap::fromImage(img).scaledToWidth(128); + _img = new QGraphicsPixmapItem(scaled, this); + _img->setVisible(false); + } + } + } + + if(_img) + _text->setPos(this->mapFromScene(4+150,0)); + else + _text->setPos(this->mapFromScene(4,0)); + if(_childCount >= 0) + { + _text->setPlainText(QString("ID = %1\nValue = %2\nWeight = %3").arg(this->data().x()).arg(this->data().y()).arg(_childCount)); + } + else + { + _text->setPlainText(QString("ID = %1\nValue = %2").arg(this->data().x()).arg(this->data().y())); + } + _text->setVisible(true); + if(_img) + { + _img->setPos(this->mapFromScene(4,0)); + _img->setVisible(true); + } + } + else + { + _text->setVisible(false); + if(_img) + _img->setVisible(false); + } + UPlotItem::showDescription(shown); +} + + + + + +PdfPlotCurve::PdfPlotCurve(const QString & name, const QMap * imagesMapRef = 0, QObject * parent) : + UPlotCurve(name, parent), + _imagesMapRef(imagesMapRef) +{ + +} + +PdfPlotCurve::~PdfPlotCurve() +{ + +} + +void PdfPlotCurve::clear() +{ + UPlotCurve::clear(); +} + +void PdfPlotCurve::setData(const QMap & dataMap, const QMap & weightsMap) +{ + ULOGGER_DEBUG("dataMap=%d, weightsMap=%d", dataMap.size(), weightsMap.size()); + if(dataMap.size() > 0) + { + //match the size of the current data + int margin = int((_items.size()+1)/2) - dataMap.size(); + + while(margin < 0) + { + PdfPlotItem * newItem = new PdfPlotItem(0, 0, 2, 0); + newItem->setImagesRef(_imagesMapRef); + this->_addValue(newItem); + ++margin; + } + + while(margin > 0) + { + this->removeItem(0); + --margin; + } + + ULOGGER_DEBUG("itemsize=%d", _items.size()); + + // update values + QList::iterator iter = _items.begin(); + QMap::const_iterator j=weightsMap.begin(); + for(QMap::const_iterator i=dataMap.begin(); i!=dataMap.end(); ++i, ++j) + { + ((PdfPlotItem*)*iter)->setLikelihood(i.key(), i.value(), j!=weightsMap.end()?j.value():-1); + //2 times... + ++iter; + ++iter; + } + + //reset minMax, this will force the plot to update the axes + this->updateMinMax(); + emit dataChanged(this); + } +} + +} diff --git a/src/rtabmap/PdfPlot.h b/src/rtabmap/PdfPlot.h new file mode 100644 index 00000000..042f2c6b --- /dev/null +++ b/src/rtabmap/PdfPlot.h @@ -0,0 +1,69 @@ +// Taken from RTAB-Map library r605 [www.rtabmap.googlecode.com] + +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#ifndef PDFPLOT_H_ +#define PDFPLOT_H_ + +#include "utilite/UPlot.h" + +namespace rtabmap { + +class PdfPlotItem : public UPlotItem +{ +public: + PdfPlotItem(float dataX, float dataY, float width, int childCount = -1); + virtual ~PdfPlotItem(); + + void setLikelihood(int id, float value, int childCount); + void setImagesRef(const QMap * imagesRef) {_imagesRef = imagesRef;} + + float value() const {return this->data().y();} + int id() const {return this->data().x();} + +protected: + virtual void showDescription(bool shown); + +private: + QGraphicsTextItem * _text; + QGraphicsPixmapItem * _img; + int _childCount; + const QMap * _imagesRef; + +}; + +class PdfPlotCurve : public UPlotCurve +{ + Q_OBJECT + +public: + PdfPlotCurve(const QString & name, const QMap * imagesMapRef, QObject * parent = 0); + virtual ~PdfPlotCurve(); + + virtual void clear(); + void setData(const QMap & dataMap, const QMap & weightsMap); + +private: + const QMap * _imagesMapRef; +}; + +} + +#endif /* PDFPLOT_H_ */ diff --git a/src/ui/aboutDialog.ui b/src/ui/aboutDialog.ui index ab5713a7..c1ff97ba 100644 --- a/src/ui/aboutDialog.ui +++ b/src/ui/aboutDialog.ui @@ -6,8 +6,8 @@ 0 0 - 404 - 223 + 527 + 289 @@ -137,6 +137,23 @@ p, li { white-space: pre-wrap; } + + + + OpenCV version : + + + + + + + + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + diff --git a/src/ui/mainWindow.ui b/src/ui/mainWindow.ui index 8fce5800..547d0e80 100644 --- a/src/ui/mainWindow.ui +++ b/src/ui/mainWindow.ui @@ -6,8 +6,8 @@ 0 0 - 1104 - 497 + 1180 + 684 @@ -134,8 +134,8 @@ 0 0 - 1104 - 25 + 1180 + 22 @@ -185,8 +185,8 @@ - 300 - 194 + 360 + 376 @@ -200,6 +200,188 @@ + + 0 + + + 0 + + + + + Statistics + + + + 0 + + + + + 6 + + + 0 + + + + + Detect outliers and GUI + + + + + + + 000 + + + + + + + Descriptors extraction + + + + + + + ms + + + + + + + 000 + + + + + + + ms + + + + + + + Features detection + + + + + + + 000 + + + + + + + Descriptors matching + + + + + + + Descriptors indexing + + + + + + + 000 + + + + + + + 000 + + + + + + + ms + + + + + + + ms + + + + + + + ms + + + + + + + 000 + + + + + + + Min matched distance + + + + + + + Max matched distance + + + + + + + 000 + + + + + + + Total + + + + + + + 000 + + + + + + + ms + + + + + + + + @@ -210,171 +392,14 @@ 0 0 - 282 - 365 + 348 + 373 - Statistics + Dummy - - - - - 6 - - - 0 - - - - - ms - - - - - - - 000 - - - - - - - ms - - - - - - - Descriptors extraction - - - - - - - Features detection - - - - - - - 000 - - - - - - - Descriptors matching - - - - - - - Descriptors indexing - - - - - - - 000 - - - - - - - 000 - - - - - - - ms - - - - - - - ms - - - - - - - Detect outliers and GUI - - - - - - - 000 - - - - - - - ms - - - - - - - 000 - - - - - - - Min matched distance - - - - - - - Max matched distance - - - - - - - 000 - - - - - - - - - Qt::Vertical - - - - 20 - 174 - - - - - + @@ -389,12 +414,6 @@ - - - 300 - 196 - - Objects @@ -403,6 +422,12 @@ + + 0 + + + 0 + @@ -419,11 +444,14 @@ 0 0 - 280 - 394 + 265 + 557 + + 0 + 0 @@ -445,11 +473,58 @@ - - - Update objects + + + 12 - + + + + Update objects + + + + + + + + 20 + 0 + + + + 100 + + + 100 + + + Qt::Horizontal + + + + + + + + + + + Likelihood + + + 8 + + + + + 0 + + + 0 + + + @@ -533,6 +608,12 @@
ParametersToolBox.h
1 + + UPlot + QWidget +
utilite/UPlot.h
+ 1 +
diff --git a/src/utilite/UPlot.cpp b/src/utilite/UPlot.cpp new file mode 100644 index 00000000..df0d87aa --- /dev/null +++ b/src/utilite/UPlot.cpp @@ -0,0 +1,2559 @@ +// Taken from UtiLite library r185 [www.utilite.googlecode.com] + +/* +* utilite is a cross-platform library with +* useful utilities for fast and small developing. +* Copyright (C) 2010 Mathieu Labbe +* +* utilite is free library: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* utilite is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +*/ + +#include "utilite/UPlot.h" +//#include "utilite/ULogger.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef QT_SVG_LIB +#include +#endif +#include + +#define PRINT_DEBUG 0 +#define ULOGGER_ERROR(A, ...) +#define ULOGGER_WARN(A, ...) + +UPlotItem::UPlotItem(qreal dataX, qreal dataY, qreal width) : + QGraphicsEllipseItem(0, 0, width, width, 0), + _previousItem(0), + _nextItem(0) +{ + _data.setX(dataX); + _data.setY(dataY); + this->setZValue(1); + this->setAcceptsHoverEvents(true); + _text = new QGraphicsTextItem(this); + _text->setPlainText(QString("(%1,%2)").arg(_data.x()).arg(_data.y())); + _text->setVisible(false); + this->setFlag(QGraphicsItem::ItemIsFocusable, true); +} + +UPlotItem::UPlotItem(const QPointF & data, qreal width) : + QGraphicsEllipseItem(0, 0, width, width, 0), + _data(data), + _previousItem(0), + _nextItem(0) +{ + this->setZValue(1); + this->setAcceptsHoverEvents(true); + _text = new QGraphicsTextItem(this); + _text->setPlainText(QString("(%1,%2)").arg(_data.x()).arg(_data.y())); + _text->setVisible(false); + this->setFlag(QGraphicsItem::ItemIsFocusable, true); +} + +UPlotItem::~UPlotItem() +{ + if(_previousItem && _nextItem) + { + _previousItem->setNextItem(_nextItem); + _nextItem->setPreviousItem(_previousItem); + } + else if(_previousItem) + { + _previousItem->setNextItem(0); + } + else if(_nextItem) + { + _nextItem->setPreviousItem(0); + } +} + +void UPlotItem::setData(const QPointF & data) +{ + _data = data; +} + +void UPlotItem::setNextItem(UPlotItem * nextItem) +{ + if(_nextItem != nextItem) + { + _nextItem = nextItem; + if(nextItem) + { + nextItem->setPreviousItem(this); + } + } +} + +void UPlotItem::setPreviousItem(UPlotItem * previousItem) +{ + if(_previousItem != previousItem) + { + _previousItem = previousItem; + if(previousItem) + { + previousItem->setNextItem(this); + } + } +} + +void UPlotItem::showDescription(bool shown) +{ + _text->setPlainText(QString("(%1,%2)").arg(_data.x()).arg(_data.y())); + if(shown) + { + this->setPen(QPen(Qt::black, 2)); + if(this->scene()) + { + QRectF rect = this->scene()->sceneRect(); + QPointF p = this->pos(); + QRectF br = _text->boundingRect(); + + // Make sure the text is always in the scene + if(p.x() - br.width() < 0) + { + p.setX(0); + } + else if(p.x() + br.width() > rect.width()) + { + p.setX(rect.width() - br.width()); + } + else + { + p.setX(p.x() - br.width()); + } + + if(p.y() - br.height() < 0) + { + p.setY(0); + } + else + { + p.setY(p.y() - br.height()); + } + + _text->setPos(this->mapFromScene(p)); + } + + _text->setVisible(true); + } + else + { + this->setPen(QPen(Qt::black, 1)); + _text->setVisible(false); + } +} + +void UPlotItem::hoverEnterEvent(QGraphicsSceneHoverEvent * event) +{ + QGraphicsScene * scene = this->scene(); + if(scene && scene->focusItem() == 0) + { + this->showDescription(true); + } + else + { + this->setPen(QPen(Qt::black, 2)); + } + QGraphicsEllipseItem::hoverEnterEvent(event); +} + +void UPlotItem::hoverLeaveEvent(QGraphicsSceneHoverEvent * event) +{ + if(!this->hasFocus()) + { + this->showDescription(false); + } + QGraphicsEllipseItem::hoverEnterEvent(event); +} + +void UPlotItem::focusInEvent(QFocusEvent * event) +{ + this->showDescription(true); + QGraphicsEllipseItem::focusInEvent(event); +} + +void UPlotItem::focusOutEvent(QFocusEvent * event) +{ + this->showDescription(false); + QGraphicsEllipseItem::focusOutEvent(event); +} + +void UPlotItem::keyReleaseEvent(QKeyEvent * keyEvent) +{ + //Get the next/previous visible item + if(keyEvent->key() == Qt::Key_Right) + { + UPlotItem * next = _nextItem; + while(next && !next->isVisible()) + { + next = next->nextItem(); + } + if(next && next->isVisible()) + { + this->clearFocus(); + next->setFocus(); + } + } + else if(keyEvent->key() == Qt::Key_Left) + { + UPlotItem * previous = _previousItem; + while(previous && !previous->isVisible()) + { + previous = previous->previousItem(); + } + if(previous && previous->isVisible()) + { + this->clearFocus(); + previous->setFocus(); + } + } + QGraphicsEllipseItem::keyReleaseEvent(keyEvent); +} + + + + + +UPlotCurve::UPlotCurve(const QString & name, QObject * parent) : + QObject(parent), + _plot(0), + _name(name), + _xIncrement(1), + _xStart(0), + _visible(true), + _valuesShown(false) +{ +} + +UPlotCurve::UPlotCurve(const QString & name, QVector data, QObject * parent) : + QObject(parent), + _plot(0), + _name(name), + _xIncrement(1), + _xStart(0), + _visible(true), + _valuesShown(false) +{ + this->setData(data); +} + +UPlotCurve::UPlotCurve(const QString & name, const QVector & x, const QVector & y, QObject * parent) : + QObject(parent), + _plot(0), + _name(name), + _xIncrement(1), + _xStart(0), + _visible(true), + _valuesShown(false) +{ + this->setData(x, y); +} + +UPlotCurve::~UPlotCurve() +{ + if(_plot) + { + _plot->removeCurve(this); + } +#if PRINT_DEBUG + ULOGGER_DEBUG("%s", this->name().toStdString().c_str()); +#endif + this->clear(); +} + +void UPlotCurve::attach(UPlot * plot) +{ + if(!plot || plot == _plot) + { + return; + } + this->setParent(plot); + if(_plot) + { + _plot->removeCurve(this); + } + _plot = plot; + for(int i=0; i<_items.size(); ++i) + { + _plot->addItem(_items.at(i)); + } +} + +void UPlotCurve::detach(UPlot * plot) +{ +#if PRINT_DEBUG + ULOGGER_DEBUG("curve=\"%s\" from plot=\"%s\"", this->objectName().toStdString().c_str(), plot?plot->objectName().toStdString().c_str():""); +#endif + if(plot && _plot == plot) + { + _plot = 0; + for(int i=0; i<_items.size(); ++i) + { + if(_items.at(i)->scene()) + { + _items.at(i)->scene()->removeItem(_items.at(i)); + } + } + } +} + +void UPlotCurve::updateMinMax() +{ + float x,y; + const UPlotItem * item; + if(!_items.size()) + { + _minMax = QVector(); + } + else + { + _minMax = QVector(4); + } + for(int i=0; i<_items.size(); ++i) + { + item = qgraphicsitem_cast(_items.at(i)); + if(item) + { + x = item->data().x(); + y = item->data().y(); + if(i==0) + { + _minMax[0] = x; + _minMax[1] = x; + _minMax[2] = y; + _minMax[3] = y; + } + else + { + if(x<_minMax[0]) _minMax[0] = x; + if(x>_minMax[1]) _minMax[1] = x; + if(y<_minMax[2]) _minMax[2] = y; + if(y>_minMax[3]) _minMax[3] = y; + } + } + } +} + +void UPlotCurve::_addValue(UPlotItem * data) +{ + // add item + if(data) + { + float x = data->data().x(); + float y = data->data().y(); + if(_minMax.size() != 4) + { + _minMax = QVector(4); + } + if(_items.size()) + { + data->setPreviousItem((UPlotItem *)_items.last()); + + QGraphicsLineItem * line = new QGraphicsLineItem(); + line->setPen(_pen); + line->setVisible(false); + _items.append(line); + if(_plot) + { + _plot->addItem(line); + } + + //Update min/max + if(x<_minMax[0]) _minMax[0] = x; + if(x>_minMax[1]) _minMax[1] = x; + if(y<_minMax[2]) _minMax[2] = y; + if(y>_minMax[3]) _minMax[3] = y; + } + else + { + _minMax[0] = x; + _minMax[1] = x; + _minMax[2] = y; + _minMax[3] = y; + } + _items.append(data); + data->setVisible(false); + //data->showDescription(_valuesShown); + if(_plot) + { + _plot->addItem(_items.last()); + } + } + else + { + ULOGGER_ERROR("Data is null ?!?"); + } +} + +void UPlotCurve::addValue(UPlotItem * data) +{ + // add item + if(data) + { + this->_addValue(data); + emit dataChanged(this); + } +} + +void UPlotCurve::addValue(float x, float y) +{ + float width = 2; // TODO warn : hard coded value! + this->addValue(new UPlotItem(x,y,width)); +} + +void UPlotCurve::addValue(float y) +{ + float x = 0; + if(_items.size()) + { + UPlotItem * lastItem = (UPlotItem *)_items.last(); + x = lastItem->data().x() + _xIncrement; + } + else + { + x = _xStart; + } + this->addValue(x,y); +} + +void UPlotCurve::addValue(const QString & value) +{ + bool ok; + float v = value.toFloat(&ok); + if(ok) + { + this->addValue(v); + } + else + { + ULOGGER_ERROR("Value not valid, must be a number, received %s", value.toStdString().c_str()); + } +} + +void UPlotCurve::addValues(QVector & data) +{ + for(int i=0; i_addValue(data.at(i)); + } + emit dataChanged(this); +} + +void UPlotCurve::addValues(const QVector & xs, const QVector & ys) +{ + float width = 2; // TODO warn : hard coded value! + for(int i=0; i_addValue(new UPlotItem(xs.at(i),ys.at(i),width)); + } + emit dataChanged(this); +} + +void UPlotCurve::addValues(const QVector & ys) +{ + float x = 0; + float width = 2; // TODO warn : hard coded value! + for(int i=0; idata().x() + _xIncrement; + } + else + { + x = _xStart; + } + this->_addValue(new UPlotItem(x,ys.at(i),width)); + } + emit dataChanged(this); +} + +void UPlotCurve::addValues(const QVector & ys) +{ + float x = 0; + float width = 2; // TODO warn : hard coded value! + for(int i=0; idata().x() + _xIncrement; + } + else + { + x = _xStart; + } + this->_addValue(new UPlotItem(x,ys.at(i),width)); + } + emit dataChanged(this); +} + +void UPlotCurve::addValues(const std::vector & ys) +{ + float x = 0; + float width = 2; // TODO warn : hard coded value! + for(unsigned int i=0; idata().x() + _xIncrement; + } + else + { + x = _xStart; + } + this->_addValue(new UPlotItem(x,ys.at(i),width)); + } + emit dataChanged(this); +} + +void UPlotCurve::addValues(const std::vector & ys) +{ + float x = 0; + float width = 2; // TODO warn : hard coded value! + for(unsigned int i=0; idata().x() + _xIncrement; + } + else + { + x = _xStart; + } + this->_addValue(new UPlotItem(x,ys.at(i),width)); + } + emit dataChanged(this); +} + +int UPlotCurve::removeItem(int index) +{ + if(index >= 0 && index < _items.size()) + { + if(index!=0) + { + index-=1; + delete _items.takeAt(index); // the line + } + else if(_items.size()>1) + { + delete _items.takeAt(index+1); // the line + } + UPlotItem * item = (UPlotItem *)_items.takeAt(index); // the plot item + //Update min/max + if(_minMax.size() == 4) + { + if(item->data().x() == _minMax[0] || item->data().x() == _minMax[1] || + item->data().y() == _minMax[2] || item->data().y() == _minMax[3]) + { + if(_items.size()) + { + UPlotItem * tmp = (UPlotItem *)_items.at(0); + float x = tmp->data().x(); + float y = tmp->data().y(); + _minMax[0]=x; + _minMax[1]=x; + _minMax[2]=y; + _minMax[3]=y; + for(int i = 2; i<_items.size(); i+=2) + { + tmp = (UPlotItem*)_items.at(i); + x = tmp->data().x(); + y = tmp->data().y(); + if(x<_minMax[0]) _minMax[0] = x; + if(x>_minMax[1]) _minMax[1] = x; + if(y<_minMax[2]) _minMax[2] = y; + if(y>_minMax[3]) _minMax[3] = y; + } + } + else + { + _minMax = QVector(); + } + } + } + delete item; + } + + return index; +} + +void UPlotCurve::removeItem(UPlotItem * item) // ownership is transfered to the caller +{ + for(int i=0; i<_items.size(); ++i) + { + if(_items.at(i) == item) + { + if(i!=0) + { + i-=1; + delete _items[i]; + _items.removeAt(i); + } + else if(_items.size()>1) + { + delete _items[i+1]; + _items.removeAt(i+1); + } + item->scene()->removeItem(item); + _items.removeAt(i); + break; + } + } +} + +void UPlotCurve::clear() +{ +#if PRINT_DEBUG + ULOGGER_DEBUG("%s", this->name().toStdString().c_str()); +#endif + qDeleteAll(_items); + _items.clear(); +} + +void UPlotCurve::setPen(const QPen & pen) +{ + _pen = pen; + for(int i=1; i<_items.size(); i+=2) + { + ((QGraphicsLineItem*) _items.at(i))->setPen(_pen); + } +} + +void UPlotCurve::setBrush(const QBrush & brush) +{ + _brush = brush; + ULOGGER_WARN("Not used..."); +} + +void UPlotCurve::update(float scaleX, float scaleY, float offsetX, float offsetY, float xDir, float yDir, bool allDataKept) +{ + //ULOGGER_DEBUG("scaleX=%f, scaleY=%f, offsetX=%f, offsetY=%f, xDir=%d, yDir=%d, _plot->scene()->width()=%f, _plot->scene()->height=%f", scaleX, scaleY, offsetX, offsetY, xDir, yDir,_plot->scene()->width(),_plot->scene()->height()); + //make sure direction values are 1 or -1 + xDir<0?xDir=-1:xDir=1; + yDir<0?yDir=-1:yDir=1; + + bool hide = false; + for(int i=_items.size()-1; i>=0; --i) + { + if(i%2 == 0) + { + UPlotItem * item = (UPlotItem *)_items.at(i); + if(hide) + { + if(allDataKept) + { + // if not visible, stop looping... all other items are normally already hided + if(!item->isVisible()) + { + break; + } + item->setVisible(false); + } + else + { + //remove the item with his line + i = this->removeItem(i); + } + } + else + { + QPointF newPos(((xDir*item->data().x()+offsetX)*scaleX-item->rect().width()/2.0f), + ((yDir*item->data().y()+offsetY)*scaleY-item->rect().width()/2.0f)); + if(!item->isVisible()) + { + item->setVisible(true); + } + item->setPos(newPos); + } + } + else + { + if(hide) + { + _items.at(i)->setVisible(false); + } + else + { + UPlotItem * from = (UPlotItem *)_items.at(i-1); + UPlotItem * to = (UPlotItem *)_items.at(i+1); + QGraphicsLineItem * lineItem = (QGraphicsLineItem *)_items.at(i); + lineItem->setLine((xDir*from->data().x()+offsetX)*scaleX, + (yDir*from->data().y()+offsetY)*scaleY, + (xDir*to->data().x()+offsetX)*scaleX, + (yDir*to->data().y()+offsetY)*scaleY); + if(!lineItem->isVisible()) + { + lineItem->setVisible(true); + } + //Don't update not visible items + // (Detect also if the curve goes forward or backward) + QLineF line = lineItem->line(); + if((line.x1() <= line.x2() && line.x2() < 0-((line.x2() - line.x1()))) || + (line.x1() > line.x2() && line.x2() > lineItem->scene()->sceneRect().width() + ((line.x1() - line.x2())))) + { + hide = true; + } + + } + } + } + +} + +void UPlotCurve::draw(QPainter * painter) +{ + if(painter) + { + for(int i=_items.size()-1; i>=0 && _items.at(i)->isVisible(); i-=2) + { + //plotItem + const UPlotItem * item = (const UPlotItem *)_items.at(i); + int x = (int)item->x(); + if(x<0) + { + break; + } + + // draw line in first + if(i-1>=0) + { + painter->save(); + painter->setPen(this->pen()); + painter->setBrush(this->brush()); + //lineItem + const QGraphicsLineItem * item = (const QGraphicsLineItem *)_items.at(i-1); + QLineF line = item->line(); + int x = (int)line.p1().x(); + if(x<0) + { + line.setP1(QPoint(0, line.p1().y())); + } + painter->drawLine(line); + painter->restore(); + } + + painter->drawEllipse(item->pos()+QPointF(item->rect().width()/2, item->rect().height()/2), (int)item->rect().width()/2, (int)item->rect().height()/2); + } + } +} + +int UPlotCurve::itemsSize() const +{ + return _items.size(); +} + +QPointF UPlotCurve::getItemData(int index) +{ + QPointF data; + //make sure the index point to a PlotItem {PlotItem, line, PlotItem, line...} + if(index>=0 && index < _items.size() && index % 2 == 0 ) + { + data = ((UPlotItem*)_items.at(index))->data(); + } + else + { + ULOGGER_ERROR("Wrong index, not pointing on a PlotItem"); + } + return data; +} + +void UPlotCurve::setVisible(bool visible) +{ + _visible = visible; + for(int i=0; i<_items.size(); ++i) + { + _items.at(i)->setVisible(visible); + } +} + +void UPlotCurve::setXIncrement(float increment) +{ + _xIncrement = increment; +} + +void UPlotCurve::setXStart(float val) +{ + _xStart = val; +} + +void UPlotCurve::setData(QVector & data) +{ + this->clear(); + for(int i = 0; iaddValue(data[i]); + } +} + +void UPlotCurve::setData(const QVector & x, const QVector & y) +{ + if(x.size() == y.size()) + { + //match the size of the current data + int margin = int((_items.size()+1)/2) - x.size(); + while(margin < 0) + { + UPlotItem * newItem = new UPlotItem(0, 0, 2); + this->_addValue(newItem); + ++margin; + } + while(margin > 0) + { + this->removeItem(0); + --margin; + } + + // update values + int index = 0; + QVector::const_iterator i=x.begin(); + QVector::const_iterator j=y.begin(); + for(; i!=x.end() && j!=y.end(); ++i, ++j, index+=2) + { + ((UPlotItem*)_items[index])->setData(QPointF(*i, *j)); + } + + //reset minMax, this will force the plot to update the axes + this->updateMinMax(); + emit dataChanged(this); + } + else + { + ULOGGER_ERROR("Data vectors have not the same size."); + } +} + +void UPlotCurve::setData(const std::vector & x, const std::vector & y) +{ + if(x.size() == y.size()) + { + //match the size of the current data + int margin = int((_items.size()+1)/2) - int(x.size()); + while(margin < 0) + { + UPlotItem * newItem = new UPlotItem(0, 0, 2); + this->_addValue(newItem); + ++margin; + } + while(margin > 0) + { + this->removeItem(0); + --margin; + } + + // update values + int index = 0; + std::vector::const_iterator i=x.begin(); + std::vector::const_iterator j=y.begin(); + for(; i!=x.end() && j!=y.end(); ++i, ++j, index+=2) + { + ((UPlotItem*)_items[index])->setData(QPointF(*i, *j)); + } + + //reset minMax, this will force the plot to update the axes + this->updateMinMax(); + emit dataChanged(this); + } + else + { + ULOGGER_ERROR("Data vectors have not the same size."); + } +} + +void UPlotCurve::setData(const QVector & y) +{ + this->setData(y.toStdVector()); +} + +void UPlotCurve::setData(const std::vector & y) +{ + //match the size of the current data + int margin = int((_items.size()+1)/2) - int(y.size()); + while(margin < 0) + { + UPlotItem * newItem = new UPlotItem(0, 0, 2); + this->_addValue(newItem); + ++margin; + } + while(margin > 0) + { + this->removeItem(0); + --margin; + } + + // update values + int index = 0; + float x = 0; + std::vector::const_iterator j=y.begin(); + for(; j!=y.end(); ++j, index+=2) + { + ((UPlotItem*)_items[index])->setData(QPointF(x++, *j)); + } + + //reset minMax, this will force the plot to update the axes + this->updateMinMax(); + emit dataChanged(this); +} + +void UPlotCurve::getData(QVector & x, QVector & y) const +{ + x.clear(); + y.clear(); + if(_items.size()) + { + x.resize((_items.size()-1)/2+1); + y.resize(x.size()); + int j=0; + for(int i=0; i<_items.size(); i+=2) + { + x[j] = ((UPlotItem*)_items.at(i))->data().x(); + y[j++] = ((UPlotItem*)_items.at(i))->data().y(); + } + } +} + + + + + +UPlotCurveThreshold::UPlotCurveThreshold(const QString & name, float thesholdValue, Qt::Orientation orientation, QObject * parent) : + UPlotCurve(name, parent), + _orientation(orientation) +{ + if(_orientation == Qt::Horizontal) + { + this->addValue(0, thesholdValue); + this->addValue(1, thesholdValue); + } + else + { + this->addValue(thesholdValue, 0); + this->addValue(thesholdValue, 1); + } +} + +UPlotCurveThreshold::~UPlotCurveThreshold() +{ + +} + +void UPlotCurveThreshold::setThreshold(float threshold) +{ +#if PRINT_DEBUG + ULOGGER_DEBUG("%f", threshold); +#endif + if(_items.size() == 3) + { + UPlotItem * item = 0; + if(_orientation == Qt::Horizontal) + { + item = (UPlotItem*)_items.at(0); + item->setData(QPointF(item->data().x(), threshold)); + item = (UPlotItem*)_items.at(2); + item->setData(QPointF(item->data().x(), threshold)); + } + else + { + item = (UPlotItem*)_items.at(0); + item->setData(QPointF(threshold, item->data().y())); + item = (UPlotItem*)_items.at(2); + item->setData(QPointF(threshold, item->data().y())); + } + } + else + { + ULOGGER_ERROR("A threshold must has only 3 items (1 PlotItem + 1 QGraphicsLineItem + 1 PlotItem)"); + } +} + +void UPlotCurveThreshold::setOrientation(Qt::Orientation orientation) +{ + if(_orientation != orientation) + { + _orientation = orientation; + if(_items.size() == 3) + { + UPlotItem * item = 0; + item = (UPlotItem*)_items.at(0); + item->setData(QPointF(item->data().y(), item->data().x())); + item = (UPlotItem*)_items.at(2); + item->setData(QPointF(item->data().y(), item->data().x())); + } + else + { + ULOGGER_ERROR("A threshold must has only 3 items (1 PlotItem + 1 QGraphicsLineItem + 1 PlotItem)"); + } + } +} + +void UPlotCurveThreshold::update(float scaleX, float scaleY, float offsetX, float offsetY, float xDir, float yDir, bool allDataKept) +{ + if(_items.size() == 3) + { + if(_plot) + { + UPlotItem * item = 0; + if(_orientation == Qt::Horizontal) + { + //(xDir*item->data().x()+offsetX)*scaleX + item = (UPlotItem*)_items.at(0); + item->setData(QPointF(-offsetX/xDir, item->data().y())); + item = (UPlotItem*)_items.at(2); + item->setData(QPointF( (_plot->sceneRect().width()/scaleX-offsetX)/xDir, item->data().y())); + } + else + { + item = (UPlotItem*)_items.at(0); + item->setData(QPointF(item->data().x(), -offsetY/yDir)); + item = (UPlotItem*)_items.at(2); + item->setData(QPointF(item->data().x(), (_plot->sceneRect().height()/scaleY-offsetY)/yDir)); + } + this->updateMinMax(); + } + } + else + { + ULOGGER_ERROR("A threshold must has only 3 items (1 PlotItem + 1 QGraphicsLineItem + 1 PlotItem)"); + } + UPlotCurve::update(scaleX, scaleY, offsetX, offsetY, xDir, yDir, allDataKept); +} + + + + + + + +UPlotAxis::UPlotAxis(Qt::Orientation orientation, float min, float max, QWidget * parent) : + QWidget(parent), + _orientation(orientation), + _reversed(false), + _gradMaxDigits(4), + _border(0) +{ + if(_orientation == Qt::Vertical) + { + _reversed = true; // default bottom->up + } +#ifdef WIN32 + this->setMinimumSize(15, 25); +#else + this->setMinimumSize(15, 25); +#endif + this->setAxis(min, max); // this initialize all attributes +} + +UPlotAxis::~UPlotAxis() +{ +#if PRINT_DEBUG + ULOGGER_DEBUG(""); +#endif +} + +// Vertical :bottom->up, horizontal :right->left +void UPlotAxis::setReversed(bool reversed) +{ + if(_reversed != reversed) + { + float min = _min; + _min = _max; + _max = min; + } + _reversed = reversed; +} + +void UPlotAxis::setAxis(float & min, float & max) +{ + int borderMin = 0; + int borderMax = 0; + if(_orientation == Qt::Vertical) + { + borderMin = borderMax = this->fontMetrics().height()/2; + } + else + { + borderMin = this->fontMetrics().width(QString::number(_min,'g',_gradMaxDigits))/2; + borderMax = this->fontMetrics().width(QString::number(_max,'g',_gradMaxDigits))/2; + } + int border = borderMin>borderMax?borderMin:borderMax; + int borderDelta; + int length; + if(_orientation == Qt::Vertical) + { + length = (this->height()-border*2); + } + else + { + length = (this->width()-border*2); + } + + if(length <= 70) + { + _count = 5; + } + else if(length <= 175) + { + _count = 10; + } + else if(length <= 350) + { + _count = 20; + } + else if(length <= 700) + { + _count = 40; + } + else if(length <= 1000) + { + _count = 60; + } + else if(length <= 1300) + { + _count = 80; + } + else + { + _count = 100; + } + + // Rounding min and max + if(min != max) + { + float mul = 1; + float rangef = max - min; + int countStep = _count/5; + float val; + for(int i=0; i<6; ++i) + { + val = (rangef/float(countStep)) * mul; + if( val >= 1.0f && val < 10.0f) + { + break; + } + else if(val<1) + { + mul *= 10.0f; + } + else + { + mul /= 10.0f; + } + } + //ULOGGER_DEBUG("min=%f, max=%f", min, max); + int minR = min*mul-0.9; + int maxR = max*mul+0.9; + min = float(minR)/mul; + max = float(maxR)/mul; + //ULOGGER_DEBUG("mul=%f, minR=%d, maxR=%d,countStep=%d", mul, minR, maxR, countStep); + } + + _min = min; + _max = max; + + if(_reversed) + { + _min = _max; + _max = min; + } + + if(_orientation == Qt::Vertical) + { + _step = length/_count; + borderDelta = length - (_step*_count); + } + else + { + _step = length/_count; + borderDelta = length - (_step*_count); + } + + if(borderDelta%2 != 0) + { + borderDelta+=1; + } + + _border = border + borderDelta/2; + + //Resize estimation + if(_orientation == Qt::Vertical) + { + int minWidth = 0; + for (int i = 0; i <= _count; i+=5) + { + QString n(QString::number(_min + (i/5)*((_max-_min)/(_count/5)),'g',_gradMaxDigits)); + if(this->fontMetrics().width(n) > minWidth) + { + minWidth = this->fontMetrics().width(n); + } + } + this->setMinimumWidth(15+minWidth); + } +} + +void UPlotAxis::paintEvent(QPaintEvent * event) +{ + QPainter painter(this); + if(_orientation == Qt::Vertical) + { + painter.translate(0, _border); + for (int i = 0; i <= _count; ++i) + { + if(i%5 == 0) + { + painter.drawLine(this->width(), 0, this->width()-10, 0); + QLabel n(QString::number(_min + (i/5)*((_max-_min)/(_count/5)),'g',_gradMaxDigits)); + painter.drawText(this->width()-(12+n.sizeHint().width()), n.sizeHint().height()/2-2, n.text()); + } + else + { + painter.drawLine(this->width(), 0, this->width()-5, 0); + } + painter.translate(0, _step); + } + } + else + { + painter.translate(_border, 0); + for (int i = 0; i <= _count; ++i) + { + if(i%5 == 0) + { + painter.drawLine(0, 0, 0, 10); + QLabel n(QString::number(_min + (i/5)*((_max-_min)/(_count/5)),'g',_gradMaxDigits)); + painter.drawText(-(n.sizeHint().width()/2)+1, 22, n.text()); + } + else + { + painter.drawLine(0, 0, 0, 5); + } + painter.translate(_step, 0); + } + } +} + + + + +UPlotLegendItem::UPlotLegendItem(const UPlotCurve * curve, QWidget * parent) : + QPushButton(parent), + _curve(curve) +{ + QString nameSpaced = curve->name(); + nameSpaced.replace('_', ' '); + this->setText(nameSpaced); + + _aChangeText = new QAction(tr("Change text..."), this); + _aResetText = new QAction(tr("Reset text..."), this); + _aRemoveCurve = new QAction(tr("Remove this curve"), this); + _aCopyToClipboard = new QAction(tr("Copy curve data to the clipboard"), this); + _menu = new QMenu(tr("Curve"), this); + _menu->addAction(_aChangeText); + _menu->addAction(_aResetText); + _menu->addAction(_aRemoveCurve); + _menu->addAction(_aCopyToClipboard); +} + +UPlotLegendItem::~UPlotLegendItem() +{ + +} +void UPlotLegendItem::contextMenuEvent(QContextMenuEvent * event) +{ + QAction * action = _menu->exec(event->globalPos()); + if(action == _aChangeText) + { + bool ok; + QString text = QInputDialog::getText(this, _aChangeText->text(), tr("Name :"), QLineEdit::Normal, this->text(), &ok); + if(ok && !text.isEmpty()) + { + this->setText(text); + } + } + else if(action == _aResetText) + { + if(_curve) + { + this->setText(_curve->name()); + } + } + else if(action == _aRemoveCurve) + { + emit legendItemRemoved(_curve); + } + else if (action == _aCopyToClipboard) + { + if(_curve) + { + QVector x; + QVector y; + _curve->getData(x, y); + QString textX; + QString textY; + for(int i=0; isetText((textX+"\n")+textY); + } + } +} + + + + + + +UPlotLegend::UPlotLegend(QWidget * parent) : + QWidget(parent), + _flat(true) +{ + //menu + _aUseFlatButtons = new QAction(tr("Use flat buttons"), this); + _aUseFlatButtons->setCheckable(true); + _aUseFlatButtons->setChecked(_flat); + _menu = new QMenu(tr("Legend"), this); + _menu->addAction(_aUseFlatButtons); + + QVBoxLayout * vLayout = new QVBoxLayout(this); + vLayout->setContentsMargins(0,0,0,0); + this->setLayout(vLayout); + vLayout->addStretch(0); + vLayout->setSpacing(0); +} + +UPlotLegend::~UPlotLegend() +{ +#if PRINT_DEBUG + ULOGGER_DEBUG(""); +#endif +} + +void UPlotLegend::setFlat(bool on) +{ + if(_flat != on) + { + _flat = on; + QList items = this->findChildren(); + for(int i=0; isetFlat(_flat); + items.at(i)->setChecked(!items.at(i)->isChecked()); + } + _aUseFlatButtons->setChecked(_flat); + } +} + +void UPlotLegend::addItem(const UPlotCurve * curve) +{ + if(curve) + { + UPlotLegendItem * legendItem = new UPlotLegendItem(curve, this); + legendItem->setAutoDefault(false); + legendItem->setFlat(_flat); + legendItem->setCheckable(true); + legendItem->setChecked(false); + legendItem->setIcon(QIcon(this->createSymbol(curve->pen(), curve->brush()))); + legendItem->setIconSize(QSize(25,20)); + connect(legendItem, SIGNAL(toggled(bool)), this, SLOT(redirectToggled(bool))); + connect(legendItem, SIGNAL(legendItemRemoved(const UPlotCurve *)), this, SLOT(removeLegendItem(const UPlotCurve *))); + + // layout + QHBoxLayout * hLayout = new QHBoxLayout(); + hLayout->addWidget(legendItem); + hLayout->addStretch(0); + hLayout->setMargin(0); + + // add to the legend + ((QVBoxLayout*)this->layout())->insertLayout(this->layout()->count()-1, hLayout); + } +} + +QPixmap UPlotLegend::createSymbol(const QPen & pen, const QBrush & brush) +{ + QPixmap pixmap(50, 50); + pixmap.fill(Qt::transparent); + QPainter painter(&pixmap); + QPen p = pen; + p.setWidthF(4.0); + painter.setPen(p); + painter.drawLine(0.0, 25.0, 50.0, 25.0); + return pixmap; +} + +bool UPlotLegend::remove(const UPlotCurve * curve) +{ + QList items = this->findChildren(); + for(int i=0; icurve() == curve) + { + delete items.at(i); + return true; + } + } + return false; +} + +void UPlotLegend::removeLegendItem(const UPlotCurve * curve) +{ + if(this->remove(curve)) + { + emit legendItemRemoved(curve); + } +} + +void UPlotLegend::contextMenuEvent(QContextMenuEvent * event) +{ + QAction * action = _menu->exec(event->globalPos()); + if(action == _aUseFlatButtons) + { + this->setFlat(_aUseFlatButtons->isChecked()); + } +} + +void UPlotLegend::redirectToggled(bool toggled) +{ + if(sender()) + { + UPlotLegendItem * item = qobject_cast(sender()); + if(item) + { + emit legendItemToggled(item->curve(), _flat?!toggled:toggled); + } + } +} + + + + + + + +UOrientableLabel::UOrientableLabel(const QString & text, Qt::Orientation orientation, QWidget * parent) : + QLabel(text, parent), + _orientation(orientation) +{ +} + +UOrientableLabel::~UOrientableLabel() +{ +} + +QSize UOrientableLabel::sizeHint() const +{ + QSize size = QLabel::sizeHint(); + if (_orientation == Qt::Vertical) + size.transpose(); + return size; + +} + +QSize UOrientableLabel::minimumSizeHint() const +{ + QSize size = QLabel::minimumSizeHint(); + if (_orientation == Qt::Vertical) + size.transpose(); + return size; +} + +void UOrientableLabel::setOrientation(Qt::Orientation orientation) +{ + _orientation = orientation; + switch(orientation) + { + case Qt::Horizontal: + setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); + break; + + case Qt::Vertical: + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum); + break; + } +} + +void UOrientableLabel::paintEvent(QPaintEvent* event) +{ + QPainter p(this); + QRect r = rect(); + switch (_orientation) + { + case Qt::Horizontal: + break; + case Qt::Vertical: + p.rotate(-90); + p.translate(-height(), 0); + QSize size = r.size(); + size.transpose(); + r.setSize(size); + break; + } + p.drawText(r, this->alignment() | (this->wordWrap()?Qt::TextWordWrap:0), this->text()); +} + + + + + + + + + + + + + +UPlot::UPlot(QWidget *parent) : + QWidget(parent), + _maxVisibleItems(-1), + _autoScreenCaptureFormat("png") +{ + this->setupUi(); + this->createActions(); + this->createMenus(); + + // This will update actions + this->showLegend(true); + this->setGraphicsView(false); + this->setMaxVisibleItems(0); + this->showGrid(false); + this->showRefreshRate(false); + this->keepAllData(false); + + for(int i=0; i<4; ++i) + { + _axisMaximums[i] = 0; + _axisMaximumsSet[i] = false; + if(i<2) + { + _fixedAxis[i] = false; + } + } + + _refreshIntervalTime.start(); + _lowestRefreshRate = 99; + _refreshStartTime.start(); + + _penStyleCount = rand() % 10 + 1; // rand 1->10 + _workingDirectory = QDir::homePath(); +} + +UPlot::~UPlot() +{ + _aAutoScreenCapture->setChecked(false); +#if PRINT_DEBUG + ULOGGER_DEBUG("%s", this->title().toStdString().c_str()); +#endif + this->removeCurves(); +} + +void UPlot::setupUi() +{ + _legend = new UPlotLegend(this); + _view = new QGraphicsView(this); + _view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + _view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + _view->setScene(new QGraphicsScene(0,0,0,0,this)); + _view->setStyleSheet( "QGraphicsView { border-style: none; }" ); + _sceneRoot = _view->scene()->addText(""); + _sceneRoot->translate(0,0); + _graphicsViewHolder = new QWidget(this); + _graphicsViewHolder->setMinimumSize(100,100); + _verticalAxis = new UPlotAxis(Qt::Vertical, 0, 1, this); + _horizontalAxis = new UPlotAxis(Qt::Horizontal, 0, 1, this); + _title = new QLabel(""); + _xLabel = new QLabel(""); + _refreshRate = new QLabel(""); + _yLabel = new UOrientableLabel(""); + _yLabel->setOrientation(Qt::Vertical); + _title->setAlignment(Qt::AlignCenter); + _xLabel->setAlignment(Qt::AlignCenter); + _yLabel->setAlignment(Qt::AlignCenter); + _refreshRate->setAlignment(Qt::AlignCenter); + _title->setWordWrap(true); + _xLabel->setWordWrap(true); + _yLabel->setWordWrap(true); + _title->setVisible(false); + _xLabel->setVisible(false); + _yLabel->setVisible(false); + _refreshRate->setVisible(false); + + //layouts + QVBoxLayout * vLayout = new QVBoxLayout(_graphicsViewHolder); + vLayout->setContentsMargins(0,0,0,0); + vLayout->addWidget(_view); + + QGridLayout * grid = new QGridLayout(this); + grid->setContentsMargins(0,0,0,0); + grid->addWidget(_title, 0, 2); + grid->addWidget(_yLabel, 1, 0); + grid->addWidget(_verticalAxis, 1, 1); + grid->addWidget(_refreshRate, 2, 1); + grid->addWidget(_graphicsViewHolder, 1, 2); + grid->setColumnStretch(2, 1); + grid->setRowStretch(1, 1); + grid->addWidget(_horizontalAxis, 2, 2); + grid->addWidget(_xLabel, 3, 2); + grid->addWidget(_legend, 1, 3); + + connect(_legend, SIGNAL(legendItemToggled(const UPlotCurve *, bool)), this, SLOT(showCurve(const UPlotCurve *, bool))); + connect(_legend, SIGNAL(legendItemRemoved(const UPlotCurve *)), this, SLOT(removeCurve(const UPlotCurve *))); +} + +void UPlot::createActions() +{ + _aShowLegend = new QAction(tr("Show legend"), this); + _aShowLegend->setCheckable(true); + _aShowGrid = new QAction(tr("Show grid"), this); + _aShowGrid->setCheckable(true); + _aShowRefreshRate = new QAction(tr("Show refresh rate"), this); + _aShowRefreshRate->setCheckable(true); + _aGraphicsView = new QAction(tr("Graphics view"), this); + _aGraphicsView->setCheckable(true); + _aKeepAllData = new QAction(tr("Keep all data"), this); + _aKeepAllData->setCheckable(true); + _aLimit0 = new QAction(tr("No maximum items shown"), this); + _aLimit10 = new QAction(tr("10"), this); + _aLimit50 = new QAction(tr("50"), this); + _aLimit100 = new QAction(tr("100"), this); + _aLimit500 = new QAction(tr("500"), this); + _aLimit1000 = new QAction(tr("1000"), this); + _aLimitCustom = new QAction(tr(""), this); + _aLimit0->setCheckable(true); + _aLimit10->setCheckable(true); + _aLimit50->setCheckable(true); + _aLimit100->setCheckable(true); + _aLimit500->setCheckable(true); + _aLimit1000->setCheckable(true); + _aLimitCustom->setCheckable(true); + _aLimitCustom->setVisible(false); + _aAddVerticalLine = new QAction(tr("Vertical line..."), this); + _aAddHorizontalLine = new QAction(tr("Horizontal line..."), this); + _aChangeTitle = new QAction(tr("Change title"), this); + _aChangeXLabel = new QAction(tr("Change X label..."), this); + _aChangeYLabel = new QAction(tr("Change Y label..."), this); + _aYLabelVertical = new QAction(tr("Vertical orientation"), this); + _aYLabelVertical->setCheckable(true); + _aYLabelVertical->setChecked(true); + _aSaveFigure = new QAction(tr("Save figure..."), this); + _aAutoScreenCapture = new QAction(tr("Auto screen capture..."), this); + _aAutoScreenCapture->setCheckable(true); + _aClearData = new QAction(tr("Clear data"), this); + + QActionGroup * grpLimit = new QActionGroup(this); + grpLimit->addAction(_aLimit0); + grpLimit->addAction(_aLimit10); + grpLimit->addAction(_aLimit50); + grpLimit->addAction(_aLimit100); + grpLimit->addAction(_aLimit500); + grpLimit->addAction(_aLimit1000); + grpLimit->addAction(_aLimitCustom); + _aLimit0->setChecked(true); +} + +void UPlot::createMenus() +{ + _menu = new QMenu(tr("Plot"), this); + _menu->addAction(_aShowLegend); + _menu->addAction(_aShowGrid); + _menu->addAction(_aShowRefreshRate); + _menu->addAction(_aGraphicsView); + _menu->addAction(_aKeepAllData); + _menu->addSeparator()->setStatusTip(tr("Maximum items shown")); + _menu->addAction(_aLimit0); + _menu->addAction(_aLimit10); + _menu->addAction(_aLimit50); + _menu->addAction(_aLimit100); + _menu->addAction(_aLimit500); + _menu->addAction(_aLimit1000); + _menu->addAction(_aLimitCustom); + _menu->addSeparator(); + QMenu * addLineMenu = _menu->addMenu(tr("Add line")); + addLineMenu->addAction(_aAddHorizontalLine); + addLineMenu->addAction(_aAddVerticalLine); + _menu->addSeparator(); + _menu->addAction(_aChangeTitle); + _menu->addAction(_aChangeXLabel); + QMenu * yLabelMenu = _menu->addMenu(tr("Y label")); + yLabelMenu->addAction(_aChangeYLabel); + yLabelMenu->addAction(_aYLabelVertical); + _menu->addAction(_aSaveFigure); + _menu->addAction(_aAutoScreenCapture); + _menu->addSeparator(); + _menu->addAction(_aClearData); + +} + +UPlotCurve * UPlot::addCurve(const QString & curveName, const QColor & color) +{ + // add curve + UPlotCurve * curve = new UPlotCurve(curveName, this); + if(color.isValid()) + { + curve->setPen(color); + } + else + { + curve->setPen(this->getRandomPenColored()); + } + this->addCurve(curve); + return curve; +} + +bool UPlot::addCurve(UPlotCurve * curve) +{ + if(curve) + { + // only last curve can trigger an update, so disable previous connections + if(!qobject_cast(curve)) + { + for(int i=_curves.size()-1; i>=0; --i) + { + if(!qobject_cast(_curves.at(i))) + { + disconnect(_curves.at(i), SIGNAL(dataChanged(const UPlotCurve *)), this, SLOT(updateAxis())); + break; + } + } + } + + // add curve + _curves.append(curve); + curve->attach(this); // ownership is transferred + this->updateAxis(curve); + curve->setXStart(_axisMaximums[1]); + + connect(curve, SIGNAL(dataChanged(const UPlotCurve *)), this, SLOT(updateAxis())); + + _legend->addItem(curve); + +#if PRINT_DEBUG + ULOGGER_DEBUG("Curve \"%s\" added to plot \"%s\"", curve->name().toStdString().c_str(), this->title().toStdString().c_str()); +#endif + + return true; + } + else + { + ULOGGER_ERROR("The curve is null!"); + } + return false; +} + +QStringList UPlot::curveNames() +{ + QStringList names; + for(QList::iterator iter = _curves.begin(); iter!=_curves.end(); ++iter) + { + if(*iter) + { + names.append((*iter)->name()); + } + } + return names; +} + +bool UPlot::contains(const QString & curveName) +{ + for(QList::iterator iter = _curves.begin(); iter!=_curves.end(); ++iter) + { + if(*iter && (*iter)->name().compare(curveName) == 0) + { + return true; + } + } + return false; +} + +QPen UPlot::getRandomPenColored() +{ + return QPen((Qt::GlobalColor)(_penStyleCount++ % 12 + 7 )); +} + +void UPlot::replot(QPainter * painter) +{ + if(_maxVisibleItems>0) + { + UPlotCurve * c = 0; + int maxItem = 0; + // find the curve with the most items + for(QList::iterator i=_curves.begin(); i!=_curves.end(); ++i) + { + if((*i)->isVisible() && ((UPlotCurve *)(*i))->itemsSize() > maxItem) + { + c = *i; + maxItem = c->itemsSize(); + } + } + if(c && (maxItem-1)/2+1 > _maxVisibleItems) + { + _axisMaximums[0] = c->getItemData((c->itemsSize()-1) -_maxVisibleItems*2).x(); + } + } + + float axis[4] = {0}; + for(int i=0; i<4; ++i) + { + axis[i] = _axisMaximums[i]; + } + + _verticalAxis->setAxis(axis[2], axis[3]); + _horizontalAxis->setAxis(axis[0], axis[1]); + if(_aGraphicsView->isChecked() && !painter) + { + _verticalAxis->update(); + _horizontalAxis->update(); + } + + //ULOGGER_DEBUG("x1=%f, x2=%f, y1=%f, y2=%f", _axisMaximums[0], _axisMaximums[1], _axisMaximums[2], _axisMaximums[3]); + + QRectF newRect(0,0, _graphicsViewHolder->size().width(), _graphicsViewHolder->size().height()); + _view->scene()->setSceneRect(newRect); + float borderHor = (float)_horizontalAxis->border(); + float borderVer = (float)_verticalAxis->border(); + + //grid + qDeleteAll(hGridLines); + hGridLines.clear(); + qDeleteAll(vGridLines); + vGridLines.clear(); + if(_aShowGrid->isChecked()) + { + // TODO make a PlotGrid class ? + float w = newRect.width()-(borderHor*2); + float h = newRect.height()-(borderVer*2); + float stepH = w / float(_horizontalAxis->count()); + float stepV = h / float(_verticalAxis->count()); + QPen pen(Qt::DashLine); + for(float i=0.0f; i*stepV <= h+stepV; i+=5.0f) + { + //horizontal lines + if(!_aGraphicsView->isChecked()) + { + if(painter) + { + painter->drawLine(0, stepV*i+borderVer+0.5f, borderHor, stepV*i+borderVer+0.5f); + painter->save(); + painter->setPen(pen); + painter->drawLine(borderHor, stepV*i+borderVer+0.5f, w+borderHor, stepV*i+borderVer+0.5f); + painter->restore(); + painter->drawLine(w+borderHor, stepV*i+borderVer+0.5f, w+borderHor*2, stepV*i+borderVer+0.5f); + } + } + else + { + hGridLines.append(new QGraphicsLineItem(0, stepV*i+borderVer, borderHor, stepV*i+borderVer, _sceneRoot)); + hGridLines.append(new QGraphicsLineItem(borderHor, stepV*i+borderVer, w+borderHor, stepV*i+borderVer, _sceneRoot)); + hGridLines.last()->setPen(pen); + hGridLines.append(new QGraphicsLineItem(w+borderHor, stepV*i+borderVer, w+borderHor*2, stepV*i+borderVer, _sceneRoot)); + } + } + for(float i=0; i*stepH < w+stepH; i+=5.0f) + { + //vertical lines + if(!_aGraphicsView->isChecked()) + { + if(painter) + { + painter->drawLine(stepH*i+borderHor+0.5f, 0, stepH*i+borderHor+0.5f, borderVer); + painter->save(); + painter->setPen(pen); + painter->drawLine(stepH*i+borderHor+0.5f, borderVer, stepH*i+borderHor+0.5f, h+borderVer); + painter->restore(); + painter->drawLine(stepH*i+borderHor+0.5f, h+borderVer, stepH*i+borderHor+0.5f, h+borderVer*2); + } + } + else + { + vGridLines.append(new QGraphicsLineItem(stepH*i+borderHor, 0, stepH*i+borderHor, borderVer, _sceneRoot)); + vGridLines.append(new QGraphicsLineItem(stepH*i+borderHor, borderVer, stepH*i+borderHor, h+borderVer, _sceneRoot)); + vGridLines.last()->setPen(pen); + vGridLines.append(new QGraphicsLineItem(stepH*i+borderHor, h+borderVer, stepH*i+borderHor, h+borderVer*2, _sceneRoot)); + } + } + } + + // curves + float scaleX = 1; + float scaleY = 1; + float den = 0; + den = axis[1] - axis[0]; + if(den != 0) + { + scaleX = (newRect.width()-(borderHor*2)) / den; + } + den = axis[3] - axis[2]; + if(den != 0) + { + scaleY = (newRect.height()-(borderVer*2)) / den; + } + for(QList::iterator i=_curves.begin(); i!=_curves.end(); ++i) + { + if((*i)->isVisible()) + { + float xDir = 1.0f; + float yDir = -1.0f; + (*i)->update(scaleX, + scaleY, + xDir<0?axis[1]+borderHor/scaleX:-(axis[0]-borderHor/scaleX), + yDir<0?axis[3]+borderVer/scaleY:-(axis[2]-borderVer/scaleY), + xDir, + yDir, + _aKeepAllData->isChecked()); + if(painter) + { + (*i)->draw(painter); + } + } + } + + // Update refresh rate + if(_aShowRefreshRate->isChecked()) + { + int refreshRate = qRound(1000.0f/float(_refreshIntervalTime.restart())); + if(refreshRate > 0 && refreshRate < _lowestRefreshRate) + { + _lowestRefreshRate = refreshRate; + } + // Refresh the label only after each 1000 ms + if(_refreshStartTime.elapsed() > 1000) + { + _refreshRate->setText(QString::number(_lowestRefreshRate)); + _lowestRefreshRate = 99; + _refreshStartTime.start(); + } + } +} + +void UPlot::setFixedXAxis(float x1, float x2) +{ + _fixedAxis[0] = true; + _axisMaximums[0] = x1; + _axisMaximums[1] = x2; +} + +void UPlot::setFixedYAxis(float y1, float y2) +{ + _fixedAxis[1] = true; + _axisMaximums[2] = y1; + _axisMaximums[3] = y2; +} + +void UPlot::updateAxis(const UPlotCurve * curve) +{ + if(curve && curve->isVisible() && curve->itemsSize() && curve->isMinMaxValid()) + { + const QVector & minMax = curve->getMinMax(); + //ULOGGER_DEBUG("x1=%f, x2=%f, y1=%f, y2=%f", minMax[0], minMax[1], minMax[2], minMax[3]); + if(minMax.size() != 4) + { + ULOGGER_ERROR("minMax size != 4 ?!?"); + return; + } + this->updateAxis(minMax[0], minMax[1], minMax[2], minMax[3]); + _aGraphicsView->isChecked()?this->replot(0):this->update(); + } +} + +bool UPlot::updateAxis(float x1, float x2, float y1, float y2) +{ + bool modified = false; + modified = updateAxis(x1,y1); + if(!modified) + { + modified = updateAxis(x2,y2); + } + else + { + updateAxis(x2,y2); + } + return modified; +} + +bool UPlot::updateAxis(float x, float y) +{ + //ULOGGER_DEBUG("x=%f, y=%f", x,y); + bool modified = false; + if(!_fixedAxis[0] && (!_axisMaximumsSet[0] || x < _axisMaximums[0])) + { + _axisMaximums[0] = x; + _axisMaximumsSet[0] = true; + modified = true; + } + + if(!_fixedAxis[0] && (!_axisMaximumsSet[1] || x > _axisMaximums[1])) + { + _axisMaximums[1] = x; + _axisMaximumsSet[1] = true; + modified = true; + } + + if(!_fixedAxis[1] && (!_axisMaximumsSet[2] || y < _axisMaximums[2])) + { + _axisMaximums[2] = y; + _axisMaximumsSet[2] = true; + modified = true; + } + + if(!_fixedAxis[1] && (!_axisMaximumsSet[3] || y > _axisMaximums[3])) + { + _axisMaximums[3] = y; + _axisMaximumsSet[3] = true; + modified = true; + } + + return modified; +} + +void UPlot::updateAxis() +{ + //Reset the axis + for(int i=0; i<4; ++i) + { + if((!_fixedAxis[0] && i<2) || (!_fixedAxis[1] && i>=2)) + { + _axisMaximums[i] = 0; + _axisMaximumsSet[i] = false; + } + } + + for(int i=0; i<_curves.size(); ++i) + { + if(_curves.at(i)->isVisible() && _curves.at(i)->isMinMaxValid()) + { + const QVector & minMax = _curves.at(i)->getMinMax(); + this->updateAxis(minMax[0], minMax[1], minMax[2], minMax[3]); + } + } + + _aGraphicsView->isChecked()?this->replot(0):this->update(); + + this->captureScreen(); +} + +void UPlot::paintEvent(QPaintEvent * event) +{ +#if PRINT_DEBUG + UDEBUG(""); +#endif + if(!_aGraphicsView->isChecked()) + { + QPainter painter(this); + painter.translate(_graphicsViewHolder->pos()); + painter.save(); + painter.setBrush(Qt::white); + painter.setPen(QPen(Qt::NoPen)); + painter.drawRect(_graphicsViewHolder->rect()); + painter.restore(); + + this->replot(&painter); + } + else + { + QWidget::paintEvent(event); + } +} + +void UPlot::resizeEvent(QResizeEvent * event) +{ + if(_aGraphicsView->isChecked()) + { + this->replot(0); + } + QWidget::resizeEvent(event); +} + +void UPlot::contextMenuEvent(QContextMenuEvent * event) +{ + QAction * action = _menu->exec(event->globalPos()); + + if(!action) + { + return; + } + else if(action == _aShowLegend) + { + this->showLegend(_aShowLegend->isChecked()); + } + else if(action == _aShowGrid) + { + this->showGrid(_aShowGrid->isChecked()); + } + else if(action == _aShowRefreshRate) + { + this->showRefreshRate(_aShowRefreshRate->isChecked()); + } + else if(action == _aGraphicsView) + { + this->setGraphicsView(_aGraphicsView->isChecked()); + } + else if(action == _aKeepAllData) + { + this->keepAllData(_aKeepAllData->isChecked()); + } + else if(action == _aLimit0 || + action == _aLimit10 || + action == _aLimit50 || + action == _aLimit100 || + action == _aLimit500 || + action == _aLimit1000 || + action == _aLimitCustom) + { + this->setMaxVisibleItems(action->text().toInt()); + } + else if(action == _aAddVerticalLine || action == _aAddHorizontalLine) + { + bool ok; + QString text = QInputDialog::getText(this, action->text(), tr("New line name :"), QLineEdit::Normal, "", &ok); + while(ok && text.isEmpty()) + { + QMessageBox::warning(this, action->text(), tr("The name is not valid or it is already used in this plot.")); + text = QInputDialog::getText(this, action->text(), tr("New line name :"), QLineEdit::Normal, "", &ok); + } + if(ok) + { + double min = _axisMaximums[2]; + double max = _axisMaximums[3]; + QString axis = "Y"; + if(action == _aAddVerticalLine) + { + min = _axisMaximums[0]; + max = _axisMaximums[1]; + axis = "X"; + } + double value = QInputDialog::getDouble(this, + action->text(), + tr("%1 value (min=%2, max=%3):").arg(axis).arg(min).arg(max), + (min+max)/2, + -2147483647, + 2147483647, + 4, + &ok); + if(ok) + { + if(action == _aAddHorizontalLine) + { + this->addThreshold(text, value, Qt::Horizontal); + } + else + { + this->addThreshold(text, value, Qt::Vertical); + } + } + } + } + else if(action == _aChangeTitle) + { + bool ok; + QString text = _title->text(); + if(text.isEmpty()) + { + text = this->objectName(); + } + text = QInputDialog::getText(this, _aChangeTitle->text(), tr("Title :"), QLineEdit::Normal, text, &ok); + if(ok) + { + this->setTitle(text); + } + } + else if(action == _aChangeXLabel) + { + bool ok; + QString text = QInputDialog::getText(this, _aChangeXLabel->text(), tr("X axis label :"), QLineEdit::Normal, _xLabel->text(), &ok); + if(ok) + { + this->setXLabel(text); + } + } + else if(action == _aChangeYLabel) + { + bool ok; + QString text = QInputDialog::getText(this, _aChangeYLabel->text(), tr("Y axis label :"), QLineEdit::Normal, _yLabel->text(), &ok); + if(ok) + { + this->setYLabel(text, _yLabel->orientation()); + } + } + else if(action == _aYLabelVertical) + { + this->setYLabel(_yLabel->text(), _aYLabelVertical->isChecked()?Qt::Vertical:Qt::Horizontal); + } + else if(action == _aSaveFigure) + { + + QString text; +#ifdef QT_SVG_LIB + text = QFileDialog::getSaveFileName(this, tr("Save figure to ..."), (QDir::homePath() + "/") + this->title() + ".png", "*.png *.xpm *.jpg *.pdf *.svg"); +#else + text = QFileDialog::getSaveFileName(this, tr("Save figure to ..."), (QDir::homePath() + "/") + this->title() + ".png", "*.png *.xpm *.jpg *.pdf"); +#endif + if(!text.isEmpty()) + { + bool flatModified = false; + if(!_legend->isFlat()) + { + _legend->setFlat(true); + flatModified = true; + } + + QPalette p(palette()); + // Set background color to white + QColor c = p.color(QPalette::Background); + p.setColor(QPalette::Background, Qt::white); + setPalette(p); + +#ifdef QT_SVG_LIB + if(QFileInfo(text).suffix().compare("svg") == 0) + { + QSvgGenerator generator; + generator.setFileName(text); + generator.setSize(this->size()); + QPainter painter; + painter.begin(&generator); + this->render(&painter); + painter.end(); + } + else + { +#endif + if(QFileInfo(text).suffix().compare("pdf") == 0) + { + QPrinter printer; + printer.setOutputFormat(QPrinter::PdfFormat); + printer.setOutputFileName(text); + this->render(&printer); + } + else + { + QPixmap figure = QPixmap::grabWidget(this); + figure.save(text); + } +#ifdef QT_SVG_LIB + } +#endif + // revert background color + p.setColor(QPalette::Background, c); + setPalette(p); + + if(flatModified) + { + _legend->setFlat(false); + } + } + } + else if(action == _aAutoScreenCapture) + { + if(_aAutoScreenCapture->isChecked()) + { + this->selectScreenCaptureFormat(); + } + } + else if(action == _aClearData) + { + this->clearData(); + } + else + { + ULOGGER_WARN("Unknown action"); + } +} + +void UPlot::setWorkingDirectory(const QString & workingDirectory) +{ + if(QDir(_workingDirectory).exists()) + { + _workingDirectory = workingDirectory; + } + else + { + ULOGGER_ERROR("The directory \"%s\" doesn't exist", workingDirectory.toStdString().c_str()); + } +} + +void UPlot::captureScreen() +{ + if(!_aAutoScreenCapture->isChecked()) + { + return; + } + QString targetDir = _workingDirectory + "/ScreensCaptured"; + QDir dir; + if(!dir.exists(targetDir)) + { + dir.mkdir(targetDir); + } + targetDir += "/"; + targetDir += this->title().replace(" ", "_"); + if(!dir.exists(targetDir)) + { + dir.mkdir(targetDir); + } + targetDir += "/"; + QString name = (QDateTime::currentDateTime().toString("yyMMddhhmmsszzz") + ".") + _autoScreenCaptureFormat; + QPixmap figure = QPixmap::grabWidget(this); + figure.save(targetDir + name); +} + +void UPlot::selectScreenCaptureFormat() +{ + QStringList items; + items << QString("png") << QString("jpg"); + bool ok; + QString item = QInputDialog::getItem(this, tr("Select format"), tr("Format:"), items, 0, false, &ok); + if(ok && !item.isEmpty()) + { + _autoScreenCaptureFormat = item; + } + this->captureScreen(); +} + +void UPlot::clearData() +{ + for(int i=0; i<_curves.size(); ++i) + { + // Don't clear threshold curves + if(qobject_cast(_curves.at(i)) == 0) + { + _curves.at(i)->clear(); + } + } + _aGraphicsView->isChecked()?this->replot(0):this->update(); +} + +// for convenience... +UPlotCurveThreshold * UPlot::addThreshold(const QString & name, float value, Qt::Orientation orientation) +{ + UPlotCurveThreshold * curve = new UPlotCurveThreshold(name, value, orientation, this); + QPen pen = curve->pen(); + pen.setStyle((Qt::PenStyle)(_penStyleCount++ % 4 + 2)); + curve->setPen(pen); + if(!this->addCurve(curve)) + { + if(curve) + { + delete curve; + } + } + else + { + _aGraphicsView->isChecked()?this->replot(0):this->update(); + } + return curve; +} + +void UPlot::setTitle(const QString & text) +{ + _title->setText(text); + _title->setVisible(!text.isEmpty()); + this->update(); + if(_aGraphicsView->isChecked()) + { + QTimer::singleShot(10, this, SLOT(updateAxis())); + } +} + +void UPlot::setXLabel(const QString & text) +{ + _xLabel->setText(text); + _xLabel->setVisible(!text.isEmpty()); + this->update(); + if(_aGraphicsView->isChecked()) + { + QTimer::singleShot(10, this, SLOT(updateAxis())); + } +} + +void UPlot::setYLabel(const QString & text, Qt::Orientation orientation) +{ + _yLabel->setText(text); + _yLabel->setOrientation(orientation); + _yLabel->setVisible(!text.isEmpty()); + _aYLabelVertical->setChecked(orientation==Qt::Vertical); + this->update(); + if(_aGraphicsView->isChecked()) + { + QTimer::singleShot(10, this, SLOT(updateAxis())); + } +} + +void UPlot::addItem(QGraphicsItem * item) +{ + item->setParentItem(_sceneRoot); +} + +void UPlot::showLegend(bool shown) +{ + _legend->setVisible(shown); + _aShowLegend->setChecked(shown); + this->update(); + if(_aGraphicsView->isChecked()) + { + QTimer::singleShot(10, this, SLOT(updateAxis())); + } +} + +void UPlot::showGrid(bool shown) +{ + _aShowGrid->setChecked(shown); + _aGraphicsView->isChecked()?this->replot(0):this->update(); +} + +void UPlot::showRefreshRate(bool shown) +{ + _aShowRefreshRate->setChecked(shown); + _refreshRate->setVisible(shown); + this->update(); + if(_aGraphicsView->isChecked()) + { + QTimer::singleShot(10, this, SLOT(updateAxis())); + } +} + +void UPlot::setGraphicsView(bool on) +{ + _aGraphicsView->setChecked(on); + _view->setVisible(on); + _aGraphicsView->isChecked()?this->replot(0):this->update(); +} + +void UPlot::keepAllData(bool kept) +{ + _aKeepAllData->setChecked(kept); +} + +void UPlot::setMaxVisibleItems(int maxVisibleItems) +{ + if(maxVisibleItems <= 0) + { + _aLimit0->setChecked(true); + } + else if(maxVisibleItems == 10) + { + _aLimit10->setChecked(true); + } + else if(maxVisibleItems == 50) + { + _aLimit50->setChecked(true); + } + else if(maxVisibleItems == 100) + { + _aLimit100->setChecked(true); + } + else if(maxVisibleItems == 500) + { + _aLimit500->setChecked(true); + } + else if(maxVisibleItems == 1000) + { + _aLimit1000->setChecked(true); + } + else + { + _aLimitCustom->setVisible(true); + _aLimitCustom->setChecked(true); + _aLimitCustom->setText(QString::number(maxVisibleItems)); + } + _maxVisibleItems = maxVisibleItems; + updateAxis(); +} + +QRectF UPlot::sceneRect() const +{ + return _view->sceneRect(); +} + +void UPlot::removeCurves() +{ + QList tmp = _curves; + for(QList::iterator iter=tmp.begin(); iter!=tmp.end(); ++iter) + { + this->removeCurve(*iter); + } + _curves.clear(); +} + +void UPlot::removeCurve(const UPlotCurve * curve) +{ + QList::iterator iter = qFind(_curves.begin(), _curves.end(), curve); +#if PRINT_DEBUG + ULOGGER_DEBUG("Plot=\"%s\" removing curve=\"%s\"", this->objectName().toStdString().c_str(), curve?curve->name().toStdString().c_str():""); +#endif + if(iter!=_curves.end()) + { + UPlotCurve * c = *iter; + c->detach(this); + _curves.erase(iter); + _legend->remove(c); + if(!qobject_cast(c)) + { + // transfer update connection to next curve + for(int i=_curves.size()-1; i>=0; --i) + { + if(!qobject_cast(_curves.at(i))) + { + connect(_curves.at(i), SIGNAL(dataChanged(const UPlotCurve *)), this, SLOT(updateAxis())); + break; + } + } + } + + if(c->parent() == this) + { + delete c; + } + // Update axis + updateAxis(); + } +} + +void UPlot::showCurve(const UPlotCurve * curve, bool shown) +{ + QList::iterator iter = qFind(_curves.begin(), _curves.end(), curve); + if(iter!=_curves.end()) + { + UPlotCurve * value = *iter; + if(value->isVisible() != shown) + { + value->setVisible(shown); + this->updateAxis(); + } + } +} diff --git a/src/utilite/UPlot.h b/src/utilite/UPlot.h new file mode 100644 index 00000000..8df2baad --- /dev/null +++ b/src/utilite/UPlot.h @@ -0,0 +1,598 @@ +// Taken from UtiLite library r185 [www.utilite.googlecode.com] + +/* +* utilite is a cross-platform library with +* useful utilities for fast and small developing. +* Copyright (C) 2010 Mathieu Labbe +* +* utilite is free library: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* utilite is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +*/ + +#ifndef UPLOT_H_ +#define UPLOT_H_ + +//#include "utilite/UtiLiteExp.h" // DLL export/import defines + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class QGraphicsView; +class QGraphicsScene; +class QGraphicsItem; +class QFormLayout; + +/** + * UPlotItem is a QGraphicsEllipseItem and can be inherited to do custom behaviors + * on an hoverEnterEvent() for example. + */ +class UPlotItem : public QGraphicsEllipseItem +{ +public: + /** + * Constructor 1. + */ + UPlotItem(qreal dataX, qreal dataY, qreal width=2); + /** + * Constructor 2. + */ + UPlotItem(const QPointF & data, qreal width=2); + virtual ~UPlotItem(); + +public: + void setNextItem(UPlotItem * nextItem); + void setPreviousItem(UPlotItem * previousItem); + void setData(const QPointF & data); + + UPlotItem * nextItem() const {return _nextItem;} + UPlotItem * previousItem() const {return _previousItem;}; + const QPointF & data() const {return _data;} + +protected: + virtual void hoverEnterEvent(QGraphicsSceneHoverEvent * event); + virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent * event); + virtual void focusInEvent(QFocusEvent * event); + virtual void focusOutEvent(QFocusEvent * event); + virtual void keyReleaseEvent(QKeyEvent * keyEvent); + + virtual void showDescription(bool shown); + +private: + QPointF _data; + QGraphicsTextItem * _text; + UPlotItem * _previousItem; + UPlotItem * _nextItem; +}; + +class UPlot; + +/** + * UPlotCurve is a curve used to hold data shown in a UPlot. + */ +class UPlotCurve : public QObject +{ + Q_OBJECT + +public: + /** + * Constructor 1 + */ + UPlotCurve(const QString & name, QObject * parent = 0); + /** + * Constructor 2 + */ + UPlotCurve(const QString & name, const QVector data, QObject * parent = 0); + /** + * Constructor 3 + */ + UPlotCurve(const QString & name, const QVector & x, const QVector & y, QObject * parent = 0); + virtual ~UPlotCurve(); + + /** + * Get pen. + */ + const QPen & pen() const {return _pen;} + /** + * Get brush. + */ + const QBrush & brush() const {return _brush;} + + /** + * Set pen. + */ + void setPen(const QPen & pen); + /** + * Set brush. + */ + void setBrush(const QBrush & brush); + + /** + * Get name. + */ + QString name() const {return _name;} + /** + * Get the number of items in the curve (dot + line items). + */ + int itemsSize() const; + QPointF getItemData(int index); + bool isVisible() const {return _visible;} + void setData(QVector & data); // take the ownership + void setData(const QVector & x, const QVector & y); + void setData(const std::vector & x, const std::vector & y); + void setData(const QVector & y); + void setData(const std::vector & y); + void getData(QVector & x, QVector & y) const; // only call in Qt MainThread + void draw(QPainter * painter); + const QVector & getMinMax() const {return _minMax;} + +public slots: + /** + * + * Clear curve's values. + */ + virtual void clear(); + /** + * + * Show or hide the curve. + */ + void setVisible(bool visible); + /** + * + * Set increment of the x values (when auto-increment is used). + */ + void setXIncrement(float increment); + /** + * + * Set starting x value (when auto-increment is used). + */ + void setXStart(float val); + /** + * + * Add a single value, using a custom UPlotItem. + */ + void addValue(UPlotItem * data); // take the ownership + /** + * + * Add a single value y, x is auto-incremented by the increment set with setXIncrement(). + * @see setXStart() + */ + void addValue(float y); + /** + * + * Add a single value y at x. + */ + void addValue(float x, float y); + /** + * + * For convenience... + * Add a single value y, x is auto-incremented by the increment set with setXIncrement(). + * @see setXStart() + */ + void addValue(const QString & y); + /** + * + * For convenience... + * Add multiple values, using custom UPlotItem. + */ + void addValues(QVector & data); // take the ownership + /** + * + * Add multiple values y at x. Vectors must have the same size. + */ + void addValues(const QVector & xs, const QVector & ys); + /** + * + * Add multiple values y, x is auto-incremented by the increment set with setXIncrement(). + * @see setXStart() + */ + void addValues(const QVector & ys); + void addValues(const QVector & ys); // for convenience + /** + * + * Add multiple values y, x is auto-incremented by the increment set with setXIncrement(). + * @see setXStart() + */ + void addValues(const std::vector & ys); // for convenience + void addValues(const std::vector & ys); // for convenience + +signals: + /** + * + * emitted when data is changed. + */ + void dataChanged(const UPlotCurve *); + +protected: + friend class UPlot; + void attach(UPlot * plot); + void detach(UPlot * plot); + void updateMinMax(); + int removeItem(int index); + void _addValue(UPlotItem * data);; + virtual bool isMinMaxValid() const {return _minMax.size();} + virtual void update(float scaleX, float scaleY, float offsetX, float offsetY, float xDir, float yDir, bool allDataKept); + QList _items; + UPlot * _plot; + +private: + void removeItem(UPlotItem * item); + +private: + QString _name; + QPen _pen; + QBrush _brush; + float _xIncrement; + float _xStart; + bool _visible; + bool _valuesShown; + QVector _minMax; // minX, maxX, minY, maxY +}; + + +/** + * A special UPlotCurve that shows as a line at the specified value, spanning all the UPlot. + */ +class UPlotCurveThreshold : public UPlotCurve +{ + Q_OBJECT + +public: + /** + * Constructor. + */ + UPlotCurveThreshold(const QString & name, float thesholdValue, Qt::Orientation orientation = Qt::Horizontal, QObject * parent = 0); + virtual ~UPlotCurveThreshold(); + +public slots: + /** + * Set threshold value. + */ + void setThreshold(float threshold); + /** + * Set orientation (Qt::Horizontal or Qt::Vertical). + */ + void setOrientation(Qt::Orientation orientation); + +protected: + friend class UPlot; + virtual void update(float scaleX, float scaleY, float offsetX, float offsetY, float xDir, float yDir, bool allDataKept); + virtual bool isMinMaxValid() const {return false;} + +private: + Qt::Orientation _orientation; +}; + +/** + * The UPlot axis object. + */ +class UPlotAxis : public QWidget +{ +public: + /** + * Constructor. + */ + UPlotAxis(Qt::Orientation orientation = Qt::Horizontal, float min=0, float max=1, QWidget * parent = 0); + virtual ~UPlotAxis(); + +public: + /** + * Set axis minimum and maximum values, compute the resulting + * intervals depending on the size of the axis. + */ + void setAxis(float & min, float & max); + /** + * Size of the border between the first line and the beginning of the widget. + */ + int border() const {return _border;} + /** + * Interval step value. + */ + int step() const {return _step;} + /** + * Number of intervals. + */ + int count() const {return _count;} + /** + * Reverse the axis (for vertical :bottom->up, for horizontal :right->left) + */ + void setReversed(bool reversed); // Vertical :bottom->up, horizontal :right->left + +protected: + virtual void paintEvent(QPaintEvent * event); + +private: + Qt::Orientation _orientation; + float _min; + float _max; + int _count; + int _step; + bool _reversed; + int _gradMaxDigits; + int _border; +}; + + +/** + * The UPlot legend item. Used internally by UPlot. + */ +class UPlotLegendItem : public QPushButton +{ + Q_OBJECT + +public: + /** + * Constructor. + */ + UPlotLegendItem(const UPlotCurve * curve, QWidget * parent = 0); + virtual ~UPlotLegendItem(); + const UPlotCurve * curve() const {return _curve;} + +signals: + void legendItemRemoved(const UPlotCurve *); + +protected: + virtual void contextMenuEvent(QContextMenuEvent * event); + +private: + const UPlotCurve * _curve; + QMenu * _menu; + QAction * _aChangeText; + QAction * _aResetText; + QAction * _aRemoveCurve; + QAction * _aCopyToClipboard; +}; + +/** + * The UPlot legend. Used internally by UPlot. + */ +class UPlotLegend : public QWidget +{ + Q_OBJECT + +public: + /** + * Constructor. + */ + UPlotLegend(QWidget * parent = 0); + virtual ~UPlotLegend(); + + void setFlat(bool on); + bool isFlat() const {return _flat;} + void addItem(const UPlotCurve * curve); + QPixmap createSymbol(const QPen & pen, const QBrush & brush); + bool remove(const UPlotCurve * curve); + +public slots: + void removeLegendItem(const UPlotCurve * curve); + +signals: + void legendItemRemoved(const UPlotCurve * curve); + void legendItemToggled(const UPlotCurve * curve, bool toggled); + +protected: + virtual void contextMenuEvent(QContextMenuEvent * event); + +private slots: + void redirectToggled(bool); + +private: + bool _flat; + QMenu * _menu; + QAction * _aUseFlatButtons; +}; + + +/** + * Orientable QLabel. Inherit QLabel and let you to specify the orientation. + */ +class UOrientableLabel : public QLabel +{ + Q_OBJECT + +public: + /** + * Constructor. + */ + UOrientableLabel(const QString & text, Qt::Orientation orientation = Qt::Horizontal, QWidget * parent = 0); + virtual ~UOrientableLabel(); + /** + * Get orientation. + */ + Qt::Orientation orientation() const {return _orientation;} + /** + * Set orientation (Qt::Vertical or Qt::Horizontal). + */ + void setOrientation(Qt::Orientation orientation); + QSize sizeHint() const; + QSize minimumSizeHint() const; +protected: + virtual void paintEvent(QPaintEvent* event); +private: + Qt::Orientation _orientation; +}; + +/** + * UPlot is a QWidget to create a plot like MATLAB, and + * incrementally add new values like a scope using Qt signals/slots. + * Many customizations can be done at runtime with the right-click menu. + * @image html UPlot.gif + * @image html UPlotMenu.png + * + * Example: + * @code + * #include "utilite/UPlot.h" + * #include + * + * int main(int argc, char * argv[]) + * { + * QApplication app(argc, argv); + * UPlot plot; + * UPlotCurve * curve = plot.addCurve("My curve"); + * float y[10] = {0, 1, 2, 3, -3, -2, -1, 0, 1, 2}; + * curve->addValues(std::vector(y, y+10)); + * plot.showGrid(true); + * plot.setGraphicsView(true); + * plot.show(); + * app.exec(); + * return 0; + * } + * @endcode + * @image html SimplePlot.tiff + * + * + */ +class UPlot : public QWidget +{ + Q_OBJECT + +public: + /** + * Constructor. + */ + UPlot(QWidget * parent = 0); + virtual ~UPlot(); + + /** + * Add a curve. The returned curve doesn't need to be deallocated (UPlot keeps the ownership). + */ + UPlotCurve * addCurve(const QString & curveName, const QColor & color = QColor()); + /** + * Add a curve. Ownership is transferred to UPlot. + * If you add the curve to more than one UPlot, the ownership is transferred + * to the last UPlot. + */ + bool addCurve(UPlotCurve * curve); + /** + * Get all curve names. + */ + QStringList curveNames(); + bool contains(const QString & curveName); + void removeCurves(); + /** + * Add a threshold to the plot. + */ + UPlotCurveThreshold * addThreshold(const QString & name, float value, Qt::Orientation orientation = Qt::Horizontal); + QString title() const {return this->objectName();} + QPen getRandomPenColored(); + void showLegend(bool shown); + void showGrid(bool shown); + void showRefreshRate(bool shown); + void keepAllData(bool kept); + void showXAxis(bool shown) {_horizontalAxis->setVisible(shown);} + void showYAxis(bool shown) {_verticalAxis->setVisible(shown);} + void setVariableXAxis() {_fixedAxis[0] = false;} + void setVariableYAxis() {_fixedAxis[1] = false;} + void setFixedXAxis(float x1, float x2); + void setFixedYAxis(float y1, float y2); + void setMaxVisibleItems(int maxVisibleItems); + void setTitle(const QString & text); + void setXLabel(const QString & text); + void setYLabel(const QString & text, Qt::Orientation orientation = Qt::Vertical); + void setWorkingDirectory(const QString & workingDirectory); + void setGraphicsView(bool on); + QRectF sceneRect() const; + +public slots: + /** + * + * Remove a curve. If UPlot is the parent of the curve, the curve is deleted. + */ + void removeCurve(const UPlotCurve * curve); + void showCurve(const UPlotCurve * curve, bool shown); + void updateAxis(); //reset axis and recompute it with all curves minMax + /** + * + * Clear all curves' data. + */ + void clearData(); + +private slots: + void captureScreen(); + void updateAxis(const UPlotCurve * curve); + +protected: + virtual void contextMenuEvent(QContextMenuEvent * event); + virtual void paintEvent(QPaintEvent * event); + virtual void resizeEvent(QResizeEvent * event); + +private: + friend class UPlotCurve; + void addItem(QGraphicsItem * item); + +private: + void replot(QPainter * painter); + bool updateAxis(float x, float y); + bool updateAxis(float x1, float x2, float y1, float y2); + void setupUi(); + void createActions(); + void createMenus(); + void selectScreenCaptureFormat(); + +private: + UPlotLegend * _legend; + QGraphicsView * _view; + QGraphicsItem * _sceneRoot; + QWidget * _graphicsViewHolder; + float _axisMaximums[4]; // {x1->x2, y1->y2} + bool _axisMaximumsSet[4]; // {x1->x2, y1->y2} + bool _fixedAxis[2]; + UPlotAxis * _verticalAxis; + UPlotAxis * _horizontalAxis; + int _penStyleCount; + int _maxVisibleItems; + QList hGridLines; + QList vGridLines; + QList _curves; + QLabel * _title; + QLabel * _xLabel; + UOrientableLabel * _yLabel; + QLabel * _refreshRate; + QString _workingDirectory; + QTime _refreshIntervalTime; + int _lowestRefreshRate; + QTime _refreshStartTime; + QString _autoScreenCaptureFormat; + + QMenu * _menu; + QAction * _aShowLegend; + QAction * _aShowGrid; + QAction * _aKeepAllData; + QAction * _aLimit0; + QAction * _aLimit10; + QAction * _aLimit50; + QAction * _aLimit100; + QAction * _aLimit500; + QAction * _aLimit1000; + QAction * _aLimitCustom; + QAction * _aAddVerticalLine; + QAction * _aAddHorizontalLine; + QAction * _aChangeTitle; + QAction * _aChangeXLabel; + QAction * _aChangeYLabel; + QAction * _aYLabelVertical; + QAction * _aShowRefreshRate; + QAction * _aSaveFigure; + QAction * _aAutoScreenCapture; + QAction * _aClearData; + QAction * _aGraphicsView; +}; + +#endif /* UPLOT_H_ */