/* This file is part of Fast Track. FastTrack 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. FastTrack 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 FastTrack. If not, see . */ #include "interactive.h" #include "ui_interactive.h" #ifndef M_PI #define M_PI 3.14159265358979323846 #endif /** * @class Interactive * * @brief The Interactive widget provides an environment to use the tracking widget in an interactive environment. * * @author Benjamin Gallois * * @version $Revision: 480 $ * * Contact: benjamin.gallois@fasttrack.sh * */ /** * @brief Constructs the interactive object derived from a QMainWindow object. */ Interactive::Interactive(QWidget *parent) : QMainWindow(parent), ui(new Ui::Interactive), video(new VideoReader()), videoStatus(false) { ui->setupUi(this); controls = new RuntimeControls; controls->progressBar = ui->trackingProgressBar; parameterValues = {{QStringLiteral("maxArea"), 0}, {QStringLiteral("minArea"), 0}, {QStringLiteral("spot"), 0}, {QStringLiteral("normDist"), 100.0}, {QStringLiteral("normAngle"), 180.0}, {QStringLiteral("maxDist"), 0}, {QStringLiteral("maxTime"), 10}, {QStringLiteral("normArea"), 0.0}, {QStringLiteral("normPerim"), 0.0}, {QStringLiteral("thresh"), 127}, {QStringLiteral("nBack"), 1}, {QStringLiteral("methBack"), 0}, {QStringLiteral("regBack"), 0}, {QStringLiteral("reg"), 0}, {QStringLiteral("morph"), 8}, {QStringLiteral("morphSize"), 0}, {QStringLiteral("morphType"), 2}, {QStringLiteral("lightBack"), 0}, {QStringLiteral("xTop"), 0}, {QStringLiteral("yTop"), 0}, {QStringLiteral("xBottom"), 0}, {QStringLiteral("yBottom"), 0}, {QStringLiteral("startImage"), 0}, {QStringLiteral("stopImage"), 0}, {QStringLiteral("frame"), 0}, {QStringLiteral("displayMode"), 0}}; ui->interactiveTab->setFocusPolicy(Qt::NoFocus); // MetaType qRegisterMetaType>("QHash"); connect(ui->interactiveTab, &QTabWidget::currentChanged, this, [this](int index) { emit activeViewChanged(index == 1 && replayVisible); }); connect(this, &Interactive::message, this, [](const QString &msg) { QMessageBox msgBox; msgBox.setIcon(QMessageBox::Critical); msgBox.setText(tr("Error")); msgBox.setInformativeText(msg); msgBox.exec(); }); replay = new Replay(this, video); connect(ui->interactiveTab, &QTabWidget::tabCloseRequested, this, [this](int index) { if (index != 0) { setReplayVisible(false); } }); // Events filter to select ROI ui->display->installEventFilter(this); isBackground = false; // Sets a color map colorMap.reserve(1000000); int a, b, c; srand((unsigned int)time(NULL)); for (int j = 0; j < 1000000; ++j) { a = rand() % 255; b = rand() % 255; c = rand() % 255; colorMap.push_back(Point3i(a, b, c)); } // Sets the object counter on top of the display counterLabel = new QLabel(ui->display); // TODO put as QGraphicsItem counterLabel->move(20, 20); } void Interactive::setReplayVisible(bool visible) { if (replayVisible == visible) { return; } replayVisible = visible; if (visible) { ui->interactiveTab->addTab(replay, tr("Replay")); ui->interactiveTab->setCurrentIndex(1); } else { while (ui->interactiveTab->count() > 1) { ui->interactiveTab->removeTab(1); } } emit replayVisibleChanged(visible); } QHash Interactive::parameterState() const { return parameterValues; } void Interactive::setParameterState(const QHash &state) { if (state.isEmpty()) { return; } QScopedValueRollback applying(applyingParameterState, true); for (auto it = state.cbegin(); it != state.cend(); ++it) { parameterValues.insert(it.key(), it.value()); } display(parameterInt(QStringLiteral("frame"))); } Replay *Interactive::replayWidget() const { return replay; } void Interactive::preview() { previewTracking(); } void Interactive::startTracking() { if (QMessageBox::question(this, tr("Confirmation"), tr("You are going to start a full tracking analysis. That can take some time, are you sure?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { track(); } } void Interactive::computeWorkspaceBackground() { computeBackground(); } void Interactive::selectWorkspaceBackground() { selectBackground(); } void Interactive::cropWorkspace() { crop(); } void Interactive::resetWorkspaceCrop() { reset(); } void Interactive::levelWorkspaceParameters() { level(); } bool Interactive::canPreview() const { return trackingAvailable; } bool Interactive::canTrack() const { return trackingAvailable; } bool Interactive::isReplayVisible() const { return replayVisible; } bool Interactive::isReplayActive() const { return replayVisible && ui->interactiveTab->currentIndex() == 1; } bool Interactive::isTimelineEnabled() const { return timelineEnabled; } int Interactive::frameCount() const { return video->isOpened() ? static_cast(video->getImageCount()) : 0; } QList Interactive::informationValues() const { return informationValuesState; } QString Interactive::trackingStatusText() const { return trackingStatusState; } void Interactive::setTrackingAvailable(bool available) { trackingAvailable = available; emit trackingAvailabilityChanged(); } int Interactive::parameterInt(const QString &name) const { return parameterValues.value(name).toInt(); } double Interactive::parameterDouble(const QString &name) const { return parameterValues.value(name).toDouble(); } void Interactive::setParameter(const QString &name, const QVariant &value) { if (parameterValues.value(name) == value) { return; } parameterValues.insert(name, value); notifyParameterStateChanged(); } void Interactive::notifyParameterStateChanged() { if (!applyingParameterState) { emit parameterStateChanged(parameterState()); } } void Interactive::updateProgressEstimate(int value) { controls->progressBar->setValue(value); const int completed = value - controls->progressBar->minimum() + 1; const int total = controls->progressBar->maximum() - controls->progressBar->minimum() + 1; if (completed <= 0 || total <= 0 || !trackingElapsed.isValid()) { return; } const qint64 remainingMs = trackingElapsed.elapsed() * (total - completed) / completed; const qint64 remainingSeconds = qMax(0, remainingMs / 1000); const QString remaining = QStringLiteral("%1:%2") .arg(remainingSeconds / 60, 2, 10, QLatin1Char('0')) .arg(remainingSeconds % 60, 2, 10, QLatin1Char('0')); controls->progressBar->setFormat(tr("%p% - %1 remaining").arg(remaining)); } /** * @brief Asks the path to a folder where an image sequence is stored. Setups the ui and resets the class attributs for a new analysis. Triggered when the open button from the menu bar is clicked. */ void Interactive::openFolder(QString path) { // Resets the class members setReplayVisible(false); videoStatus = false; isBackground = false; memoryDir.clear(); backgroundPath.clear(); background.release(); // Resets the ui ui->display->clear(); video->release(); replay->clear(); parameterValues.insert(QStringLiteral("displayMode"), 0); if (path.isEmpty()) { dir = QFileDialog::getOpenFileName(this, tr("Open File"), memoryDir); } else { dir = path; } QApplication::setOverrideCursor(Qt::WaitCursor); if (dir.length()) { // Finds the image format // Setups the class member try { memoryDir = dir; video->open(dir.toStdString()); setTrackingAvailable(false); trackingStatusState = tr("Compute the background to continue"); parameterValues.insert(QStringLiteral("nBack"), static_cast(video->getImageCount())); parameterValues.insert(QStringLiteral("startImage"), 0); parameterValues.insert(QStringLiteral("stopImage"), static_cast(video->getImageCount())); Mat frame; video->getImage(0, frame); cvtColor(frame, frame, COLOR_GRAY2RGB); originalImageSize.setWidth(frame.cols); originalImageSize.setHeight(frame.rows); cropedImageSize.setWidth(originalImageSize.width()); cropedImageSize.setHeight(originalImageSize.height()); informationValuesState[0] = dir; informationValuesState[1] = QString::number(video->getImageCount()); informationValuesState[2] = QString::number(frame.cols); informationValuesState[3] = QString::number(frame.rows); isBackground = false; reset(); // Load replay replay->loadReplay(dir); if (!replay->trackingData->isEmpty) { setReplayVisible(true); } // Load parameters QFileInfo savingInfo(dir); QString savingFilename = savingInfo.baseName(); QString cfgFile = savingInfo.absolutePath(); if (video->isSequence()) { cfgFile.append(QStringLiteral("/Tracking_Result") + QDir::separator() + "cfg.toml"); } else { cfgFile.append(QStringLiteral("/Tracking_Result_") + savingFilename + QDir::separator() + "cfg.toml"); } if (QFileInfo::exists(cfgFile)) { loadParameters(cfgFile); const QString savedBackground = QFileInfo(cfgFile).absoluteDir().filePath(QStringLiteral("background.pgm")); imread(savedBackground.toStdString(), IMREAD_GRAYSCALE | IMREAD_ANYDEPTH).copyTo(background); if (!background.empty() && background.cols == originalImageSize.width() && background.rows == originalImageSize.height()) { backgroundPath = savedBackground; isBackground = true; setTrackingAvailable(true); trackingStatusState.clear(); } } if (video->isOpened()) { videoStatus = true; emit inputOpened(QFileInfo(dir).absoluteFilePath()); } display(0); ui->display->fitToView(); crop(); } // If an error occurs during the opening, resets the information table and warns the user catch (exception &e) { informationValuesState = {QString(), QStringLiteral("0"), QStringLiteral("0"), QStringLiteral("0"), QStringLiteral("0")}; qWarning() << QString::fromStdString(e.what()) << "occurs during opening of " << dir; emit message(tr("No image found.")); } } QApplication::restoreOverrideCursor(); } /** * @brief Displays the image at index in the image sequence in the ui. * @param[in] index Index of the image to display in the image sequence. * @param[in] scale Optional scale to display. */ void Interactive::display(int index, int scale) { if (!videoStatus || displayInProgress) { return; } QScopedValueRollback displayGuard(displayInProgress, true); UMat frame; if (!video->getImage(index, frame)) { return; } try { // Computes the image with the background subtracted if (parameterInt(QStringLiteral("displayMode")) == 1 && isBackground) { (parameterInt(QStringLiteral("lightBack")) == 0) ? (subtract(background, frame, frame)) : (subtract(frame, background, frame)); cvtColor(frame, frame, COLOR_GRAY2RGB); } // Computes the binary image an applies morphological operations else if (parameterInt(QStringLiteral("displayMode")) == 2 && isBackground) { (parameterInt(QStringLiteral("lightBack")) == 0) ? (subtract(background, frame, frame)) : (subtract(frame, background, frame)); Tracking::binarisation(frame, 'b', parameterInt(QStringLiteral("thresh"))); if (parameterInt(QStringLiteral("morph")) != 8) { const int morphSize = parameterInt(QStringLiteral("morphSize")); Mat element = getStructuringElement(parameterInt(QStringLiteral("morphType")), Size(2 * morphSize + 1, 2 * morphSize + 1), Point(morphSize, morphSize)); morphologyEx(frame, frame, parameterInt(QStringLiteral("morph")), element); } vector> contours; findContours(frame, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE); double min = parameterDouble(QStringLiteral("minArea")); double max = parameterDouble(QStringLiteral("maxArea")); // If too many contours are detected to be displayed without slowdowns, ask the user what to do if (contours.size() > 10000) { if (largeContourDisplayDecision == 0 || contourWarningOpen) { return; } if (largeContourDisplayDecision < 0) { contourWarningOpen = true; auto *warning = new QMessageBox( QMessageBox::Question, tr("Confirmation"), tr("Too many objects detected to be displayed.\nDo you want to display them anyway (the program can be slow)?"), QMessageBox::No | QMessageBox::Yes, this); warning->setAttribute(Qt::WA_DeleteOnClose); connect(warning, &QMessageBox::finished, this, [this, index, scale](int result) { contourWarningOpen = false; largeContourDisplayDecision = (result == QMessageBox::Yes) ? 1 : 0; if (largeContourDisplayDecision == 1) { display(index, scale); } }); warning->open(); return; } } else { // Ask again after the contour count has dropped below the warning // threshold and subsequently crosses it again. largeContourDisplayDecision = -1; } vector> displayContours; vector> rejectedContours; displayContours.reserve(contours.size()); rejectedContours.reserve(contours.size()); for (auto const &a : contours) { double size = contourArea(a); if (size > min && size < max) { displayContours.push_back(a); } else { rejectedContours.push_back(a); } } cvtColor(frame, frame, COLOR_GRAY2RGB); drawContours(frame, displayContours, -1, Scalar(0, 255, 0), FILLED, 8); drawContours(frame, rejectedContours, -1, Scalar(255, 0, 0), FILLED, 8); counterLabel->setText("Objects detected: " + QString::number(displayContours.size())); counterLabel->adjustSize(); } else { cvtColor(frame, frame, COLOR_GRAY2RGB); } // Draws the scale if (scale != 0) { line(frame, Point(20, 20), Point(20 + scale, 20), Scalar(255, 0, 0), 2); } // Crops the image if ((roi.width != 0 || roi.height != 0) && (roi.width != originalImageSize.width() || roi.height != originalImageSize.height())) { frame = frame(roi); } display(frame); } catch (const std::exception &e) { qWarning() << QString::fromStdString(e.what()) << " occurs at image " << index << " display"; emit message(QString::fromStdString(e.what()) + tr(" occurs on image %1.").arg(index)); } catch (...) { qWarning() << "Unknown error occurs at image " << index << " display"; emit message(tr("An error occurs on image %1.").arg(index)); } } /** * @brief This is an overloaded function to display a QImage in the display. */ void Interactive::display(const QImage &image) { ui->display->setImage(image); } /** * @brief This is an overloaded function to display a UMat in the display. * @param[in] image UMat to display. * @param[in] format enum QImage::Format. */ void Interactive::display(const UMat &image, QImage::Format format) { // getMat is needed instead of accessing data directly through u->data because the offset generated by the crop is not applied otherwise. QImage qimage(image.getMat(ACCESS_FAST).data, image.cols, image.rows, static_cast(image.step), format); ui->display->setImage(qimage); } /** * @brief Computes and displays the background image in the display. */ void Interactive::computeBackground() { if (videoStatus) { // Before compute background process int nBack = parameterInt(QStringLiteral("nBack")); int method = parameterInt(QStringLiteral("methBack")); int registrationMethod = parameterInt(QStringLiteral("regBack")); const string videoPath = memoryDir.toStdString(); QApplication::setOverrideCursor(Qt::WaitCursor); this->setEnabled(false); // After compute background process QFutureWatcher *watcher = new QFutureWatcher(); connect(watcher, &QFutureWatcher::finished, this, [this, watcher]() { background = watcher->result().clone(); // Clone needed for Windows otherwise no initial display and ui buggued afterward, why if UMat is a smartpointer? if (!background.empty()) { isBackground = true; // Automatic background type selection based on the image mean int meanValue = int(mean(background)[0]); parameterValues.insert(QStringLiteral("lightBack"), meanValue > 128 ? 0 : 1); ui->interactiveTab->setCurrentIndex(0); setTrackingAvailable(true); trackingStatusState.clear(); display(background, QImage::Format_Grayscale8); } else { isBackground = false; parameterValues.insert(QStringLiteral("displayMode"), 0); setTrackingAvailable(false); } this->setEnabled(true); QApplication::restoreOverrideCursor(); watcher->deleteLater(); }); // Compute background process QFuture future = QtConcurrent::run([=]() { UMat background; try { VideoReader backgroundVideo(videoPath); if (!backgroundVideo.open()) { throw std::runtime_error("Background computation error: the video can not be opened."); } background = Tracking::backgroundExtraction(backgroundVideo, nBack, method, registrationMethod); } catch (const std::runtime_error &e) { qWarning() << QString::fromStdString(e.what()) << "occurs during background computation"; emit message(e.what()); } catch (...) { qWarning() << "Unknown error occurs during background computation"; emit message(tr("An error occurs. Please change the registration method")); } return background; }); watcher->setFuture(future); } } /** * @brief Opens a dialogue to select a background image. */ void Interactive::selectBackground() { QString dir = QFileDialog::getOpenFileName(this, tr("Open Background Image"), memoryDir); if (dir.length()) { backgroundPath = dir; imread(backgroundPath.toStdString(), IMREAD_GRAYSCALE | IMREAD_ANYDEPTH).copyTo(background); if (background.cols == originalImageSize.width() && background.rows == originalImageSize.height()) { isBackground = true; setTrackingAvailable(true); trackingStatusState.clear(); // Automatic background type selection based on image mean int meanValue = int(mean(background)[0]); parameterValues.insert(QStringLiteral("lightBack"), meanValue > 128 ? 0 : 1); display(background, QImage::Format_Grayscale8); } else { isBackground = false; emit message(tr("The width or height of the background image does not match the video width or height.")); } } } /** * @brief Gets all the tracking parameters from the ui and updates the parameter map that will be passed to the tracking object. */ void Interactive::getParameters() { for (auto it = parameterValues.cbegin(); it != parameterValues.cend(); ++it) { parameters.insert(it.key(), it.value().toString()); } parameters.insert(QStringLiteral("xTop"), QString::number(roi.tl().x)); parameters.insert(QStringLiteral("yTop"), QString::number(roi.tl().y)); parameters.insert(QStringLiteral("xBottom"), QString::number(roi.br().x)); parameters.insert(QStringLiteral("yBottom"), QString::number(roi.br().y)); } /** * @brief Does a tracking analysis on a sub-part of the image sequence. */ void Interactive::previewTracking() { if (videoStatus) { const int startImage = parameterInt(QStringLiteral("startImage")); const int stopImage = parameterInt(QStringLiteral("stopImage")); controls->progressBar->setRange(startImage, startImage + stopImage - 1); controls->progressBar->setValue(0); controls->progressBar->setFormat(tr("%p% - estimating...")); trackingElapsed.start(); setTrackingAvailable(false); setReplayVisible(false); replay->clear(); // Avoid mixing 2 subsequent analysy QThread *thread = new QThread; Tracking *tracking = new Tracking(memoryDir.toStdString(), background, startImage, startImage + stopImage); tracking->moveToThread(thread); connect(thread, &QThread::started, tracking, &Tracking::startProcess); connect(tracking, &Tracking::progress, this, &Interactive::updateProgressEstimate); connect(tracking, &Tracking::statistic, this, [this, stopImage](int time) { informationValuesState[4] = QString::number(double(stopImage * 1000) / double(time)); }); connect(tracking, &Tracking::finished, this, [this]() { controls->progressBar->setValue(controls->progressBar->maximum()); controls->progressBar->setFormat(tr("Done")); timelineEnabled = true; emit timelineEnabledChanged(timelineEnabled); setTrackingAvailable(true); replay->loadReplay(dir); setReplayVisible(true); }); connect(tracking, &Tracking::forceFinished, this, [this](const QString &errorMessage) { controls->progressBar->setFormat(tr("Stopped")); timelineEnabled = true; emit timelineEnabledChanged(timelineEnabled); setTrackingAvailable(true); replay->loadReplay(dir); setReplayVisible(true); emit message(errorMessage); }); connect(tracking, &Tracking::forceFinished, thread, &QThread::quit); connect(tracking, &Tracking::forceFinished, tracking, &Tracking::deleteLater); connect(tracking, &Tracking::finished, tracking, &Tracking::deleteLater); connect(thread, &QThread::finished, thread, &QThread::deleteLater); getParameters(); tracking->updatingParameters(parameters); thread->start(); timelineEnabled = false; emit timelineEnabledChanged(timelineEnabled); } } /** * @brief Does a full tracking analysis. */ void Interactive::track() { if (videoStatus) { controls->progressBar->setRange(0, static_cast(video->getImageCount()) - 1); controls->progressBar->setValue(0); controls->progressBar->setFormat(tr("%p% - estimating...")); trackingElapsed.start(); setTrackingAvailable(false); setReplayVisible(false); replay->clear(); // Avoid mixing 2 subsequent analysy QThread *thread = new QThread; Tracking *tracking = new Tracking(memoryDir.toStdString(), background); QSharedPointer> logMap(new QHash); logMap->insert(QStringLiteral("date"), QDateTime::currentDateTime().toString()); logMap->insert(QStringLiteral("path"), dir); tracking->moveToThread(thread); connect(thread, &QThread::started, tracking, &Tracking::startProcess); connect(tracking, &Tracking::progress, this, &Interactive::updateProgressEstimate); connect(tracking, &Tracking::statistic, this, [this, logMap](int time) { informationValuesState[4] = QString::number(double(video->getImageCount() * 1000) / double(time)); logMap->insert(QStringLiteral("time"), QString::number(time)); }); connect(tracking, &Tracking::forceFinished, this, [this, logMap](const QString &errorMessage) { controls->progressBar->setFormat(tr("Stopped")); timelineEnabled = true; emit timelineEnabledChanged(timelineEnabled); setTrackingAvailable(true); replay->loadReplay(dir); setReplayVisible(true); logMap->insert(QStringLiteral("status"), errorMessage); emit log(*logMap); emit status(tr("Tracking failed")); emit message(errorMessage); }); connect(tracking, &Tracking::finished, thread, &QThread::quit); connect(tracking, &Tracking::finished, this, [this, logMap]() { controls->progressBar->setValue(controls->progressBar->maximum()); controls->progressBar->setFormat(tr("Done")); timelineEnabled = true; emit timelineEnabledChanged(timelineEnabled); setTrackingAvailable(true); replay->loadReplay(dir); setReplayVisible(true); logMap->insert(QStringLiteral("status"), QStringLiteral("Done")); emit log(*logMap); emit status(tr("Tracking succeeded")); }); connect(tracking, &Tracking::forceFinished, thread, &QThread::quit); connect(tracking, &Tracking::forceFinished, tracking, &Tracking::deleteLater); connect(tracking, &Tracking::finished, tracking, &Tracking::deleteLater); connect(thread, &QThread::finished, thread, &QThread::deleteLater); getParameters(); tracking->updatingParameters(parameters); thread->start(); timelineEnabled = false; emit timelineEnabledChanged(timelineEnabled); } } /** * @brief Manages all the mouse inputs in the display. * @param[in] target Widget to apply the filter. * @param[in] event Describes the mouse event. */ bool Interactive::eventFilter(QObject *target, QEvent *event) { // Mouse event for the display if (target == ui->display) { // Set the first point for the ROI at user click if (event->type() == QEvent::MouseButtonPress) { QMouseEvent *mouseEvent = dynamic_cast(event); if (mouseEvent->buttons() == Qt::LeftButton) { clicks.first = ui->display->mapToScene(mouseEvent->pos()); } } // Sets the second point and draw the roi if (event->type() == QEvent::MouseMove) { QMouseEvent *moveEvent = dynamic_cast(event); if (moveEvent->buttons() == Qt::LeftButton) { clicks.second = ui->display->mapToScene(moveEvent->pos()); // Draws the ROI with QRect roiRect = QRect(clicks.first.x(), clicks.first.y(), clicks.second.x() - clicks.first.x(), clicks.second.y() - clicks.first.y()); ui->display->setRectangle(roiRect); // Updates ui value // Find the rectangle top corner int xTop, yTop, xBottom, yBottom; if (clicks.first.y() < clicks.second.y()) { xTop = clicks.first.x(); yTop = clicks.first.y(); xBottom = clicks.second.x(); yBottom = clicks.second.y(); } else { xBottom = clicks.first.x(); yBottom = clicks.first.y(); xTop = clicks.second.x(); yTop = clicks.second.y(); } // Find the left corner of the rectangle int width = xBottom - xTop; if (width < 0) { xTop += width; xBottom -= width; } // Converts clicks from display widget frame of reference to original image frame of reference parameterValues.insert(QStringLiteral("xTop"), xTop + roi.tl().x); parameterValues.insert(QStringLiteral("yTop"), yTop + roi.tl().y); parameterValues.insert(QStringLiteral("xBottom"), xBottom + roi.tl().x); parameterValues.insert(QStringLiteral("yBottom"), yBottom + roi.tl().y); notifyParameterStateChanged(); } } } return QWidget::eventFilter(target, event); } /** * @brief Crops the image from a rectangle drawed by the user with the mouse on the display. Triggered when the QPushButton ui->crop is clicked. */ void Interactive::crop() { int xTop = parameterInt(QStringLiteral("xTop")); int yTop = parameterInt(QStringLiteral("yTop")); int xBottom = parameterInt(QStringLiteral("xBottom")); int yBottom = parameterInt(QStringLiteral("yBottom")); // Checks for wrong values int width = xBottom - xTop; int height = yBottom - yTop; if (xTop < roi.tl().x) xTop = roi.tl().x; if (yTop < roi.tl().y) yTop = roi.tl().y; if (width > cropedImageSize.width() - xTop + roi.tl().x) width = cropedImageSize.width() - xTop + roi.tl().x; if (height > cropedImageSize.height() - yTop + roi.tl().y) height = cropedImageSize.height() - yTop + roi.tl().y; roi = Rect(xTop, yTop, width, height); cropedImageSize.setWidth(roi.width); cropedImageSize.setHeight(roi.height); informationValuesState[2] = QString::number(roi.width); informationValuesState[3] = QString::number(roi.height); display(parameterInt(QStringLiteral("frame"))); parameterValues.insert(QStringLiteral("xTop"), roi.tl().x); parameterValues.insert(QStringLiteral("yTop"), roi.tl().y); parameterValues.insert(QStringLiteral("xBottom"), roi.br().x); parameterValues.insert(QStringLiteral("yBottom"), roi.br().y); notifyParameterStateChanged(); ui->display->setRectangle(QRect()); } /** * @brief Resets the region of interest. Triggered by the reset button. */ void Interactive::reset() { cropedImageSize.setWidth(originalImageSize.width()); cropedImageSize.setHeight(originalImageSize.height()); parameterValues.insert(QStringLiteral("xTop"), 0); parameterValues.insert(QStringLiteral("yTop"), 0); parameterValues.insert(QStringLiteral("xBottom"), originalImageSize.width()); parameterValues.insert(QStringLiteral("yBottom"), originalImageSize.height()); informationValuesState[2] = QString::number(originalImageSize.width()); informationValuesState[3] = QString::number(originalImageSize.height()); roi = Rect(0, 0, 0, 0); ui->display->setRectangle(QRect()); display(parameterInt(QStringLiteral("frame"))); notifyParameterStateChanged(); } /** * @brief Destructors. */ Interactive::~Interactive() { ui->interactiveTab->setCurrentIndex(0); delete video; delete controls; delete ui; } /** * @brief Level the parameters. */ void Interactive::level() { if (videoStatus) { try { QThread *thread = new QThread; getParameters(); AutoLevel *autolevel = new AutoLevel(memoryDir.toStdString(), background, parameters); autolevel->moveToThread(thread); connect(thread, &QThread::started, autolevel, &AutoLevel::level); connect(autolevel, &AutoLevel::forceFinished, this, [this](const QString &errorMessage) { this->setEnabled(true); emit message(errorMessage); }); connect(autolevel, &AutoLevel::finished, this, [this]() { this->setEnabled(true); }); connect(autolevel, &AutoLevel::levelParametersChanged, this, [this](const QHash &levelParameters) { parameterValues.insert(QStringLiteral("normAngle"), levelParameters.value(QStringLiteral("normAngle"))); parameterValues.insert(QStringLiteral("normDist"), levelParameters.value(QStringLiteral("normDist"))); parameterValues.insert(QStringLiteral("normArea"), levelParameters.value(QStringLiteral("normArea"))); parameterValues.insert(QStringLiteral("normPerim"), levelParameters.value(QStringLiteral("normPerim"))); notifyParameterStateChanged(); QApplication::restoreOverrideCursor(); }); connect(autolevel, &AutoLevel::finished, thread, &QThread::quit); connect(autolevel, &AutoLevel::finished, autolevel, &AutoLevel::deleteLater); connect(autolevel, &AutoLevel::forceFinished, thread, &QThread::quit); connect(autolevel, &AutoLevel::forceFinished, autolevel, &AutoLevel::deleteLater); connect(thread, &QThread::finished, thread, &QThread::deleteLater); QApplication::setOverrideCursor(Qt::WaitCursor); thread->start(); this->setEnabled(false); } catch (const std::exception &e) { qWarning() << QString::fromStdString(e.what()); emit message(QString::fromStdString(e.what())); } } } /** * @brief Reads a parameter file, updates parameters. */ void Interactive::loadParameters(const QString &path) { QFile parameterFile(path); if (parameterFile.open(QIODevice::ReadOnly | QIODevice::Text)) { QTextStream in(¶meterFile); QString line; QStringList parameters; QHash parameterList; while (in.readLineInto(&line)) { if (line.contains(QLatin1String("="))) { parameters = line.split(QStringLiteral("="), Qt::SkipEmptyParts); parameterList.insert(parameters[0].trimmed(), parameters[1].trimmed()); } } for (auto it = parameterList.cbegin(); it != parameterList.cend(); ++it) { parameterValues.insert(it.key(), it.value()); } if (parameterInt(QStringLiteral("xBottom")) == 0) { parameterValues.insert(QStringLiteral("xBottom"), originalImageSize.width()); } if (parameterInt(QStringLiteral("yBottom")) == 0) { parameterValues.insert(QStringLiteral("yBottom"), originalImageSize.height()); } notifyParameterStateChanged(); } parameterFile.close(); }