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_ */