diff --git a/CMakeLists.txt b/CMakeLists.txt index 3b00b56..0a6bc6d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,16 +41,15 @@ CPM_Finish() biorobotics_config() -set(OpenCV_STATIC ON) +set(OpenCV_STATIC OFF) find_package(OpenCV REQUIRED) find_package(OpenGL REQUIRED) -find_package(Qt5Widgets REQUIRED) -find_package(Qt5OpenGL REQUIRED) +find_package(Qt5Widgets 5.4 REQUIRED) +find_package(Qt5OpenGL 5.4 REQUIRED) find_package(Threads REQUIRED) -find_package(PythonLibs REQUIRED) -set(Boost_USE_STATIC_LIBS ON) +set(Boost_USE_STATIC_LIBS OFF) find_package(Boost COMPONENTS filesystem serialization system REQUIRED) include_directories( @@ -58,7 +57,11 @@ include_directories( SYSTEM ${Qt5Widgets_INCLUDE_DIRS} SYSTEM ${OpenCV_INCLUDE_DIRS} SYSTEM ${Qt5OpenGL_INCLUDE_DIRS} - SYSTEM ${PYTHON_INCLUDE_DIRS} + SYSTEM ${Boost_INCLUDE_DIRS} +) + +link_directories( + ${Boost_LIBRARY_DIRS} ) set(CMAKE_INCLUDE_CURRENT_DIR ON) @@ -67,3 +70,17 @@ add_definitions(${Qt5Widgets_DEFINITIONS}) add_definitions(-DQT_NO_KEYWORDS) add_subdirectory(biotracker) + +# Make all targets (core and GUI) build to a common output directory on windows to work around copying DLLs. +# Note that this must come before including the other projects. +if(MSVC) + set(common_output_directory ${CMAKE_BINARY_DIR}/bin) + foreach(SUBTARGET BioTracker ${CPM_LIBRARIES}) + foreach(OUTPUTCONFIG ${CMAKE_CONFIGURATION_TYPES}) + string(TOUPPER ${OUTPUTCONFIG} OUTPUTCONFIG) + set_target_properties(${SUBTARGET} PROPERTIES RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG} ${common_output_directory}) + set_target_properties(${SUBTARGET} PROPERTIES LIBRARY_OUTPUT_DIRECTORY_${OUTPUTCONFIG} ${common_output_directory}) + set_target_properties(${SUBTARGET} PROPERTIES ARCHIVE_OUTPUT_DIRECTORY_${OUTPUTCONFIG} ${common_output_directory}) + endforeach() + endforeach() +endif() diff --git a/README.md b/README.md index 89f8493..6baecea 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ This repository contains the GUI frontend for the [BioTracker](https://github.co ## Dependencies ### Unix -You can find an up-to-date list of [dependencies](https://github.com/BioroboticsLab/DockerFiles/blob/master/biotracker/dependencies.sh) +You can find an up-to-date list of [dependencies](https://github.com/BioroboticsLab/DockerFiles/blob/master/ubuntu-15.10-biotracker/dependencies.sh) needed to build the BioTracker in our docker repository. ### OSX diff --git a/biotracker/AlgorithmSelectionWidget.h b/biotracker/AlgorithmSelectionWidget.h index da266ee..90e6bf3 100644 --- a/biotracker/AlgorithmSelectionWidget.h +++ b/biotracker/AlgorithmSelectionWidget.h @@ -6,8 +6,6 @@ #include "ui_AlgorithmSelectionWidget.h" #include "biotracker/util/QtRaiiWrapper.hpp" -class Facade; - namespace BioTracker { namespace Gui { @@ -34,7 +32,8 @@ class AlgorithmSelectionWidget : public QWidget { void initConnects(); private Q_SLOTS: - void trackingAlgorithmSelected(const QString& name); + void trackingAlgorithmSelected(const QString &name); + void enableDisableTracking(bool state); }; } diff --git a/biotracker/CMakeLists.txt b/biotracker/CMakeLists.txt index c5e0f6d..7ce3bda 100644 --- a/biotracker/CMakeLists.txt +++ b/biotracker/CMakeLists.txt @@ -11,6 +11,13 @@ qt5_wrap_ui(UI_HEADERS ${ui}) add_executable(BioTracker ${src} ${hdr} ${UI_RESOURCES} ${UI_HEADERS} ) +# Make this target the default target for the solution. +set_property(DIRECTORY PROPERTY VS_STARTUP_PROJECT "BioTracker") +# Give the debug executable a different name on windows as they are build +# into the same directory. +if(MSVC) + set_target_properties(BioTracker PROPERTIES OUTPUT_NAME_DEBUG BioTracker-Debug) +endif() include_directories( SYSTEM ${CMAKE_CURRENT_BINARY_DIR}) target_link_libraries(BioTracker Qt5::Widgets diff --git a/biotracker/Gui.h b/biotracker/Gui.h index 3f6849b..0db7c7e 100644 --- a/biotracker/Gui.h +++ b/biotracker/Gui.h @@ -3,12 +3,9 @@ #include #include "biotracker/BioTrackerApp.h" -#include "biotracker/ZmqTrackingAlgorithm.h" #include "MainWindow.h" -#include "biotracker/util/QOpenGLContextWrapper.h" - namespace BioTracker { namespace Gui { @@ -23,6 +20,10 @@ class Gui : public QObject { void initConnects(); QStringList browse(const QString &title, const QString &filter); + boost::optional> getOpenFiles(); + boost::optional getFileHash(std::string const& filename, const size_t numFiles) const; + std::vector getFilenamesFromPaths(std::vector const& paths) const; + private Q_SLOTS: void browseVideo(); void browsePictures(); diff --git a/biotracker/MainWindow.h b/biotracker/MainWindow.h index 927489e..f94577e 100644 --- a/biotracker/MainWindow.h +++ b/biotracker/MainWindow.h @@ -9,8 +9,8 @@ #include "VideoControlWidget.h" #include "OpenCameraDialog.h" #include "VideoView.h" + #include "biotracker/util/QtRaiiWrapper.hpp" -#include "biotracker/util/QOpenGLContextWrapper.h" #include "biotracker/util/stringTools.h" namespace BioTracker { @@ -49,14 +49,13 @@ class MainWindow : public QMainWindow { * handles the showing of filenames in statusbar */ void frameCalculated(const size_t frameNumber, - const std::string filename, const double currentFps); + const std::string filename, const double currentFps); /** * @brief trackerSelected * gets called whenever a new tracker is selected - * @param tracker */ - void trackerSelected(std::shared_ptr tracker); + void trackerSelected(std::shared_ptr tracker); private: MainWindowUi m_ui; @@ -67,14 +66,9 @@ class MainWindow : public QMainWindow { OpenCameraDialog m_openCameraDialog; QVBoxLayout m_tools; - /** - * @brief lastToolsWidget - * We need to store a reference to the last used widget so we can - * remove it upon selecting a new algorithm - */ - std::shared_ptr m_lastToolsWidget; - void initalizeVideoView(Core::BioTrackerApp &biotracker); + + void closeEvent(QCloseEvent *event) override; }; diff --git a/biotracker/NotificationWidget.h b/biotracker/NotificationWidget.h index f2d8833..755e6c6 100644 --- a/biotracker/NotificationWidget.h +++ b/biotracker/NotificationWidget.h @@ -25,7 +25,7 @@ class NotificationWidget : public QWidget { * @brief notify * Status messages for the user interface */ - void notify(const std::string &message, const MSGS::MTYPE type); + void notify(const std::string &message, const Core::MessageType type); diff --git a/biotracker/SpinBoxWithSlider.h b/biotracker/SpinBoxWithSlider.h deleted file mode 100644 index add6b91..0000000 --- a/biotracker/SpinBoxWithSlider.h +++ /dev/null @@ -1,116 +0,0 @@ -/* - * SpinBoxWithSlider.h - * - * Created on: Nov 14, 2014 - * Author: tobias - */ - -#ifndef SPINBOXWITHSLIDER_H_ -#define SPINBOXWITHSLIDER_H_ - -#include -#include -#include -#include -#include -#include -#include - - -class SteppedSpinBox: public QSpinBox { - Q_OBJECT - public: - SteppedSpinBox(QWidget *p = nullptr); - - protected: - virtual int valueFromText(const QString &text) const override; -}; - - -class SpinBoxWithSlider: public QWidget { - Q_OBJECT - public: - /** - * Integer Spinbox + Slider. - * representable values: {min, min + step_size, min + 2 * step_size, ..., max - step_size, max} - * Default (step_size = 1): {min, min + 1, min + 2, ..., max - 1, max} - * - * @param min lower bound - * @param max upper bound, max >= min && max == min + n * step_size; \f$n \in \mathbb{N}\f$ - * @param start_value min <= start_value <= max && start_value == min + n * step_size; \f$n \in \mathbb{N}\f$ - * @param step_size >= 1 - */ - explicit SpinBoxWithSlider(QWidget *parent, const QString &name, int min, - int max, int start_value, int step_size = 1); - explicit SpinBoxWithSlider(QWidget *parent, QFormLayout *layout, - const QString &name, int min, int max, int start_value, int step_size = 1); - - SpinBoxWithSlider(const SpinBoxWithSlider &) = delete; - SpinBoxWithSlider &operator=(const SpinBoxWithSlider &) = delete; - - int value() const { - return m_spinbox.value(); - } - - void setValue(const int value) { - m_spinbox.setValue(value); - } - - virtual ~SpinBoxWithSlider() override = default; - - private Q_SLOTS: - void SpinBoxValueChanged(int val); - void SliderValueChanged(int value); - - Q_SIGNALS: - void valueChanged(int i); - - private: - int m_step_size; - QHBoxLayout m_layout; - QLabel m_name; - QSlider m_slider; - SteppedSpinBox m_spinbox; -}; - - -class DoubleSpinBoxWithSlider: public QWidget { - Q_OBJECT - public: - /** - * double Spinbox + Slider. - * - * @param precision : decimal places - */ - explicit DoubleSpinBoxWithSlider(QWidget *parent, const QString &name, - double min, double max, double start_value, int precision = 1); - explicit DoubleSpinBoxWithSlider(QWidget *parent, QFormLayout *layout, - const QString &name, double min, double max, double start_value, - int precision = 1); - - DoubleSpinBoxWithSlider(const DoubleSpinBoxWithSlider &) = delete; - DoubleSpinBoxWithSlider &operator=(const DoubleSpinBoxWithSlider &) = delete; - - double value() const { - return m_spinbox.value(); - } - - virtual ~DoubleSpinBoxWithSlider() override = default; - - private Q_SLOTS: - void SpinBoxValueChanged(double val); - void SliderValueChanged(int value); - - Q_SIGNALS: - void valueChanged(double i); - - private: - int m_factor; - QHBoxLayout m_layout; - QLabel m_name; - QSlider m_slider; - QDoubleSpinBox m_spinbox; -}; - - -#endif /* SPINBOXWITHSLIDER_H_ */ diff --git a/biotracker/VideoControlWidget.h b/biotracker/VideoControlWidget.h index 85482e1..871634b 100644 --- a/biotracker/VideoControlWidget.h +++ b/biotracker/VideoControlWidget.h @@ -60,6 +60,9 @@ class VideoControlWidget : public QWidget { void videoSliderReleased(); void videoSliderPressed(); void speedSliderValueChanged(int value); + void viewChanged(QString); + void onRequestRepaint(); + void registerViews(const std::vector view); }; } diff --git a/biotracker/VideoView.h b/biotracker/VideoView.h index 161326b..16d6dba 100644 --- a/biotracker/VideoView.h +++ b/biotracker/VideoView.h @@ -34,43 +34,21 @@ class VideoView : public QOpenGLWidget, protected QOpenGLFunctions { return m_currentMode; } - Core::TextureObject &getTexture() { - return m_texture; - } - bool isZoomed(); -public Q_SLOTS: + void setView(Core::TrackingAlgorithm::View v) { + m_view = v; + update(); + } + + public Q_SLOTS: void setMode(const Mode mode); void fitToWindow(); - void initialPaint(); -private Q_SLOTS: + private Q_SLOTS: void handleLoggedMessage(const QOpenGLDebugMessage &debugMessage); private: - /** - * @brief Used to store mouse cursor offsets while panning. - */ - struct CurrentPanState { - QPointF lastPos; - - CurrentPanState(const QPointF lastPos) - : lastPos(lastPos) { - } - }; - - /** - * @brief Stores the current zoom and pan offsets. While panning, panState stores the last mouse cursor position. - */ - struct PanZoomState { - float zoomFactor = 0.f; - float panX = 0.f; - float panY = 0.f; - bool isChanged = false; - - boost::optional panState; - }; QOpenGLDebugLogger m_openGLLogger; @@ -83,31 +61,24 @@ private Q_SLOTS: /** * @brief Pan/Zoom state variables. */ - PanZoomState m_panZoomState; + BioTracker::Core::PanZoomState m_panZoomState; /** * @brief Ratio of widget size and image size */ float m_screenPicRatio; - /** - * @brief Wrapper for the OpenGL texture. Also contains the original image as opencv matrix. - */ - Core::TextureObject m_texture; - Core::BioTrackerApp &m_biotracker; + Core::TrackingAlgorithm::View m_view; + /** * @brief m_painter */ QPainter m_painter; bool m_firstAttempt; - void initializeGL() override; - void resizeGL(int w, int h) override; - void resizeEvent(QResizeEvent *event) override; - void paintEvent(QPaintEvent *event) override; - + void paintGL() override; /** * @brief unprojectScreenPos @@ -116,22 +87,11 @@ private Q_SLOTS: */ QPoint unprojectScreenPos(QPoint mouseCoords); - /** - * @brief projectPicturePos - * @param imageCoords coordinates relative to image - * @return coordinates relative to window - */ - QPoint projectPicturePos(QPoint imageCoords); - void keyPressEvent(QKeyEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; void wheelEvent(QWheelEvent *e) override; - - void directPaint(const size_t w, const size_t h, const bool fitToWindow); -private Q_SLOTS: - void firstPaint(); }; } } diff --git a/biotracker/src/AlgorithmSelectionWidget.cpp b/biotracker/src/AlgorithmSelectionWidget.cpp index ea38c70..b6461fa 100644 --- a/biotracker/src/AlgorithmSelectionWidget.cpp +++ b/biotracker/src/AlgorithmSelectionWidget.cpp @@ -20,10 +20,12 @@ AlgorithmSelectionWidget::AlgorithmSelectionWidget(QWidget *parent, QComboBox::InsertPolicy::InsertAlphabetically); } -void AlgorithmSelectionWidget::addTrackingAlgorithm(const Core::TrackerType - type) { - m_ui.cb_algorithms->addItem(QString::fromStdString( - m_biotracker.getRegistry().getStringByType().at(type))); +void AlgorithmSelectionWidget::addTrackingAlgorithm(const Core::TrackerType type) { + const QString trackerName = QString::fromStdString( + m_biotracker.getRegistry().getStringByType().at(type)); + m_ui.cb_algorithms->addItem(trackerName); + const int index = m_ui.cb_algorithms->findText(trackerName); + m_ui.cb_algorithms->setCurrentIndex(index); } void AlgorithmSelectionWidget::initAlgorithmList() { @@ -44,10 +46,36 @@ void AlgorithmSelectionWidget::initConnects() { this, &AlgorithmSelectionWidget::addTrackingAlgorithm); QObject::connect(m_ui.cb_algorithms, static_cast(&QComboBox::currentIndexChanged), this, &AlgorithmSelectionWidget::trackingAlgorithmSelected); + QObject::connect(m_ui.chk_enableTracking, &QCheckBox::toggled, + this, &AlgorithmSelectionWidget::enableDisableTracking); + + const QString shortcutTrackingKey = QString::fromStdString( + m_biotracker.getSettings().getValueOrDefault + (GuiParam::SHORTCUT_TRACKING, "T")); + auto *shortcutTracking = new QShortcut(QKeySequence(shortcutTrackingKey), this); + QObject::connect(shortcutTracking, &QShortcut::activated, m_ui.chk_enableTracking, + &QCheckBox::click); } void AlgorithmSelectionWidget::trackingAlgorithmSelected(const QString &name) { m_biotracker.setTrackingAlgorithm(name.toStdString()); + + // check if we have "any" tracking or not + auto noTrackingStr = QString::fromStdString(m_biotracker.getRegistry().getStringByType().at(Core::NoTracking)); + if (name.compare(noTrackingStr) == 0) { + m_ui.chk_enableTracking->setChecked(true); + m_ui.chk_enableTracking->setEnabled(false); + } else { + m_ui.chk_enableTracking->setEnabled(true); + } +} + +void AlgorithmSelectionWidget::enableDisableTracking(bool checked) { + if (checked) { + m_biotracker.enableTracking(); + } else { + m_biotracker.disableTracking(); + } } } diff --git a/biotracker/src/AlgorithmSelectionWidget.ui b/biotracker/src/AlgorithmSelectionWidget.ui index 8ef92d0..23a6319 100644 --- a/biotracker/src/AlgorithmSelectionWidget.ui +++ b/biotracker/src/AlgorithmSelectionWidget.ui @@ -20,6 +20,21 @@ Form + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + @@ -69,6 +84,19 @@ + + + + false + + + Tracking enabled + + + true + + + diff --git a/biotracker/src/Gui.cpp b/biotracker/src/Gui.cpp index 0035892..9665445 100644 --- a/biotracker/src/Gui.cpp +++ b/biotracker/src/Gui.cpp @@ -12,6 +12,12 @@ #include #include "opencv2/highgui/highgui.hpp" +#include "biotracker/serialization/SerializationData.h" + +#include +#include +#include + namespace BioTracker { namespace Gui { @@ -20,11 +26,12 @@ Gui::Gui() , m_biotracker() , m_mainWindow(m_biotracker) { initConnects(); + m_mainWindow.show(); } void Gui::initConnects() { - //File -> Open Video + // File -> Open Video QObject::connect(m_mainWindow.getUi().actionOpen_Video, &QAction::triggered, this, &Gui::browseVideo); QObject::connect(m_mainWindow.getUi().actionOpen_Picture, &QAction::triggered, @@ -49,7 +56,7 @@ void Gui::initConnects() { &m_mainWindow, &MainWindow::frameCalculated); QObject::connect(&m_biotracker, &Core::BioTrackerApp::trackerSelected, - &m_mainWindow, &MainWindow::trackerSelected); + &m_mainWindow, &MainWindow::trackerSelected); QObject::connect(&m_biotracker, &Core::BioTrackerApp::notify, &m_mainWindow.getNotification(), &NotificationWidget::notify); @@ -59,7 +66,7 @@ void Gui::initConnects() { } void Gui::browseVideo() { - static const QString videoFilter("Video files (*.avi *.wmv *.mp4 *.mkv)"); + static const QString videoFilter("Video files (*.avi *.wmv *.mp4 *.mkv *.mov)"); std::vector files; const QString filename = QFileDialog::getOpenFileName(&m_mainWindow, @@ -93,16 +100,28 @@ void Gui::browseCameras() { QListWidget *cameraListWidget = cameraDialog.getUi().cameraList; cameraListWidget->clear(); + BioTracker::Core::Settings settings = m_biotracker.getSettings(); + BioTracker::Core::TrackerStatus trackerStatus = m_biotracker.getTrackingThread().getStatus(); // Add Item for each camera cv::VideoCapture vcap; - for (uint8_t i = 0; i <= 254; i++) { - vcap = cv::VideoCapture(i); - if (!vcap.isOpened()) { - break; + boost::optional mediaTypeOpt = settings.maybeGetValueOfParam(GuiParam::MEDIA_TYPE); + GuiParam::MediaType mediaType = mediaTypeOpt ? static_cast(*mediaTypeOpt) : GuiParam::MediaType::NoMedia; + boost::optional camIdOpt = settings.maybeGetValueOfParam(CaptureParam::CAP_CAMERA_ID); + int camId = camIdOpt ? *camIdOpt : -1; + // OpenCV does not have an API to list camera devices https://github.com/Itseez/opencv/issues/4269 + for (int i = 0; i <= 254; i++) { + QListWidgetItem* item = new QListWidgetItem(QString("Camera ") + QString::number(static_cast(i))); + if (mediaType == GuiParam::MediaType::Camera && camId == i && trackerStatus != BioTracker::Core::TrackerStatus::NothingLoaded) { + item->setText(item->text() + QString(" (in use)")); + item->setFlags(Qt::NoItemFlags); + } else { + vcap = cv::VideoCapture(i); + if (!vcap.isOpened()) { + break; + } + vcap.release(); } - vcap.release(); - cameraListWidget->addItem(QString("Camera ") + QString::number(static_cast - (i))); + cameraListWidget->addItem(item); } if (cameraDialog.exec() == QDialog::Accepted) { @@ -128,11 +147,161 @@ void Gui::loadTracker() { } void Gui::loadTrackingData() { + static const QString trackingdataFilter("Trackingdata Files (*.tdat)"); + const QString path = QFileDialog::getOpenFileName(&m_mainWindow, "Load trackingdata", "", trackingdataFilter); + if (!path.isEmpty()) { + std::ifstream is(path.toStdString()); + cereal::JSONInputArchive ar(is); + + Core::Serialization::Data sdata; + ar(sdata); + + if (m_biotracker.getTrackingThread().getTrackerType()){ + assert(m_biotracker.getTrackingThread().getTrackerType()); + const std::string trackerType = + Core::Registry::getInstance().getStringByType().at(m_biotracker.getTrackingThread().getTrackerType().get()); + + if (sdata.getType() != trackerType) { + QMessageBox::warning(&m_mainWindow, "Unable to load tracking data", + "Tracker type does not match."); + return; + } + } else { + // try to automatically select the required tracking algorithm + const auto& typeByString = Core::Registry::getInstance().getTypeByString(); + const auto it = typeByString.find(sdata.getType()); + if (it != typeByString.end()) { + m_biotracker.getTrackingThread().setTrackingAlgorithm( + Core::Registry::getInstance().makeNewTracker( + Core::Registry::getInstance().getTypeByString().at(sdata.getType()), m_biotracker.getSettings() + ) + ); + } else { + QMessageBox::warning(&m_mainWindow, "Unable to load tracking data", + "Unknown tracker type."); + return; + } + } + + const boost::optional> currentFiles = getOpenFiles(); + + if (!currentFiles) { + QMessageBox::warning(&m_mainWindow, "Unable to load tracking data", + "No file opened."); + return; + } + + const boost::optional hash = getFileHash(currentFiles.get().front(), currentFiles.get().size()); + + if (!hash) { + QMessageBox::warning(&m_mainWindow, "Unable to load tracking data", + "Could not calculate file hash."); + return; + } + + if (sdata.getFileSha1Hash() != hash.get()) { + auto buttonClicked = QMessageBox::warning(&m_mainWindow, "Unable to load tracking data", + "File hash does not match", QMessageBox::Ok | QMessageBox::Ignore); + if (buttonClicked == QMessageBox::Ok) + return; + } + + m_biotracker.getTrackingThread().loadTrackedObjects(sdata.getTrackedObjects()); + } } void Gui::storeTrackingData() { + if (!m_biotracker.getTrackingThread().getTrackerType()) { + QMessageBox::warning(&m_mainWindow, "Unable to store tracking data", + "No tracker selected."); + return; + } + QFileDialog dialog(&m_mainWindow, "Save tracking data"); + dialog.setFileMode(QFileDialog::AnyFile); + dialog.setAcceptMode(QFileDialog::AcceptSave); + dialog.setDefaultSuffix("tdat"); + dialog.setNameFilter("Data Files (*.tdat)"); + if (dialog.exec()) { + const QStringList filenames = dialog.selectedFiles(); + if (!filenames.empty()) { + assert(m_biotracker.getTrackingThread().getTrackerType()); + + const std::string trackerType = + Core::Registry::getInstance().getStringByType().at(m_biotracker.getTrackingThread().getTrackerType().get()); + const boost::optional> currentFiles = getOpenFiles(); + + if (!currentFiles) { + QMessageBox::warning(&m_mainWindow, "Unable to store tracking data", + "No file opened."); + return; + } + + const boost::optional hash = getFileHash(currentFiles.get().front(), currentFiles.get().size()); + + if (!hash) { + QMessageBox::warning(&m_mainWindow, "Unable to store tracking data", + "Could not calculate file hash."); + return; + } + + Core::Serialization::Data sdata(trackerType, hash.get(), getFilenamesFromPaths(currentFiles.get()), + m_biotracker.getTrackingThread().getTrackedObjects().get()); + + std::ofstream ostream(filenames.first().toStdString(), std::ios::binary); + cereal::JSONOutputArchive archive(ostream); + archive(std::move(sdata)); + } + } +} + +boost::optional> Gui::getOpenFiles() { + Core::Settings settings = m_biotracker.getSettings(); + boost::optional mediaTypeOpt = settings.maybeGetValueOfParam(GuiParam::MEDIA_TYPE); + GuiParam::MediaType mediaType = mediaTypeOpt ? static_cast(*mediaTypeOpt) : GuiParam::MediaType::NoMedia; + + boost::optional> filename; + switch (mediaType) { + case GuiParam::MediaType::Images: + filename = settings.getValueOfParam>(PictureParam::PICTURE_FILES); + break; + case GuiParam::MediaType::Video: + filename = std::vector(); + filename.get().push_back(settings.getValueOfParam(CaptureParam::CAP_VIDEO_FILE)); + break; + default: + return boost::optional>(); + } + + // cap_video_file and picture_file can be set, but empty. therefore, we + // need to check if the parameter actually contains a file name. + if (filename && !filename.get().empty()) return filename; + + return boost::optional>(); +} + +boost::optional Gui::getFileHash(const std::string &filename, const size_t numFiles) const { + QCryptographicHash sha1Generator(QCryptographicHash::Sha1); + QFile file(QString::fromStdString(filename)); + if (file.open(QIODevice::ReadOnly)) { + // calculate hash from first 4096 bytes of file + sha1Generator.addData(file.peek(4096)); + sha1Generator.addData(QByteArray::number(static_cast(numFiles))); + return QString(sha1Generator.result().toHex()).toStdString(); + } + + return boost::optional(); +} + +std::vector Gui::getFilenamesFromPaths(const std::vector &paths) const { + std::vector filenames; + filenames.reserve(paths.size()); + for (std::string const& path : paths) { + const QFileInfo fi(QString::fromStdString(path)); + filenames.push_back(fi.baseName().toStdString()); + } + return filenames; } void Gui::exit() { diff --git a/biotracker/src/MainWindow.cpp b/biotracker/src/MainWindow.cpp index e276a7a..5e88b75 100644 --- a/biotracker/src/MainWindow.cpp +++ b/biotracker/src/MainWindow.cpp @@ -8,8 +8,22 @@ MainWindow::MainWindow(Core::BioTrackerApp &biotracker) , m_algorithmSelection(m_ui.widget_alg, biotracker) , m_notification(m_ui.dockWidgetNotificationContents, biotracker) , m_openCameraDialog(m_ui.centralWidget, biotracker) - , m_tools(m_ui.groupBox_tools){ + , m_tools(m_ui.groupBoxContents) { initalizeVideoView(biotracker); + + { + QFile file(QString::fromStdString(ConfigParam::GEOMETRY_FILE.string())); + if (file.open(QIODevice::ReadOnly)) { + restoreGeometry(file.readAll()); + } + } + + { + QFile file(QString::fromStdString(ConfigParam::STATE_FILE.string())); + if (file.open(QIODevice::ReadOnly)) { + restoreState(file.readAll()); + } + } } void MainWindow::initalizeVideoView(Core::BioTrackerApp &biotracker) { @@ -19,17 +33,38 @@ void MainWindow::initalizeVideoView(Core::BioTrackerApp &biotracker) { biotracker, m_videoView.get()); } -void MainWindow::frameCalculated(const size_t frameNumber, - const std::string filename, const double currentFps) { - setWindowTitle("BioTracker [" + QString::fromStdString(stem_filename(filename)) + "]"); -} +void MainWindow::closeEvent(QCloseEvent *event) { + const auto dialog = QMessageBox::warning( + this, "Exit", "Do you really want to close the application?", + QMessageBox::Yes | QMessageBox::No); + + if (dialog == QMessageBox::Yes) { + { + QFile file(QString::fromStdString(ConfigParam::GEOMETRY_FILE.string())); + if (file.open(QIODevice::WriteOnly)) { + file.write(saveGeometry()); + } + } -void MainWindow::trackerSelected(std::shared_ptr tracker) { - if (m_lastToolsWidget) { - m_tools.removeWidget(m_lastToolsWidget.get()); + { + QFile file(QString::fromStdString(ConfigParam::STATE_FILE.string())); + if (file.open(QIODevice::WriteOnly)) { + file.write(saveState()); + } + } + + QMainWindow::closeEvent(event); + } else { + event->ignore(); } - m_lastToolsWidget = tracker.get()->getToolsWidget(); - m_tools.addWidget(m_lastToolsWidget.get()); +} + +void MainWindow::frameCalculated(const size_t, const std::string filename, const double) { + setWindowTitle("BioTracker [" + QString::fromStdString(Util::stem_filename(filename)) + "]"); +} + +void MainWindow::trackerSelected(std::shared_ptr tracker) { + m_tools.addWidget(tracker->getToolsWidget()); m_ui.groupBox_tools->repaint(); } diff --git a/biotracker/src/MainWindow.ui b/biotracker/src/MainWindow.ui index 8b760c2..f135193 100644 --- a/biotracker/src/MainWindow.ui +++ b/biotracker/src/MainWindow.ui @@ -85,7 +85,7 @@ 3 - + 0 @@ -98,16 +98,22 @@ 150 - - Tracking Area - - - false - - - false - + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + @@ -144,7 +150,7 @@ 0 0 982 - 20 + 27 @@ -174,7 +180,7 @@ 200 - 172 + 207 @@ -204,19 +210,19 @@ - 3 + 2 - 8 + 3 - 8 + 3 - 8 + 3 - 8 + 3 @@ -231,7 +237,7 @@ - + 0 0 @@ -239,6 +245,67 @@ Tools + + false + + + + 3 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + 0 + 0 + + + + QFrame::Plain + + + 0 + + + Qt::ScrollBarAsNeeded + + + Qt::ScrollBarAlwaysOff + + + true + + + + + 0 + 0 + 176 + 525 + + + + + 0 + 0 + + + + + + @@ -290,7 +357,7 @@ - Load Tracker... + L&oad Tracker... diff --git a/biotracker/src/NotificationWidget.cpp b/biotracker/src/NotificationWidget.cpp index 941d161..f289557 100644 --- a/biotracker/src/NotificationWidget.cpp +++ b/biotracker/src/NotificationWidget.cpp @@ -16,14 +16,23 @@ void NotificationWidget::initConnects() { } void NotificationWidget::notify(const std::string &message, - const MSGS::MTYPE type) { + const Core::MessageType type) { + std::stringstream ss; switch (type) { - case MSGS::FILE_OPEN: - m_ui.edit_notification->append(QString(message.c_str())); + case Core::MessageType::FILE_OPEN: + ss << "" << message << ""; + break; + case Core::MessageType::NOTIFICATION: + ss << "" << message << ""; + break; + case Core::MessageType::FAIL: + ss << "" << message << ""; break; default: - ; + assert(false); } + ss << "
"; + m_ui.edit_notification->insertHtml(QString(ss.str().c_str())); } diff --git a/biotracker/src/SpinBoxWithSlider.cpp b/biotracker/src/SpinBoxWithSlider.cpp deleted file mode 100644 index 24737e2..0000000 --- a/biotracker/src/SpinBoxWithSlider.cpp +++ /dev/null @@ -1,141 +0,0 @@ -/* - * SpinBoxWithSlider.cpp - * - * Created on: Nov 14, 2014 - * Author: tobias - */ - -#include "../SpinBoxWithSlider.h" -#include // std::invalid_argument - -int pow(int base, unsigned int exp) { - return exp == 0 ? 1 : base * pow(base, exp - 1); -} - - - -SteppedSpinBox::SteppedSpinBox(QWidget *p) - : QSpinBox(p) { -} - -int SteppedSpinBox::valueFromText(const QString &text) const { - int value = text.toInt(); - value = qMax(value, this->minimum()); - value = qMin(value, this->maximum()); - value -= (value - this->minimum()) % this->singleStep(); - return value; -} - -SpinBoxWithSlider::SpinBoxWithSlider(QWidget *parent, const QString &name, - int min, int max, int start_value, int step_size) - : QWidget(parent) - , m_step_size(step_size) - , m_layout(this) - , m_name(name, this) - , m_slider(this) - , m_spinbox(this) { - { - if (m_step_size < 1) { - throw std::invalid_argument("step isn't strictly positive"); - } - if (!(min < max)) { - throw std::invalid_argument("invalid range"); - } - if (!(min <= start_value && start_value <= max)) { - throw std::invalid_argument("start value isn't in range"); - } - if ((max - min) % m_step_size) { - throw std::invalid_argument("range length isn't a multiple of step size"); - } - if ((start_value - min) % m_step_size) { - throw std::invalid_argument("invalid start value"); - } - } - - m_spinbox.setRange(min, max); - m_spinbox.setValue(start_value); - - m_slider.setOrientation(Qt::Horizontal); - m_slider.setRange(0, (max - min) / m_step_size); - m_slider.setValue((start_value - min) / m_step_size); - - - QObject::connect(&m_spinbox, SIGNAL(valueChanged(int)), - this, SLOT(SpinBoxValueChanged(int))); - QObject::connect(&m_slider, SIGNAL(valueChanged(int)), - this, SLOT(SliderValueChanged(int))); - - m_spinbox.setSingleStep(m_step_size); - - m_layout.setMargin(3); - m_layout.setSpacing(3); - - m_layout.addWidget(&m_name); - m_layout.addWidget(&m_slider); - m_layout.addWidget(&m_spinbox); -} - -SpinBoxWithSlider::SpinBoxWithSlider(QWidget *parent, QFormLayout *layout, - const QString &name, int min, int max, int start_value, int step_size) - : SpinBoxWithSlider(parent, name, min, max, start_value, step_size) { - if (layout) { - layout->addRow(this); - } -} - -void SpinBoxWithSlider::SpinBoxValueChanged(int val) { - m_slider.setValue((val - m_spinbox.minimum()) / m_step_size); - Q_EMIT valueChanged(this->value()); -} -void SpinBoxWithSlider::SliderValueChanged(int value) { - m_spinbox.setValue(m_spinbox.minimum() + value * m_step_size); - Q_EMIT valueChanged(this->value()); -} - -DoubleSpinBoxWithSlider::DoubleSpinBoxWithSlider(QWidget *parent, - const QString &name, double min, double max, double start_value, int precision) - : QWidget(parent) - , m_factor(pow(10, precision)) - , m_layout(this) - , m_name(name, this) - , m_slider(this) - , m_spinbox(this) { - m_spinbox.setRange(min, max); - m_spinbox.setValue(start_value); - m_spinbox.setDecimals(precision); - m_spinbox.setSingleStep(1.0 / m_factor); - - m_slider.setOrientation(Qt::Horizontal); - m_slider.setRange(static_cast(m_factor * min), - static_cast(m_factor * max)); - m_slider.setValue(static_cast(m_factor * start_value)); - - - QObject::connect(&m_spinbox, SIGNAL(valueChanged(double)), - this, SLOT(SpinBoxValueChanged(double))); - QObject::connect(&m_slider, SIGNAL(valueChanged(int)), - this, SLOT(SliderValueChanged(int))); - - m_layout.addWidget(&m_name); - m_layout.addWidget(&m_slider); - m_layout.addWidget(&m_spinbox); -} - -DoubleSpinBoxWithSlider::DoubleSpinBoxWithSlider(QWidget *parent, - QFormLayout *layout, const QString &name, double min, double max, - double start_value, int precision) - : DoubleSpinBoxWithSlider(parent, name, min, max, start_value, precision) { - if (layout) { - layout->addRow(this); - } -} - -void DoubleSpinBoxWithSlider::SpinBoxValueChanged(double val) { - m_slider.setValue(static_cast(val * m_factor)); - Q_EMIT valueChanged(this->value()); -} - -void DoubleSpinBoxWithSlider::SliderValueChanged(int value) { - m_spinbox.setValue(static_cast(value) / m_factor); - Q_EMIT valueChanged(this->value()); -} diff --git a/biotracker/src/VideoControlWidget.cpp b/biotracker/src/VideoControlWidget.cpp index 1581f05..821ca15 100644 --- a/biotracker/src/VideoControlWidget.cpp +++ b/biotracker/src/VideoControlWidget.cpp @@ -17,8 +17,7 @@ VideoControlWidget::VideoControlWidget(QWidget *parent, , m_ui(parent) , m_bioTracker(facade) , m_videoView(videoView) - , m_isPanZoomMode(false) -{ + , m_isPanZoomMode(false) { m_iconPause.addFile(QStringLiteral(":/BioTracker/resources/pause-sign.png"), QSize(), QIcon::Normal, QIcon::Off); m_iconPlay.addFile(QStringLiteral(":/BioTracker/resources/arrow-forward1.png"), @@ -68,7 +67,7 @@ void VideoControlWidget::updateWidgets() { void VideoControlWidget::initShortcuts() { const QString shortcutPanKey = QString::fromStdString( m_bioTracker.getSettings().getValueOrDefault - (GUIPARAM::SHORTCUT_ZOOM,"Z")); + (GuiParam::SHORTCUT_ZOOM,"Z")); const QShortcut *shortcutPan = new QShortcut(QKeySequence(shortcutPanKey), this); QObject::connect(shortcutPan, &QShortcut::activated, m_ui.button_panZoom, @@ -76,7 +75,7 @@ void VideoControlWidget::initShortcuts() { const QString shortcutPlayKey = QString::fromStdString( m_bioTracker.getSettings().getValueOrDefault - (GUIPARAM::SHORTCUT_PLAY,"Space")); + (GuiParam::SHORTCUT_PLAY,"Space")); const QShortcut *shortcutPlay = new QShortcut(QKeySequence(shortcutPlayKey), this); QObject::connect(shortcutPlay, &QShortcut::activated, m_ui.button_playPause, @@ -84,7 +83,7 @@ void VideoControlWidget::initShortcuts() { const QString shortcutNextKey = QString::fromStdString( m_bioTracker.getSettings().getValueOrDefault - (GUIPARAM::SHORTCUT_NEXT,"Right")); + (GuiParam::SHORTCUT_NEXT,"Right")); const QShortcut *shortcutNext = new QShortcut(QKeySequence(shortcutNextKey), this); QObject::connect(shortcutNext, &QShortcut::activated, m_ui.button_nextFrame, @@ -92,7 +91,7 @@ void VideoControlWidget::initShortcuts() { const QString shortcutPrevKey = QString::fromStdString( m_bioTracker.getSettings().getValueOrDefault - (GUIPARAM::SHORTCUT_PREV,"Left")); + (GuiParam::SHORTCUT_PREV,"Left")); const QShortcut *shortcutPrev = new QShortcut(QKeySequence(shortcutPrevKey), this); QObject::connect(shortcutPrev, &QShortcut::activated, m_ui.button_previousFrame, @@ -124,6 +123,16 @@ void VideoControlWidget::initConnects() { // speed slider QObject::connect(m_ui.sld_speed, &QSlider::valueChanged, this, &VideoControlWidget::speedSliderValueChanged); + + QObject::connect(m_ui.comboBoxSelectedView, + static_cast(&QComboBox::currentIndexChanged), + this, &VideoControlWidget::viewChanged); + + QObject::connect(&m_bioTracker, &Core::BioTrackerApp::registerViews, this, + &VideoControlWidget::registerViews); + QObject::connect(&m_bioTracker, &Core::BioTrackerApp::requestPaint, this, + &VideoControlWidget::onRequestRepaint); + } void VideoControlWidget::playPause() { @@ -165,13 +174,11 @@ void VideoControlWidget::fileOpened(const std::string filename, const double targetFps) { (void)filename; // "un-use" filename. FileOpen is a generic event, but we dont // need the filename at this place - m_ui.sld_video->setMaximum(totalFrames - 1); + assert(totalFrames > 0); + m_ui.sld_video->setMaximum(static_cast(totalFrames - 1)); m_ui.fps_label->setText(QString::number(targetFps)); const int fpsAsInt = static_cast(targetFps + 0.5); m_ui.sld_speed->setValue(fpsAsInt); - // refresh - m_videoView->initialPaint(); - } void VideoControlWidget::previousFrame() { @@ -215,22 +222,48 @@ void VideoControlWidget::speedSliderValueChanged(int speed) { } } +void VideoControlWidget::viewChanged(QString n) { + auto view = Core::TrackingAlgorithm::OriginalView; + if (n != "Original") { + view.name = n.toUtf8().constData(); + } + m_videoView->setView(view); +} + +void repaintVideoView(VideoView *videoView) { + videoView->update(); +} + +void VideoControlWidget::onRequestRepaint() { + repaintVideoView(m_videoView); +} + +void VideoControlWidget::registerViews(const std::vector views) { + m_ui.comboBoxSelectedView->clear(); + m_ui.comboBoxSelectedView->addItem("Original"); + for (auto view : views) { + m_ui.comboBoxSelectedView->addItem(QString::fromStdString(view.name)); + } +} + void VideoControlWidget::changeCurrentFrameByEdit() { setFrame(m_ui.sld_video->value()); } void VideoControlWidget::takeScreenshot() { - /* - QString filename = ""; - const std::chrono::system_clock::time_point p = std::chrono::system_clock::now(); - const std::time_t t = std::chrono::system_clock::to_time_t(p); - std::string dateTime = std::ctime(&t); - //ctime adds a newline to the string due to historical reasons - dateTime = dateTime.substr(0, dateTime.size() - 1); - filename.append("/screenshot_").append(QString::fromStdString(dateTime)).append(".png"); - QString filepath = QFileDialog::getSaveFileName(this, tr("Save File"), "/home/"+filename , tr("Images (*.png)")); - ui.videoView->takeScreenshot(filepath); - */ + QString filename; + char buffer[80]; + time_t rawtime; + std::time(&rawtime); + struct tm *timeinfo = localtime(&rawtime); + strftime(buffer, 80, "%d-%m-%Y_%I-%M-%S", timeinfo); + filename.append("/Screenshot_").append(buffer).append(".png"); + QString filepath = QFileDialog::getSaveFileName(this, tr("Save File"), + QDir::homePath().append(filename), + tr("Images (*.png)")); + if (filepath.count()) { + m_bioTracker.getTrackingThread().getTexture().get().save(filepath); + } } void VideoControlWidget::switchPanZoomMode() { @@ -248,18 +281,13 @@ void VideoControlWidget::switchPanZoomMode() { } void VideoControlWidget::frameCalculated(const size_t frameNumber, - const std::string filename, const double currentFps) { + const std::string, const double currentFps) { const bool hasNext = frameNumber < m_bioTracker.getNumFrames() - 1; - if (!m_videoView->isZoomed()) { - // TODO: fix this ugly hack - m_videoView->resize(m_videoView->width() + 1, m_videoView->height()); - m_videoView->resize(m_videoView->width() - 1, m_videoView->height()); - } - m_videoView->update(); + repaintVideoView(m_videoView); - m_ui.sld_video->setValue(frameNumber); + m_ui.sld_video->setValue(static_cast(frameNumber)); m_ui.frame_num_edit->setText(QString::number(frameNumber)); if (currentFps >= 0) { diff --git a/biotracker/src/VideoControlWidget.ui b/biotracker/src/VideoControlWidget.ui index f3b0d80..5cc564c 100644 --- a/biotracker/src/VideoControlWidget.ui +++ b/biotracker/src/VideoControlWidget.ui @@ -6,7 +6,7 @@ 0 0 - 719 + 721 193 @@ -68,6 +68,9 @@ Video Controls + + false + 3 @@ -99,7 +102,7 @@ - + :/BioTracker/resources/arrows-skip-back.png:/BioTracker/resources/arrows-skip-back.png @@ -125,7 +128,7 @@ - + :/BioTracker/resources/arrow-forward1.png:/BioTracker/resources/arrow-forward1.png @@ -154,7 +157,7 @@ - + :/BioTracker/resources/stop.png:/BioTracker/resources/stop.png @@ -183,7 +186,7 @@ - + :/BioTracker/resources/arrows-skip-forward.png:/BioTracker/resources/arrows-skip-forward.png @@ -197,7 +200,7 @@ - false + true @@ -217,7 +220,7 @@ - + :/BioTracker/resources/screenshot.png:/BioTracker/resources/screenshot.png @@ -340,7 +343,7 @@ - + :/BioTracker/resources/panZoom.png:/BioTracker/resources/panZoom.png @@ -502,7 +505,7 @@
- + diff --git a/biotracker/src/VideoView.cpp b/biotracker/src/VideoView.cpp index 5c8d270..c1ba162 100644 --- a/biotracker/src/VideoView.cpp +++ b/biotracker/src/VideoView.cpp @@ -1,8 +1,5 @@ #include "../VideoView.h" -// TODO -// check if m_texture is nullptr - // OS X puts the headers in a different location in the include path than // Windows and Linux, so we need to distinguish between OS X and the other // systems. @@ -12,6 +9,8 @@ #include #endif +#include + namespace BioTracker { namespace Gui { @@ -20,12 +19,11 @@ VideoView::VideoView(QWidget *parent, Core::BioTrackerApp &biotracker) , m_openGLLogger(this) , m_currentMode(Mode::INTERACTION) , m_screenPicRatio(0) - , m_texture(this) , m_biotracker(biotracker) + , m_view(Core::TrackingAlgorithm::OriginalView) , m_painter() , m_firstAttempt(true) { - QSizePolicy sizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - setSizePolicy(sizePolicy); + setFocusPolicy(Qt::FocusPolicy::ClickFocus); } void VideoView::setMode(const VideoView::Mode mode) { @@ -46,161 +44,49 @@ void VideoView::setMode(const VideoView::Mode mode) { void VideoView::fitToWindow() { makeCurrent(); // reset PanZoomState - m_panZoomState = PanZoomState(); - directPaint(width(), height(), true); + m_panZoomState = BioTracker::Core::PanZoomState(); update(); } -/** - This attempts to fix a nasty bug that the very first image is not set - correctly. - TODO: fix this bug CORRECTLY! - * @brief VideoView::initialPaint - */ -void VideoView::initialPaint() { - QTimer::singleShot(20, this, SLOT(firstPaint())); -} - void VideoView::handleLoggedMessage(const QOpenGLDebugMessage &debugMessage) { std::cout << debugMessage.message().toStdString() << std::endl; } -void VideoView::initializeGL() { - makeCurrent(); - initializeOpenGLFunctions(); - - m_openGLLogger.initialize(); // initializes in the current context, i.e. ctx - connect(&m_openGLLogger, &QOpenGLDebugLogger::messageLogged, this, - &VideoView::handleLoggedMessage); - m_openGLLogger.startLogging(); - - glClearColor(0.0f, 0.0f, 0.0f, 1.0f); - resizeGL(width(), height()); - m_biotracker.initializeOpenGL(context(), this->getTexture()); -} - -void VideoView::resizeGL(int width, int height) { - directPaint(width, height, false); -} +void VideoView::paintGL() { + BioTracker::Core::TextureObject const &texture = + m_biotracker.getTrackingThread().getTexture(); -void VideoView::resizeEvent(QResizeEvent *event) { - QOpenGLWidget::resizeEvent(event); - if (isValid()) { - fitToWindow(); - } -} + const size_t width = this->width(); + const size_t height = this->height(); -void VideoView::paintEvent(QPaintEvent *) { - directPaint(this->width(), this->height(), false); - m_biotracker.paint(*this, m_painter); -} + const int imageCols = texture.width(); + const int imageRows = texture.height(); -void VideoView::firstPaint() { + // calculate ratio of screen to displayed image + const float imgRatio = static_cast(imageCols) / imageRows; + const float windowRatio = static_cast(width) / height; - if (m_firstAttempt) { - m_firstAttempt = false; - this->resize(this->width() + 1, this->height()); - QTimer::singleShot(20, this, SLOT(firstPaint())); + if (windowRatio < imgRatio) { + m_screenPicRatio = imageRows / (width / imgRatio); } else { - this->resize(this->width() - 1, this->height()); + m_screenPicRatio = imageCols / (height * imgRatio); } -} - -void VideoView::directPaint(const size_t w, const size_t h, const bool fitToWindow) -{ - makeCurrent(); - if (w == 0 || h == 0) { - return; - } - - glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_ACCUM_BUFFER_BIT|GL_STENCIL_BUFFER_BIT); - - int width = w; - int height = h; - - const int imageCols = m_texture.getImage().cols; - const int imageRows = m_texture.getImage().rows; - - // calculate ratio of screen to displayed image - const float imgRatio = static_cast(imageCols) / imageRows; - const float windowRatio = static_cast(width) / height; - - // create viewport with coordinates matching picture size in pixels - glViewport(0, 0, width, height); - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - if (fitToWindow) { - if (windowRatio < imgRatio) { - m_panZoomState.panY = -((height - (width / imgRatio)) / 2) * - (m_screenPicRatio + m_panZoomState.zoomFactor); - m_panZoomState.panX = 0; - } else { - m_panZoomState.panX = - ((width - (height * imgRatio)) / 2) * - (m_screenPicRatio + m_panZoomState.zoomFactor); - m_panZoomState.panY = 0; - } - } else { - if (windowRatio < imgRatio) { - m_screenPicRatio = imageRows / (width / imgRatio); - } else { - m_screenPicRatio = imageCols / (height * imgRatio); - } - } - - width = static_cast(width * (m_screenPicRatio + - m_panZoomState.zoomFactor)); - height = static_cast(height *(m_screenPicRatio + - m_panZoomState.zoomFactor)); - - - const float left = m_panZoomState.panX; - const float top = m_panZoomState.panY; - const float right = left + width; - const float bottom = top + height; - - glOrtho(left, right, bottom, top, 0.0, 1.0); - glMatrixMode(GL_MODELVIEW); + QPainter painter(this); + m_biotracker.paint(width, height, painter, m_panZoomState, m_view); } QPoint VideoView::unprojectScreenPos(QPoint mouseCoords) { - // variables required to map window coordinates to picture coordinates - GLint viewport[4]; - GLdouble modelview[16]; - GLdouble projection[16]; - GLdouble posX, posY, posZ; - QPoint imageCoord; - - glGetDoublev(GL_MODELVIEW_MATRIX, modelview); - glGetDoublev(GL_PROJECTION_MATRIX, projection); - glGetIntegerv(GL_VIEWPORT, viewport); - gluUnProject(mouseCoords.x(), viewport[3] - mouseCoords.y(), 0, modelview, - projection, viewport, &posX, &posY, &posZ); - imageCoord.setX(static_cast((m_texture.getImage().cols / 2) - posX * - (m_texture.getImage().cols / 2))); - imageCoord.setY(static_cast((m_texture.getImage().rows / 2) - posY * - (m_texture.getImage().rows / 2))); - - return imageCoord; -} - -QPoint VideoView::projectPicturePos(QPoint imageCoords) { - //variables required to map picture coordinates to window coordinates - GLint viewport[4]; - GLdouble modelview[16]; - GLdouble projection[16]; - GLdouble posX, posY, posZ; - QPoint windowCoord; - - glGetDoublev(GL_MODELVIEW_MATRIX, modelview); - glGetDoublev(GL_PROJECTION_MATRIX, projection); - glGetIntegerv(GL_VIEWPORT, viewport); - gluProject(imageCoords.x(), imageCoords.y() , 0, modelview, projection, - viewport, &posX, &posY, &posZ); - windowCoord.setX(static_cast(posX)); - windowCoord.setY(-(static_cast(posY - viewport[3]))); + BioTracker::Core::TextureObject const &texture = + m_biotracker.getTrackingThread().getTexture(); - return windowCoord; + // variables required to map window coordinates to picture coordinates + return BioTracker::Core::ScreenHelper::screenToImageCoords( + m_panZoomState, + texture.width(), texture.height(), + width(), height(), + mouseCoords + ); } void VideoView::keyPressEvent(QKeyEvent *e) { @@ -212,138 +98,118 @@ void VideoView::keyPressEvent(QKeyEvent *e) { void VideoView::mouseMoveEvent(QMouseEvent *e) { switch (m_currentMode) { - case Mode::PANZOOM: { - if (m_panZoomState.panState) { - const QPointF delta = e->localPos() - (*m_panZoomState.panState).lastPos; - (*m_panZoomState.panState).lastPos = e->localPos(); - - m_panZoomState.isChanged = true; - m_panZoomState.panX -= static_cast(delta.x() * (m_screenPicRatio + - m_panZoomState.zoomFactor)); - m_panZoomState.panY -= static_cast(delta.y() * (m_screenPicRatio + - m_panZoomState.zoomFactor)); - - resizeGL(this->width(), this->height()); - update(); - } - break; - } - case Mode::INTERACTION: { - e->accept(); - QPoint p = unprojectScreenPos(e->pos()); - const QPointF localPos(p); - QMouseEvent modifiedEvent(e->type(),localPos,e->screenPos(),e->button(),e->buttons(),e->modifiers()); - m_biotracker.mouseEvent(&modifiedEvent); - break; - } - default: { - assert(false); - break; + case Mode::PANZOOM: { + if (m_panZoomState.panState) { + const QPointF delta = e->localPos() - (*m_panZoomState.panState).lastPos; + (*m_panZoomState.panState).lastPos = e->localPos(); + m_panZoomState.isChanged = true; + m_panZoomState.panX -= static_cast(delta.x()); + m_panZoomState.panY -= static_cast(delta.y()); + update(); } + break; + } + case Mode::INTERACTION: { + e->accept(); + QPoint p = unprojectScreenPos(e->pos()); + const QPointF localPos(p); + QMouseEvent modifiedEvent(e->type(),localPos,e->screenPos(),e->button(),e->buttons(),e->modifiers()); + m_biotracker.mouseEvent(&modifiedEvent); + break; + } + default: { + assert(false); + break; + } } } void VideoView::mousePressEvent(QMouseEvent *e) { switch (m_currentMode) { - case Mode::PANZOOM: { - if (QApplication::keyboardModifiers() == Qt::NoModifier) { - m_panZoomState.isChanged = true; - m_panZoomState.panState = CurrentPanState(e->localPos()); - setCursor(Qt::ClosedHandCursor); - } - if (e->button() == Qt::LeftButton && e->type() == QEvent::MouseButtonDblClick) { - fitToWindow(); - } - break; - } - case Mode::INTERACTION: { - e->accept(); - QPoint p = unprojectScreenPos(e->pos()); - const QPointF localPos(p); - QMouseEvent modifiedEvent(e->type(),localPos,e->screenPos(),e->button(),e->buttons(),e->modifiers()); - m_biotracker.mouseEvent(&modifiedEvent); - break; + case Mode::PANZOOM: { + if (QApplication::keyboardModifiers() == Qt::NoModifier) { + m_panZoomState.isChanged = true; + m_panZoomState.panState = BioTracker::Core::CurrentPanState(e->localPos()); + setCursor(Qt::ClosedHandCursor); } - default: { - assert(false); - break; + if (e->button() == Qt::LeftButton && e->type() == QEvent::MouseButtonDblClick) { + fitToWindow(); } + break; + } + case Mode::INTERACTION: { + e->accept(); + QPoint p = unprojectScreenPos(e->pos()); + const QPointF localPos(p); + QMouseEvent modifiedEvent(e->type(),localPos,e->screenPos(),e->button(),e->buttons(),e->modifiers()); + m_biotracker.mouseEvent(&modifiedEvent); + break; + } + default: { + assert(false); + break; + } } } void VideoView::mouseReleaseEvent(QMouseEvent *e) { switch (m_currentMode) { - case Mode::PANZOOM: { - setCursor(Qt::OpenHandCursor); - m_panZoomState.panState.reset(); - break; - } - case Mode::INTERACTION: { - e->accept(); - QPoint p = unprojectScreenPos(e->pos()); - const QPointF localPos(p); - QMouseEvent modifiedEvent(e->type(),localPos,e->screenPos(),e->button(),e->buttons(),e->modifiers()); - m_biotracker.mouseEvent(&modifiedEvent); - break; - } - default: { - assert(false); - break; - } + case Mode::PANZOOM: { + setCursor(Qt::OpenHandCursor); + m_panZoomState.panState.reset(); + break; + } + case Mode::INTERACTION: { + e->accept(); + QPoint p = unprojectScreenPos(e->pos()); + const QPointF localPos(p); + QMouseEvent modifiedEvent(e->type(),localPos,e->screenPos(),e->button(),e->buttons(),e->modifiers()); + m_biotracker.mouseEvent(&modifiedEvent); + break; + } + default: { + assert(false); + break; + } } } void VideoView::wheelEvent(QWheelEvent *e) { - // The maximum zoom is defined such that one image pixel can never become bigger than the widget size. The zoom - // step size is calculated based on the ratio of widget size and image size and decays exponentially when zooming - // in. - float step = 0.0005f * m_screenPicRatio; - if ((m_panZoomState.zoomFactor / m_screenPicRatio) < 0.f) { - step *= 1.f + (m_panZoomState.zoomFactor / m_screenPicRatio); - } + BioTracker::Core::TextureObject const &texture = + m_biotracker.getTrackingThread().getTexture(); switch (m_currentMode) { - case Mode::PANZOOM: { - if (e->orientation() == Qt::Vertical) { - const QPointF pos = e->posF(); - const int numDegrees = e->delta(); - const float deltaZoom = step * numDegrees; - - m_panZoomState.zoomFactor -= deltaZoom; - m_panZoomState.isChanged = true; - - // offset of mouse cursor from widget center in proportion to widget size - const float propX = width() / 2.f - static_cast(pos.x()); - const float propY = height() / 2.f - static_cast(pos.y()); - - // zoom to center - m_panZoomState.panX += (deltaZoom * width()) / 2; - m_panZoomState.panY += (deltaZoom * height()) / 2; - - // adjust for cursor position - m_panZoomState.panX -= (deltaZoom * propX); - m_panZoomState.panY -= (deltaZoom * propY); - - resizeGL(this->width(), this->height()); - update(); - e->accept(); - } - break; - } - case Mode::INTERACTION: { + case Mode::PANZOOM: { + if (e->orientation() == Qt::Vertical) { + const int numDegrees = e->delta(); + const float deltaZoom = numDegrees; + m_panZoomState = BioTracker::Core::ScreenHelper::zoomTo( + m_panZoomState, + texture.width(), texture.height(), + this->width(), this->height(), + deltaZoom, + e->pos() + ); + update(); e->accept(); - QPoint p = unprojectScreenPos(e->pos()); - const QPointF localPos(p); - QWheelEvent modifiedEvent(e->pos(),localPos,e->pixelDelta(),e->angleDelta(),e->delta(),e->orientation(),e->buttons(),e->modifiers()); - QCoreApplication::sendEvent(QApplication::activeWindow(), &modifiedEvent); - m_biotracker.mouseWheelEvent(&modifiedEvent); - break; - } - default: { - assert(false); - break; } + break; + } + case Mode::INTERACTION: { + e->accept(); + const QPoint p = unprojectScreenPos(e->pos()); + const QPointF localPos(p); + QWheelEvent modifiedEvent(e->pos(),localPos,e->pixelDelta(),e->angleDelta(),e->delta(),e->orientation(),e->buttons(), + e->modifiers()); + QCoreApplication::sendEvent(QApplication::activeWindow(), &modifiedEvent); + m_biotracker.mouseWheelEvent(&modifiedEvent); + break; + } + default: { + assert(false); + break; + } } } diff --git a/biotracker/src/main.cpp b/biotracker/src/main.cpp index e5d7008..b1ceab6 100644 --- a/biotracker/src/main.cpp +++ b/biotracker/src/main.cpp @@ -18,7 +18,8 @@ int main(int argc, char *argv[]) { // meta types qRegisterMetaType("cv::Mat"); - qRegisterMetaType("MSGS::MTYPE"); + qRegisterMetaType("BioTracker::Core::Messages::MessageType"); + qRegisterMetaType("MessageType"); qRegisterMetaType("std::string"); qRegisterMetaType("std::size_t"); qRegisterMetaType("size_t"); @@ -31,8 +32,8 @@ int main(int argc, char *argv[]) { BioTracker::Gui::Gui w; return app.exec(); } else { - static const std::string title = MSGS::SYSTEM::APPLICATION_CANNOT_START; - static const std::string msg = MSGS::SYSTEM::NO_OPENGL; + static const std::string title = BioTracker::Core::Messages::System::APPLICATION_CANNOT_START; + static const std::string msg = BioTracker::Core::Messages::System::NO_OPENGL; QMessageBox::critical(nullptr, QString::fromStdString(title), QString::fromStdString(msg));