vendor: OpenCV 5.0.0 snapshot at 755e50675d97db9b7d449d8bd6b09888646f6c6e

This commit is contained in:
Gitea Mirror Bot
2026-08-22 00:11:13 +08:00
commit 12022378a3
3872 changed files with 2513409 additions and 0 deletions
+46
View File
@@ -0,0 +1,46 @@
---
AccessModifierOffset: -2
ConstructorInitializerIndentWidth: 4
AlignEscapedNewlinesLeft: false
AlignTrailingComments: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AlwaysBreakTemplateDeclarations: false
AlwaysBreakBeforeMultilineStrings: false
BreakBeforeBinaryOperators: false
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
BinPackParameters: true
ColumnLimit: 80
ConstructorInitializerAllOnOneLineOrOnePerLine: false
DerivePointerBinding: false
ExperimentalAutoDetectBinPacking: false
IndentCaseLabels: false
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCSpaceBeforeProtocolList: true
PenaltyBreakBeforeFirstCallParameter: 19
PenaltyBreakComment: 60
PenaltyBreakString: 1000
PenaltyBreakFirstLessLess: 120
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 60
PointerBindsToType: false
SpacesBeforeTrailingComments: 1
Cpp11BracedListStyle: false
Standard: Cpp11
IndentWidth: 8
TabWidth: 8
UseTab: ForIndentation
BreakBeforeBraces: Allman
IndentFunctionDeclarationAfterType: false
SpacesInParentheses: false
SpacesInAngles: false
SpaceInEmptyParentheses: false
SpacesInCStyleCastParentheses: false
SpaceAfterControlStatementKeyword: true
SpaceBeforeAssignmentOperators: true
ContinuationIndentWidth: 4
...
+10
View File
@@ -0,0 +1,10 @@
build/
CMakeLists.txt.user
.ycm_extra_conf.py
.ycm_extra_conf.pyc
test.sh
release.sh
*.swp
*.swo
src/dbg/dbg.hpp
*~
+41
View File
@@ -0,0 +1,41 @@
if(NOT HAVE_QT OR NOT HAVE_CXX11 OR QT_VERSION_MAJOR LESS 5)
ocv_module_disable(cvv)
return()
endif()
set(the_description "Debug visualization framework")
ocv_add_module(cvv opencv_core opencv_imgproc opencv_features WRAP python)
ocv_warnings_disable(CMAKE_CXX_FLAGS -Wshadow -Wmissing-declarations)
# Qt
set(CVV_QT_MODULES Core Gui Widgets)
if(QT_VERSION_MAJOR EQUAL 6)
list(APPEND CVV_QT_MODULES Core5Compat)
endif()
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS ${CVV_QT_MODULES})
set(CMAKE_INCLUDE_CURRENT_DIR ON)
# Automoc doesn't work properly with opencv_world build
# Use QT<ver>_WRAP_CPP() directly instead
file(GLOB_RECURSE CVV_MOC_HEADERS
"${CMAKE_CURRENT_LIST_DIR}/src/*.hpp"
"${CMAKE_CURRENT_LIST_DIR}/src/*.h"
)
if(QT_VERSION_MAJOR EQUAL 6)
QT6_WRAP_CPP(_MOC_OUTFILES ${CVV_MOC_HEADERS})
elseif(QT_VERSION_MAJOR EQUAL 5)
QT5_WRAP_CPP(_MOC_OUTFILES ${CVV_MOC_HEADERS})
endif()
foreach(module ${CVV_QT_MODULES})
list(APPEND CVV_LIBRARIES ${Qt${QT_VERSION_MAJOR}${module}_LIBRARIES})
endforeach()
ocv_module_include_directories()
ocv_glob_module_sources(SOURCES ${_MOC_OUTFILES})
ocv_create_module(${CVV_LIBRARIES})
ocv_add_accuracy_tests()
ocv_add_perf_tests()
ocv_add_samples()
+37
View File
@@ -0,0 +1,37 @@
Copyright (c) 2013/2014 Johannes Bechberger
Copyright (c) 2013/2014 Erich Bretnütz
Copyright (c) 2013/2014 Nikolai Gaßner
Copyright (c) 2013/2014 Raphael Grimm
Copyright (c) 2013/2014 Clara Scherer
Copyright (c) 2013/2014 Florian Weber
Copyright (c) 2013/2014 Andreas Bihlmaier
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name CVVisual nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+4
View File
@@ -0,0 +1,4 @@
GUI for Interactive Visual Debugging of Computer Vision Programs
================================================================
Simple code that you can add to your program that pops up a GUI allowing you to interactively and visually debug computer vision programs.
+26
View File
@@ -0,0 +1,26 @@
#ifndef __OPENCV_CVV_HPP__
#define __OPENCV_CVV_HPP__
/**
@defgroup cvv GUI for Interactive Visual Debugging of Computer Vision Programs
Namespace for all functions is **cvv**, i.e. *cvv::showImage()*.
Compilation:
- For development, i.e. for cvv GUI to show up, compile your code using cvv with
*g++ -DCVVISUAL_DEBUGMODE*.
- For release, i.e. cvv calls doing nothing, compile your code without above flag.
See cvv tutorial for a commented example application using cvv.
*/
#include <opencv2/cvv/call_meta_data.hpp>
#include <opencv2/cvv/debug_mode.hpp>
#include <opencv2/cvv/dmatch.hpp>
#include <opencv2/cvv/filter.hpp>
#include <opencv2/cvv/final_show.hpp>
#include <opencv2/cvv/show_image.hpp>
#endif //__OPENCV_CVV_HPP__
@@ -0,0 +1,67 @@
#ifndef CVVISUAL_CALL_DATA_HPP
#define CVVISUAL_CALL_DATA_HPP
#include <string>
#include <cstddef>
#include <utility>
namespace cvv
{
//! @addtogroup cvv
//! @{
namespace impl
{
/**
* @brief Optional information about a location in Code.
*/
struct CallMetaData
{
public:
/**
* @brief Creates an unknown location.
*/
CallMetaData()
: file(nullptr), line(0), function(nullptr), isKnown(false)
{
}
/**
* @brief Creates the provided location.
*
* Argument should be self-explaining.
*/
CallMetaData(const char *file, size_t line, const char *function)
: file(file), line(line), function(function), isKnown(true)
{
}
operator bool()
{
return isKnown;
}
// self-explaining:
const char *file;
const size_t line;
const char *function;
/**
* @brief Whether *this holds actual data.
*/
const bool isKnown;
};
}
//! @}
} // namespaces
/**
* @brief Creates an instance of CallMetaData with the location of the macro as
* value.
*/
#define CVVISUAL_LOCATION ::cvv::impl::CallMetaData(__FILE__, __LINE__, CV_Func)
#endif
+5
View File
@@ -0,0 +1,5 @@
#ifdef __OPENCV_BUILD
#error this is a compatibility header which should not be used inside the OpenCV library
#endif
#include "opencv2/cvv.hpp"
@@ -0,0 +1,51 @@
#ifndef CVVISUAL_DEBUG_MODE_HPP
#define CVVISUAL_DEBUG_MODE_HPP
#if __cplusplus >= 201103L && defined CVVISUAL_USE_THREAD_LOCAL
#define CVVISUAL_THREAD_LOCAL thread_local
#else
#define CVVISUAL_THREAD_LOCAL
#endif
namespace cvv
{
//! @addtogroup cvv
//! @{
namespace impl
{
/**
* The debug-flag-singleton
*/
static inline bool &getDebugFlag()
{
CVVISUAL_THREAD_LOCAL static bool flag = true;
return flag;
}
} // namespace impl
/** @brief Returns whether debug-mode is active for this TU and thread.
*/
static inline bool debugMode()
{
return impl::getDebugFlag();
}
/** @brief Enable or disable cvv for current translation unit and thread
(disabled this way has higher - but still low - overhead compared to using the compile flags).
@param active
*/
static inline void setDebugFlag(bool active)
{
impl::getDebugFlag() = active;
}
//! @}
} // namespace cvv
#endif
+100
View File
@@ -0,0 +1,100 @@
#ifndef CVVISUAL_DEBUG_DMATCH_HPP
#define CVVISUAL_DEBUG_DMATCH_HPP
#include <string>
#include "opencv2/core.hpp"
#include "opencv2/features.hpp"
#include "call_meta_data.hpp"
#include "debug_mode.hpp"
#ifdef CV_DOXYGEN
#define CVVISUAL_DEBUGMODE
#endif
namespace cvv
{
//! @addtogroup cvv
//! @{
namespace impl
{
CV_EXPORTS void debugDMatch(cv::InputArray img1, std::vector<cv::KeyPoint> keypoints1,
cv::InputArray img2, std::vector<cv::KeyPoint> keypoints2,
std::vector<cv::DMatch> matches, const CallMetaData &data,
const char *description, const char *view,
bool useTrainDescriptor);
} // namespace impl
#ifdef CVVISUAL_DEBUGMODE
/** @brief Add a filled in DMatch \<dmatch\> to debug GUI.
The matches can are visualized for interactive inspection in different GUI views (one similar to an
interactive :draw_matches:drawMatches\<\>).
@param img1 First image used in DMatch \<dmatch\>.
@param keypoints1 Keypoints of first image.
@param img2 Second image used in DMatch.
@param keypoints2 Keypoints of second image.
@param matches
@param data See showImage
@param description See showImage
@param view See showImage
@param useTrainDescriptor Use DMatch \<dmatch\>'s train descriptor index instead of query
descriptor index.
*/
static inline void
debugDMatch(cv::InputArray img1, std::vector<cv::KeyPoint> keypoints1,
cv::InputArray img2, std::vector<cv::KeyPoint> keypoints2,
std::vector<cv::DMatch> matches, const impl::CallMetaData &data,
const char *description = nullptr, const char *view = nullptr,
bool useTrainDescriptor = true)
{
if (debugMode())
{
impl::debugDMatch(img1, std::move(keypoints1), img2,
std::move(keypoints2), std::move(matches),
data, description, view, useTrainDescriptor);
}
}
/** @overload */
static inline void
debugDMatch(cv::InputArray img1, std::vector<cv::KeyPoint> keypoints1,
cv::InputArray img2, std::vector<cv::KeyPoint> keypoints2,
std::vector<cv::DMatch> matches, const impl::CallMetaData &data,
const std::string &description, const std::string &view,
bool useTrainDescriptor = true)
{
if (debugMode())
{
impl::debugDMatch(img1, std::move(keypoints1), img2,
std::move(keypoints2), std::move(matches),
data, description.c_str(), view.c_str(),
useTrainDescriptor);
}
}
#else
static inline void debugDMatch(cv::InputArray, std::vector<cv::KeyPoint>,
cv::InputArray, std::vector<cv::KeyPoint>,
std::vector<cv::DMatch>,
const impl::CallMetaData &,
const char * = nullptr, const char * = nullptr,
bool = true)
{
}
static inline void debugDMatch(cv::InputArray, std::vector<cv::KeyPoint>,
cv::InputArray, std::vector<cv::KeyPoint>,
std::vector<cv::DMatch>,
const impl::CallMetaData &, const std::string &,
const std::string &, bool = true)
{
}
#endif
//! @}
} // namespace cvv
#endif
@@ -0,0 +1,76 @@
#ifndef CVVISUAL_DEBUG_FILTER_HPP
#define CVVISUAL_DEBUG_FILTER_HPP
#include <string>
#include "opencv2/core.hpp"
#include "call_meta_data.hpp"
#include "debug_mode.hpp"
#ifdef CV_DOXYGEN
#define CVVISUAL_DEBUGMODE
#endif
namespace cvv
{
//! @addtogroup cvv
//! @{
namespace impl
{
// implementation outside API
CV_EXPORTS void debugFilter(cv::InputArray original, cv::InputArray result,
const CallMetaData &data, const char *description,
const char *view);
} // namespace impl
#ifdef CVVISUAL_DEBUGMODE
/**
* @brief Use the debug-framework to compare two images (from which the second
* is intended to be the result of
* a filter applied to the first).
*/
static inline void
debugFilter(cv::InputArray original, cv::InputArray result,
impl::CallMetaData metaData = impl::CallMetaData(),
const char *description = nullptr, const char *view = nullptr)
{
if (debugMode())
{
impl::debugFilter(original, result, metaData, description,
view);
}
}
/** @overload */
static inline void debugFilter(cv::InputArray original, cv::InputArray result,
impl::CallMetaData metaData,
const ::std::string &description,
const ::std::string &view = "")
{
if (debugMode())
{
impl::debugFilter(original, result, metaData,
description.c_str(), view.c_str());
}
}
#else
static inline void debugFilter(cv::InputArray, cv::InputArray,
impl::CallMetaData = impl::CallMetaData(),
const char * = nullptr, const char * = nullptr)
{
}
static inline void debugFilter(cv::InputArray, cv::InputArray,
impl::CallMetaData, const ::std::string &,
const ::std::string &)
{
}
#endif
//! @}
} // namespace cvv
#endif
@@ -0,0 +1,52 @@
#ifndef CVVISUAL_FINAL_SHOW_HPP
#define CVVISUAL_FINAL_SHOW_HPP
#include "opencv2/core.hpp"
#include "debug_mode.hpp"
namespace cvv
{
//! @addtogroup cvv
//! @{
namespace impl
{
CV_EXPORTS void finalShow();
}
/** @brief Passes the control to the debug-window for a last time.
This function **must** be called *once* *after* all cvv calls if any. As an alternative create an
instance of FinalShowCaller, which calls finalShow() in its destructor (RAII-style).
*/
inline void finalShow()
{
#ifdef CVVISUAL_DEBUGMODE
if (debugMode())
{
impl::finalShow();
}
#endif
}
/**
* @brief RAII-class to call finalShow() in it's dtor.
*/
class FinalShowCaller
{
public:
/**
* @brief Calls finalShow().
*/
~FinalShowCaller()
{
finalShow();
}
};
//! @}
}
#endif
@@ -0,0 +1,75 @@
#ifndef CVVISUAL_DEBUG_SHOW_IMAGE_HPP
#define CVVISUAL_DEBUG_SHOW_IMAGE_HPP
#include <string>
#include "opencv2/core.hpp"
#include "call_meta_data.hpp"
#include "debug_mode.hpp"
#ifdef CV_DOXYGEN
#define CVVISUAL_DEBUGMODE
#endif
namespace cvv
{
//! @addtogroup cvv
//! @{
namespace impl
{
// implementation outside API
CV_EXPORTS void showImage(cv::InputArray img, const CallMetaData &data,
const char *description, const char *view);
} // namespace impl
#ifdef CVVISUAL_DEBUGMODE
/** @brief Add a single image to debug GUI (similar to imshow \<\>).
@param img Image to show in debug GUI.
@param metaData Properly initialized CallMetaData struct, i.e. information about file, line and
function name for GUI. Use CVVISUAL_LOCATION macro.
@param description Human readable description to provide context to image.
@param view Preselect view that will be used to visualize this image in GUI. Other views can still
be selected in GUI later on.
*/
static inline void showImage(cv::InputArray img,
impl::CallMetaData metaData = impl::CallMetaData(),
const char *description = nullptr,
const char *view = nullptr)
{
if (debugMode())
{
impl::showImage(img, metaData, description, view);
}
}
/** @overload */
static inline void showImage(cv::InputArray img, impl::CallMetaData metaData,
const ::std::string &description,
const ::std::string &view = "")
{
if (debugMode())
{
impl::showImage(img, metaData, description.c_str(),
view.c_str());
}
}
#else
static inline void showImage(cv::InputArray,
impl::CallMetaData = impl::CallMetaData(),
const char * = nullptr, const char * = nullptr)
{
}
static inline void showImage(cv::InputArray, impl::CallMetaData,
const ::std::string &, const ::std::string &)
{
}
#endif
//! @}
} // namespace cvv
#endif
+119
View File
@@ -0,0 +1,119 @@
// system includes
#include <iostream>
// library includes
#include <opencv2/imgproc.hpp>
#include <opencv2/features.hpp>
#include <opencv2/videoio.hpp>
#define CVVISUAL_DEBUGMODE
#include <opencv2/cvv/debug_mode.hpp>
#include <opencv2/cvv/show_image.hpp>
#include <opencv2/cvv/filter.hpp>
#include <opencv2/cvv/dmatch.hpp>
#include <opencv2/cvv/final_show.hpp>
using namespace std;
using namespace cv;
template<class T> std::string toString(const T& p_arg)
{
std::stringstream ss;
ss << p_arg;
return ss.str();
}
int
main(int argc, char** argv)
{
// parser keys
const char *keys =
"{ help h usage ? | | show this message }"
"{ width W | 0| camera resolution width. leave at 0 to use defaults }"
"{ height H | 0| camera resolution height. leave at 0 to use defaults }";
CommandLineParser parser(argc, argv, keys);
if (parser.has("help")) {
parser.printMessage();
return 0;
}
int res_w = parser.get<int>("width");
int res_h = parser.get<int>("height");
// setup video capture
cv::VideoCapture capture(0);
if (!capture.isOpened()) {
std::cout << "Could not open VideoCapture" << std::endl;
return 1;
}
if (res_w>0 && res_h>0) {
printf("Setting resolution to %dx%d\n", res_w, res_h);
capture.set(cv::CAP_PROP_FRAME_WIDTH, res_w);
capture.set(cv::CAP_PROP_FRAME_HEIGHT, res_h);
}
cv::Mat prevImgGray;
std::vector<cv::KeyPoint> prevKeypoints;
cv::Mat prevDescriptors;
int maxFeatureCount = 500;
Ptr<ORB> detector = ORB::create(maxFeatureCount);
cv::BFMatcher matcher(cv::NORM_HAMMING);
for (int imgId = 0; imgId < 10; imgId++) {
// capture a frame
cv::Mat imgRead;
capture >> imgRead;
printf("%d: image captured\n", imgId);
std::string imgIdString{"imgRead"};
imgIdString += toString(imgId);
cvv::showImage(imgRead, CVVISUAL_LOCATION, imgIdString.c_str());
// convert to grayscale
cv::Mat imgGray;
cv::cvtColor(imgRead, imgGray, COLOR_BGR2GRAY);
cvv::debugFilter(imgRead, imgGray, CVVISUAL_LOCATION, "to gray");
// detect ORB features
std::vector<cv::KeyPoint> keypoints;
cv::Mat descriptors;
detector->detectAndCompute(imgGray, cv::noArray(), keypoints, descriptors);
printf("%d: detected %zd keypoints\n", imgId, keypoints.size());
// match them to previous image (if available)
if (!prevImgGray.empty()) {
std::vector<cv::DMatch> matches;
matcher.match(prevDescriptors, descriptors, matches);
printf("%d: all matches size=%zd\n", imgId, matches.size());
std::string allMatchIdString{"all matches "};
allMatchIdString += toString(imgId-1) + "<->" + toString(imgId);
cvv::debugDMatch(prevImgGray, prevKeypoints, imgGray, keypoints, matches, CVVISUAL_LOCATION, allMatchIdString.c_str());
// remove worst (as defined by match distance) bestRatio quantile
double bestRatio = 0.8;
std::sort(matches.begin(), matches.end());
matches.resize(int(bestRatio * matches.size()));
printf("%d: best matches size=%zd\n", imgId, matches.size());
std::string bestMatchIdString{"best " + toString(bestRatio) + " matches "};
bestMatchIdString += toString(imgId-1) + "<->" + toString(imgId);
cvv::debugDMatch(prevImgGray, prevKeypoints, imgGray, keypoints, matches, CVVISUAL_LOCATION, bestMatchIdString.c_str());
}
prevImgGray = imgGray;
prevKeypoints = keypoints;
prevDescriptors = descriptors;
}
cvv::finalShow();
return 0;
}
@@ -0,0 +1,384 @@
#include "view_controller.hpp"
#include <stdexcept>
#include <iostream>
#include <QApplication>
#include <QDesktopServices>
#include <QUrl>
#include "../gui/call_tab.hpp"
#include "../gui/call_window.hpp"
#include "../gui/overview_panel.hpp"
#include "../gui/main_call_window.hpp"
#include "../gui/filter_call_tab.hpp"
#include "../gui/match_call_tab.hpp"
#include "../gui/image_call_tab.hpp"
#include "../impl/init.hpp"
#include "../impl/filter_call.hpp"
#include "../impl/match_call.hpp"
#include "../impl/single_image_call.hpp"
#include "../impl/data_controller.hpp"
#include "../qtutil/util.hpp"
namespace cvv
{
namespace controller
{
// It's only used for instatiating a QApplication.
// static char *emptyArray[] = {""};
static char *parameterSystemV[] = { new char[1]{ 0 }, nullptr };
static int parameterSystemC = 1;
ViewController::ViewController()
{
impl::initializeFilterAndViews();
if (!QApplication::instance())
{
auto tmp =
new QApplication{ parameterSystemC, parameterSystemV };
ownsQApplication = true;
(void)tmp;
}
ovPanel = new gui::OverviewPanel{ util::makeRef(*this) };
mainWindow = new gui::MainCallWindow(util::makeRef(*this), 0, ovPanel);
windowMap[0] = std::unique_ptr<gui::CallWindow>(mainWindow);
max_window_id = 0;
mainWindow->show();
}
ViewController::~ViewController()
{
callTabMap.clear();
windowMap.clear();
windowMap.clear();
if (ownsQApplication)
{
delete QApplication::instance();
}
}
void ViewController::addCallType(const QString typeName, TabFactory constr)
{
ViewController::callTabType[typeName] = constr;
}
static std::unique_ptr<cvv::gui::FilterCallTab>
makeFilterCallTab(cvv::util::Reference<cvv::impl::Call> call)
{
return cvv::util::make_unique<cvv::gui::FilterCallTab>(
*call.castTo<cvv::impl::FilterCall>());
}
static std::unique_ptr<cvv::gui::MatchCallTab>
makeMatchCallTab(cvv::util::Reference<cvv::impl::Call> call)
{
return cvv::util::make_unique<cvv::gui::MatchCallTab>(
*call.castTo<cvv::impl::MatchCall>());
}
static std::unique_ptr<cvv::gui::ImageCallTab>
makeImageCallTab(cvv::util::Reference<cvv::impl::Call> call)
{
return cvv::util::make_unique<cvv::gui::ImageCallTab>(
*call.castTo<cvv::impl::SingleImageCall>());
}
std::map<QString, TabFactory> ViewController::callTabType{
{ "filter", makeFilterCallTab }, { "match", makeMatchCallTab },
{ "singleImage", makeImageCallTab }
};
void ViewController::addCall(util::Reference<impl::Call> data)
{
updateMode();
if (mode == Mode::NORMAL)
{
ovPanel->addElement(*data);
mainWindow->showOverviewTab();
}
else if (mode == Mode::FAST_FORWARD)
{
ovPanel->addElementBuffered(*data);
}
}
void ViewController::exec()
{
updateMode();
if (mode == Mode::NORMAL)
{
QApplication::instance()->exec();
}
}
impl::Call &ViewController::getCall(size_t id)
{
return impl::dataController().getCall(id);
}
QString ViewController::getSetting(const QString &scope, const QString &key)
{
return qtutil::getSetting(scope, key);
}
std::vector<util::Reference<gui::CallWindow>> ViewController::getTabWindows()
{
std::vector<util::Reference<gui::CallWindow>> windows{};
for (auto &it : windowMap)
{
windows.push_back(util::makeRef(*(it.second)));
}
return windows;
}
util::Reference<gui::MainCallWindow> ViewController::getMainWindow()
{
return util::makeRef(*mainWindow);
}
void ViewController::moveCallTabToNewWindow(size_t tabId)
{
if (!hasCall(tabId))
return;
auto newWindow = util::make_unique<gui::CallWindow>(
util::makeRef<ViewController>(*this), ++max_window_id);
removeCallTab(tabId);
newWindow->addTab(getCallTab(tabId));
newWindow->show();
if (doesShowExitProgramButton)
{
newWindow->showExitProgramButton();
}
windowMap[max_window_id] = std::move(newWindow);
removeEmptyWindowsWithDelay();
}
void ViewController::moveCallTabToWindow(size_t tabId, size_t windowId)
{
if (!hasCall(tabId))
return;
removeCallTab(tabId);
auto tab = getCallTab(tabId);
windowMap[windowId]->addTab(tab);
removeEmptyWindowsWithDelay();
}
void ViewController::removeCallTab(size_t tabId, bool deleteIt, bool deleteCall, bool updateUI)
{
auto *curWindow = getCurrentWindowOfTab(tabId);
if (curWindow->hasTab(tabId))
{
getCurrentWindowOfTab(tabId)->removeTab(tabId);
if (deleteIt)
{
callTabMap.erase(tabId);
}
}
if (deleteCall && hasCall(tabId))
{
if (updateUI)
{
ovPanel->removeElement(tabId);
}
impl::dataController().removeCall(tabId);
}
removeEmptyWindowsWithDelay();
}
void ViewController::openHelpBrowser(const QString &topic)
{
qtutil::openHelpBrowser(topic);
}
void ViewController::resumeProgramExecution()
{
QApplication::instance()->exit();
}
void ViewController::setDefaultSetting(const QString &scope, const QString &key,
const QString &value)
{
qtutil::setDefaultSetting(scope, key, value);
}
void ViewController::setSetting(const QString &scope, const QString &key,
const QString &value)
{
qtutil::setSetting(scope, key, value);
}
void ViewController::showCallTab(size_t tabId)
{
auto *window = getCurrentWindowOfTab(tabId);
window->showTab(tabId);
window->setWindowState((window->windowState() & ~Qt::WindowMinimized) |
Qt::WindowActive);
window->raise();
}
void ViewController::showAndOpenCallTab(size_t tabId)
{
auto curWindow = getCurrentWindowOfTab(tabId);
if (!curWindow->hasTab(tabId))
{
moveCallTabToWindow(tabId, 0);
curWindow = mainWindow;
}
curWindow->showTab(tabId);
}
void ViewController::openCallTab(size_t tabId)
{
auto curWindow = getCurrentWindowOfTab(tabId);
if (!curWindow->hasTab(tabId))
{
moveCallTabToWindow(tabId, 0);
curWindow = mainWindow;
}
}
void ViewController::showOverview()
{
mainWindow->setWindowState(
(mainWindow->windowState() & ~Qt::WindowMinimized) |
Qt::WindowActive);
mainWindow->raise();
mainWindow->showOverviewTab();
}
gui::CallWindow *ViewController::getCurrentWindowOfTab(size_t tabId)
{
for (auto &elem : windowMap)
{
if (elem.second->hasTab(tabId))
{
return elem.second.get();
}
}
return mainWindow;
}
gui::CallTab *ViewController::getCallTab(size_t tabId)
{
if (callTabMap.count(tabId) == 0)
{
auto *call = &(getCall(tabId));
if (callTabType.count(call->type()) == 0)
{
throw std::invalid_argument{
"no such type '" + call->type().toStdString() +
"'"
};
}
callTabMap[tabId] =
callTabType[call->type()](util::makeRef(*call));
}
return callTabMap[tabId].get();
}
void ViewController::removeWindowFromMaps(size_t windowId)
{
if (windowMap.count(windowId) > 0)
{
windowMap[windowId].release();
windowMap.erase(windowId);
}
}
void ViewController::removeEmptyWindows()
{
std::vector<size_t> remIds{};
for (auto &elem : windowMap)
{
if (elem.second->tabCount() == 0 && elem.second->getId() != 0)
{
remIds.push_back(elem.first);
}
}
for (auto windowId : remIds)
{
auto window = windowMap[windowId].release();
windowMap.erase(windowId);
window->deleteLater();
}
shouldRunRemoveEmptyWindows_ = false;
}
void ViewController::removeEmptyWindowsWithDelay()
{
shouldRunRemoveEmptyWindows_ = true;
}
bool ViewController::shouldRunRemoveEmptyWindows()
{
return shouldRunRemoveEmptyWindows_;
}
void ViewController::showExitProgramButton()
{
for (auto &elem : windowMap)
{
elem.second->showExitProgramButton();
}
doesShowExitProgramButton = true;
}
bool ViewController::hasCall(size_t id)
{
return impl::dataController().hasCall(id);
}
void ViewController::setMode(Mode newMode)
{
mode = newMode;
switch (newMode)
{
case Mode::NORMAL:
break;
case Mode::HIDE:
hideAll();
QApplication::instance()->exit();
break;
case Mode::FAST_FORWARD:
if (!doesShowExitProgramButton)
{
QApplication::instance()->exit();
}
else
{
mode = Mode::NORMAL;
}
break;
}
}
Mode ViewController::getMode()
{
return mode;
}
void ViewController::updateMode()
{
if (mode == Mode::FAST_FORWARD && hasFinalCall())
{
mode = Mode::NORMAL;
ovPanel->flushElementBuffer();
}
}
void ViewController::hideAll()
{
for (auto &window : windowMap)
{
window.second->hide();
}
}
bool ViewController::hasFinalCall()
{
return doesShowExitProgramButton;
}
}
}
@@ -0,0 +1,309 @@
#ifndef CVVISUAL_VIEWCONTROLLER_HPP
#define CVVISUAL_VIEWCONTROLLER_HPP
#include <vector>
#include <algorithm>
#include <iostream>
#include <map>
#include <memory>
#include <functional>
#include <utility>
#include <QString>
#include "../util/util.hpp"
#include "../impl/call.hpp"
#include "../gui/call_window.hpp"
#include "../gui/call_tab.hpp"
namespace cvv
{
namespace gui
{
class CallTab;
class CallWindow;
class MainCallWindow;
class OverviewPanel;
}
namespace controller
{
/**
* @brief Modes that this cvv application can be running in.
*/
enum class Mode
{
/**
* @brief The normal mode.
*/
NORMAL = 0,
/**
* @brief The cvv UI is hidden.
*/
HIDE = 1,
/**
* @brief The cvv UI stops only at the final call
* The final call is the call which is called after `cvv::finalShow()`)
*/
FAST_FORWARD = 2
};
class ViewController;
/**
* @brief Typedef for a function that creates a CallTab from a impl::Call.
*/
using TabFactory =
std::function<std::unique_ptr<gui::CallTab>(util::Reference<impl::Call>)>;
/**
* @brief Controlls the windows, call tabs and the event fetch loop.
* Its the layer between the low level model (aka DataController) an the high
* level GUI (aka CallTab, OverviewPanel, ...).
*/
class ViewController
{
public:
/**
* @brief The default contructor for this class.
*/
ViewController();
/**
* @brief Clean up.
*/
~ViewController();
/**
* @brief Adds the new call tab type.
* @param typeName name of the new type
* @param constr function constructing an instance of this call tab
* type
* @return an instance of the new call tab type
*/
static void addCallType(const QString typeName, TabFactory constr);
/**
* @brief Adds a new call and shows it in the overview table.
* @param data new call (data)
*/
void addCall(util::Reference<impl::Call> data);
/**
* @brief Execute the Qt event loop.
*/
void exec();
/**
* @brief Get the call with the given id.
* @param id given id
* @return call with the given id
*/
impl::Call &getCall(size_t id);
/**
* @brief Get the current setting [key] in the given scope.
* Please use `setDefaultSetting` to set a default value that's other
* than
* an empty QString.
* @param scope given scope (e.g. 'Overview')
* @param key settings key (e.g. 'autoOpenTabs')
* @return settings string
*/
QString getSetting(const QString &scope, const QString &key);
/**
* @brief Get the inherited call windows with tabs.
* @return the inherited CallWindows
*/
std::vector<util::Reference<gui::CallWindow>> getTabWindows();
/**
* @brief Get the inherited main window.
* @return the inherited main window
*/
util::Reference<gui::MainCallWindow> getMainWindow();
/**
* @brief Move the call tab with the given id to a new window.
* @param tabId given call tab id
*/
void moveCallTabToNewWindow(size_t tabId);
/**
* @brief Move the given call tab to the given window.
* @param tabId id of the given call tab
* @param windowId id of the given window (0 is the main window)
*/
void moveCallTabToWindow(size_t tabId, size_t windowId);
/**
* @brief Removes the call tab with the given id.
* @param tabId given id
* @param deleteCall if deleteCall and deleteIt are true, it also
* deletes the proper Call
*/
void removeCallTab(size_t tabId, bool deleteIt = true,
bool deleteCall = false, bool updateUI = true);
/**
* @brief Opens the users default browser with the topic help page.
* Current URL: cvv.mostlynerdless.de/help.php?topic=[topic]
*
* Topics can be added via appending the doc/topics.yml file.
*
* @param topic help topic
*/
void openHelpBrowser(const QString &topic);
/**
* @brief Resume the execution of the calling program.
*/
void resumeProgramExecution();
/**
* @brief Set the default setting for a given stettings key and scope.
* It doesn't override existing settings.
* @param scope given settings scope
* @param key given settings key
* @param value default value of the setting
*/
void setDefaultSetting(const QString &scope, const QString &key,
const QString &value);
/**
* @brief Set the setting for a given stettings key and scope.
* @param scope given settings scope
* @param key given settings key
* @param value new value of the setting
*/
void setSetting(const QString &scope, const QString &key,
const QString &value);
/**
* @brief Show the given call tab and bring it's window to the front.
* @note It's not guaranteed that it really brings the tabs' window to the front.
* @param tabId id of the given call tab
*/
void showCallTab(size_t tabId);
/**
* @brief Shows the tab and opens it if necessary.
* @param tabId id of the tab
*/
void showAndOpenCallTab(size_t tabId);
/**
* @brief Opens the tab if necessary.
* @param tabId id of the tab
*/
void openCallTab(size_t tabId);
/**
* @brief Show the overview tab (and table) and bring it's window to the
* front.
* @note The latter is not guaranteed.
*/
void showOverview();
/**
* @brief Get the window in which the given tab lays currently.
* @param tabId id of the given call tab
* @return current window
*/
gui::CallWindow *getCurrentWindowOfTab(size_t tabId);
/**
* @brief Returns the call tab with the given id and constructs it if
* doesn't exit.
* @param tabId given id
* @return call tab with given id
*/
gui::CallTab *getCallTab(size_t tabId);
/**
* @brief Remove the window from the internal data structures.
* @param windowId id of the window
* @note Only call this method if you now the implacations of deleting
* the window.
*/
void removeWindowFromMaps(size_t windowId);
/**
* @brief Shows an "Exit program" button on each window.
*/
void showExitProgramButton();
/**
* @brief Removes the empty windows.
* @note It's safer to call the removeEmptyWindowsWithDelay method
* instead.
*/
void removeEmptyWindows();
/**
* @brief Removes the empty windows with a small delay.
*/
void removeEmptyWindowsWithDelay();
/**
* @brief Checks whether or not is useful to call the
* removeEmptyWindows() method.
* @return Is is useful to call the removeEmptyWindows() method?
* @note Please don't call this method outside a periodcally called
* method.
*/
bool shouldRunRemoveEmptyWindows();
/**
* @brief Set the mode that this application is running in.
* @param newMode mode to be set
*/
void setMode(Mode newMode);
/**
* @brief Returns the mode this program is running in.
* @return the current mode, NROMAL, HIDE or FAST_FORWARD
*/
Mode getMode();
/**
* @brief Checks whether or not the `cvv::finalCall()` method has been
* called?
* @return Has the `cvv::finalCall()` method been called?
*/
bool hasFinalCall();
private:
static std::map<QString, TabFactory> callTabType;
std::map<size_t, std::unique_ptr<gui::CallWindow>> windowMap{};
gui::MainCallWindow *mainWindow;
std::map<size_t, std::unique_ptr<gui::CallTab>> callTabMap{};
gui::OverviewPanel *ovPanel;
bool doesShowExitProgramButton = false;
/**
* @brief Counter == 0 <=> you should run `removeEmptyWindows()`.
*/
bool shouldRunRemoveEmptyWindows_ = true;
Mode mode = Mode::NORMAL;
bool ownsQApplication = false;
size_t max_window_id = 0;
bool hasCall(size_t id);
void updateMode();
void hideAll();
};
}
}
#endif
+16
View File
@@ -0,0 +1,16 @@
#include "api.hpp"
#include "../gui/filter_call_tab.hpp"
#include "../gui/match_call_tab.hpp"
namespace cvv
{
namespace extend
{
void addCallType(const QString name, TabFactory factory)
{
controller::ViewController::addCallType(name, factory);
}
}
} // namespaces cvv::extend
+63
View File
@@ -0,0 +1,63 @@
#ifndef CVVISUAL_EXTENSION_API_HPP
#define CVVISUAL_EXTENSION_API_HPP
#include <opencv2/core.hpp>
#include <QString>
#include <QWidget>
#include "../impl/call.hpp"
#include "../controller/view_controller.hpp"
#include "../view/filter_view.hpp"
#include "../gui/match_call_tab.hpp"
#include "../gui/filter_call_tab.hpp"
#include "../qtutil/filterselectorwidget.hpp"
namespace cvv
{
namespace extend
{
/**
* @brief Introduces a new filter-view.
* @param name of the new FilterView.
* @tparam FView A FilterView. Needs to have a constructor of the form
* FView(const cvv::impl::FilterCall&, QWidget*).
*/
template <class FView> void addFilterView(const QString name)
{
cvv::gui::FilterCallTab::registerFilterView<FView>(name);
}
/**
* @brief Introduces a new match-view.
* @param name of the new MatchView.
* @tparam MView A MatchView. Needs to have a constructor of the form
* MView(const cvv::impl::MatchCall&, QWidget*).
*/
template <class MView> void addMatchView(const QString name)
{
cvv::gui::MatchCallTab::registerMatchView<MView>(name);
}
using TabFactory = controller::TabFactory;
/**
* @brief Introduces a new call-type.
* @param factory A function that receives a reference to a call and should
* return the appropriate
* window.
*/
void addCallType(const QString name, TabFactory factory);
template <std::size_t In, std::size_t Out, class Filter>
/**
* @brief Introduces a new filter for the filter-selector-widget.
*/
bool registerFilter(const QString &name)
{
return cvv::qtutil::registerFilter<In, Out, Filter>(name);
}
}
} // namespaces cvv::extend
#endif
+58
View File
@@ -0,0 +1,58 @@
#ifndef CVVISUAL_CALL_TAB_HPP
#define CVVISUAL_CALL_TAB_HPP
#include <QString>
#include <QWidget>
#include "../util/util.hpp"
namespace cvv
{
namespace gui
{
/**
* @brief Super class of the inner part of a tab or window.
* A call tab.
* The inner part of a tab or a window.
* Super class for actual call tabs containing views.
*/
class CallTab : public QWidget
{
Q_OBJECT
public:
/**
* @brief Returns the name of this tab.
* @return current name
*/
const QString getName() const
{
return name;
}
/**
* @brief Sets the name of this tab.
* @param name new name
*/
void setName(const QString &newName)
{
name = newName;
}
/**
* @brief Returns the of this CallTab.
* @return the ID of the CallTab
* (ID is equal to the ID of the associated call in derived classes)
*/
virtual size_t getId() const
{
return 0;
}
private:
QString name;
};
}
} // namespaces
#endif
+285
View File
@@ -0,0 +1,285 @@
#include "call_window.hpp"
#include <QMenu>
#include <QStatusBar>
#include <QPushButton>
#include <QHBoxLayout>
#include <QVariant>
#include "../stfl/stringutils.hpp"
namespace cvv
{
namespace controller
{
class ViewController;
}
namespace gui
{
CallWindow::CallWindow(util::Reference<controller::ViewController> controller,
size_t id)
: id{ id }, controller{ controller }
{
initTabs();
initFooter();
setWindowTitle(QString("CVVisual | window no. %1").arg(id));
setMinimumWidth(600);
setMinimumHeight(600);
}
void CallWindow::initTabs()
{
tabWidget = new TabWidget(this);
tabWidget->setTabsClosable(true);
tabWidget->setMovable(true);
setCentralWidget(tabWidget);
auto *flowButtons = new QHBoxLayout();
auto *flowButtonsWidget = new QWidget(this);
tabWidget->setCornerWidget(flowButtonsWidget, Qt::TopLeftCorner);
flowButtonsWidget->setLayout(flowButtons);
flowButtons->setAlignment(Qt::AlignLeft | Qt::AlignTop);
closeButton = new QPushButton("Close", this);
flowButtons->addWidget(closeButton);
closeButton->setStyleSheet(
"QPushButton {background-color: red; color: white;}");
closeButton->setToolTip("Close this debugging application.");
connect(closeButton, SIGNAL(clicked()), this, SLOT(closeApp()));
fastForwardButton = new QPushButton(">>", this);
flowButtons->addWidget(fastForwardButton);
fastForwardButton->setStyleSheet(
"QPushButton {background-color: yellow; color: blue;}");
fastForwardButton->setToolTip(
"Fast forward until cvv::finalCall() gets called.");
connect(fastForwardButton, SIGNAL(clicked()), this,
SLOT(fastForward()));
stepButton = new QPushButton("Step", this);
flowButtons->addWidget(stepButton);
stepButton->setStyleSheet(
"QPushButton {background-color: green; color: white;}");
stepButton->setToolTip(
"Resume program execution for a next debugging step.");
connect(stepButton, SIGNAL(clicked()), this, SLOT(step()));
flowButtons->setContentsMargins(0, 0, 0, 0);
flowButtons->setSpacing(0);
auto *tabBar = tabWidget->getTabBar();
tabBar->setElideMode(Qt::ElideRight);
tabBar->setContextMenuPolicy(Qt::CustomContextMenu);
connect(tabBar, SIGNAL(customContextMenuRequested(QPoint)), this,
SLOT(contextMenuRequested(QPoint)));
connect(tabBar, SIGNAL(tabCloseRequested(int)), this,
SLOT(tabCloseRequested(int)));
}
void CallWindow::initFooter()
{
leftFooter = new QLabel();
rightFooter = new QLabel();
QStatusBar *bar = statusBar();
bar->addPermanentWidget(leftFooter, 2);
bar->addPermanentWidget(rightFooter, 2);
}
void CallWindow::showExitProgramButton()
{
stepButton->setVisible(false);
fastForwardButton->setVisible(false);
}
void CallWindow::addTab(CallTab *tab)
{
tabMap[tab->getId()] = tab;
QString name = QString("[%1] %2").arg(tab->getId()).arg(tab->getName());
int index =
tabWidget->addTab(tab, stfl::shortenString(name, 20, true, true));
tabWidget->getTabBar()->setTabData(index, QVariant((int)tab->getId()));
}
size_t CallWindow::getId()
{
return id;
}
void CallWindow::removeTab(CallTab *tab)
{
tabMap.erase(tabMap.find(tab->getId()));
int index = tabWidget->indexOf(tab);
tabWidget->removeTab(index);
}
void CallWindow::removeTab(size_t tabId)
{
if (hasTab(tabId))
{
removeTab(tabMap[tabId]);
}
}
void CallWindow::showTab(CallTab *tab)
{
tabWidget->setCurrentWidget(tab);
}
void CallWindow::showTab(size_t tabId)
{
if (hasTab(tabId))
{
showTab(tabMap[tabId]);
}
}
void CallWindow::updateLeftFooter(QString newText)
{
leftFooter->setText(newText);
}
void CallWindow::updateRightFooter(QString newText)
{
rightFooter->setText(newText);
}
void CallWindow::step()
{
controller->resumeProgramExecution();
}
void CallWindow::fastForward()
{
controller->setMode(controller::Mode::FAST_FORWARD);
}
void CallWindow::closeApp()
{
controller->setMode(controller::Mode::HIDE);
}
bool CallWindow::hasTab(size_t tabId)
{
return tabMap.count(tabId);
}
void CallWindow::contextMenuRequested(const QPoint &location)
{
controller->removeEmptyWindows();
auto tabBar = tabWidget->getTabBar();
int tabIndex = tabBar->tabAt(location);
if (tabIndex == tabOffset - 1)
return;
QMenu *menu = new QMenu(this);
connect(menu, SIGNAL(triggered(QAction *)), this,
SLOT(contextMenuAction(QAction *)));
auto windows = controller->getTabWindows();
menu->addAction(new QAction("Remove call", this));
menu->addAction(new QAction("Close tab", this));
menu->addAction(new QAction("Open in new window", this));
for (auto window : windows)
{
if (window->getId() != id)
{
menu->addAction(new QAction(
QString("Open in '%1'").arg(window->windowTitle()),
this));
}
}
currentContextMenuTabId = getCallTabIdByTabIndex(tabIndex);
menu->popup(tabBar->mapToGlobal(location));
}
void CallWindow::contextMenuAction(QAction *action)
{
if (currentContextMenuTabId == -1)
{
return;
}
auto text = action->text();
if (text == "Open in new window")
{
controller->moveCallTabToNewWindow(currentContextMenuTabId);
}
else if (text == "Remove call")
{
controller->removeCallTab(currentContextMenuTabId, true, true);
}
else if (text == "Close tab")
{
controller->removeCallTab(currentContextMenuTabId);
}
else
{
auto windows = controller->getTabWindows();
for (auto window : windows)
{
if (text ==
QString("Open in '%1'").arg(window->windowTitle()))
{
controller->moveCallTabToWindow(
currentContextMenuTabId, window->getId());
break;
}
}
}
currentContextMenuTabId = -1;
}
size_t CallWindow::tabCount()
{
return tabMap.size();
}
std::vector<size_t> CallWindow::getCallTabIds()
{
std::vector<size_t> ids{};
for (auto &elem : tabMap)
{
ids.push_back(elem.first);
}
return ids;
}
void CallWindow::closeEvent(QCloseEvent *event)
{
controller->removeWindowFromMaps(id);
// FIXME: tabWidget is already freed sometimes: Use-after-free Bug
tabWidget->clear();
for (auto &elem : tabMap)
{
controller->removeCallTab(elem.first, true);
}
event->accept();
}
void CallWindow::tabCloseRequested(int index)
{
if (hasTabAtIndex(index))
{
controller->removeCallTab(getCallTabIdByTabIndex(index));
}
controller->removeEmptyWindows();
}
size_t CallWindow::getCallTabIdByTabIndex(int index)
{
if (hasTabAtIndex(index))
{
auto tabData = tabWidget->getTabBar()->tabData(index);
bool ok = true;
size_t callTabId = tabData.toInt(&ok);
if (ok && tabMap.count(callTabId) > 0)
{
return callTabId;
}
}
return 0;
}
bool CallWindow::hasTabAtIndex(int index)
{
auto tabData = tabWidget->getTabBar()->tabData(index);
return tabData != 0 && !tabData.isNull() && tabData.isValid();
}
}
}
+165
View File
@@ -0,0 +1,165 @@
#ifndef CVVISUAL_CALLWINDOW_HPP
#define CVVISUAL_CALLWINDOW_HPP
#include <vector>
#include <map>
#include <QTabWidget>
#include <QMainWindow>
#include <QString>
#include <vector>
#include <QLabel>
#include <QKeyEvent>
#include <QPoint>
#include <QCloseEvent>
#include <QPushButton>
#include "call_tab.hpp"
#include "../controller/view_controller.hpp"
#include "../util/util.hpp"
#include "tabwidget.hpp"
namespace cvv
{
namespace controller
{
class ViewController;
}
namespace gui
{
/**
* @brief Window inheriting some call tabs with in a tab widget.
*/
class CallWindow : public QMainWindow
{
Q_OBJECT
public:
/**
* @brief Contructs a new call window.
* @param controller view controller that this window belongs to
* @param id id of the window
*/
CallWindow(util::Reference<controller::ViewController> controller,
size_t id);
/**
* @brief Shows an "Exit program" button.
*/
void showExitProgramButton();
/**
* @brief Add a new tab to the inherited tab widget.
* @param tab new tab
*/
void addTab(CallTab *tab);
/**
* @brief Get the id of this window.
* @return id of this window.
*/
size_t getId();
/**
* @brief Remove the given tab from this window.
* @param given tab to remove
*/
void removeTab(CallTab *tab);
/**
* @brief Remove the given tab from this window.
* @param id of the given tab
*/
void removeTab(size_t tabId);
/**
* @brief Show the given tab.
* @param given tab
*/
void showTab(CallTab *tab);
/**
* @brief Show the given tab.
* @param id of the given tab
*/
void showTab(size_t tabId);
/**
* @brief Examines whether or not the given is inherited in this window.
* @param id of the given tab
*/
bool hasTab(size_t tabId);
/**
* @brief Returns the number of tabs shown in this window.
* @return number of tabs
*/
size_t tabCount();
/**
* @brief Returns the ids of the available call tabs.
* @return available call tabs' ids
*/
std::vector<size_t> getCallTabIds();
public slots:
/**
* @brief Update the left footer with the given text.
* @param newText given text
*/
void updateLeftFooter(QString newText);
/**
* @brief Update the right footer with the given text.
* @param newText given text
*/
void updateRightFooter(QString newText);
private slots:
void contextMenuRequested(const QPoint &location);
void contextMenuAction(QAction *action);
void tabCloseRequested(int index);
void step();
void fastForward();
void closeApp();
protected:
size_t id;
util::Reference<controller::ViewController> controller;
TabWidget *tabWidget;
QMainWindow *window;
QPushButton *closeButton;
QPushButton *stepButton;
QPushButton *fastForwardButton;
std::map<size_t, CallTab *> tabMap;
QLabel *leftFooter;
QLabel *rightFooter;
int currentContextMenuTabId = -1;
int tabOffset = 0;
void initMenu();
void initTabs();
void initFooter();
void closeEvent(QCloseEvent *event) CV_OVERRIDE;
size_t getCallTabIdByTabIndex(int index);
bool hasTabAtIndex(int index);
};
}
}
#endif
+80
View File
@@ -0,0 +1,80 @@
#ifndef CVVISUAL_FILTER_CALL_TAB_HPP
#define CVVISUAL_FILTER_CALL_TAB_HPP
#include <QString>
#include <QWidget>
#include "multiview_call_tab.hpp"
#include "../view/filter_view.hpp"
#include "../impl/filter_call.hpp"
namespace cvv
{
namespace gui
{
/** Filter Call Tab.
* @brief Inner part of a tab, contains a FilterView.
* The inner part of a tab or window
* containing a FilterView.
* Allows to switch views and to access the help.
*/
class FilterCallTab
: public MultiViewCallTab<cvv::view::FilterView, cvv::impl::FilterCall>
{
Q_OBJECT
public:
/**
* @brief Short constructor named after the Call, using the requested View
* from the Call or, if no or invalid request, default view.
* Initializes the FilterCallTab with the requested or default view and names it
* after the associated FilterCall.
* @param filterCall - the FilterCall containing the information to be
* visualized.
*/
FilterCallTab(const cvv::impl::FilterCall &filterCall)
: FilterCallTab{
filterCall, filterCall.requestedView()
}
{
}
/**
* @brief Constructor with possibility to select view.
* Note that the default view is still created first.
* @param call - the MatchCall containing the information to be
* visualized.
* @param filterViewId - ID of the View to be set up. If a view of this name does
* not exist, the default view will be used.
*/
FilterCallTab(const cvv::impl::FilterCall &filterCall, const QString& filterViewId)
: MultiViewCallTab<cvv::view::FilterView, cvv::impl::FilterCall>{
filterCall, filterViewId, QString{ "default_filter_view" }, QString{ "DefaultFilterView" }
}
{
}
~FilterCallTab()
{
}
/**
* @brief Register the template class to the map of FilterViews.
* View needs to offer a constructor of the form View(const
* cvv::impl::FilterCall&, QWidget*).
* @param name to register the class under.
* @tparam View - Class to register.
* @return true when the view was registered and false when the name was
* already taken.
*/
template <class View>
static bool registerFilterView(const QString &name)
{
return registerView<View>(name);
}
};
}
} // namespaces
#endif
+75
View File
@@ -0,0 +1,75 @@
#include <QString>
#include <QPushButton>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QLabel>
#include "image_call_tab.hpp"
#include "../view/image_view.hpp"
#include "../controller/view_controller.hpp"
#include "../impl/single_image_call.hpp"
#include "../qtutil/util.hpp"
namespace cvv
{
namespace gui
{
ImageCallTab::ImageCallTab(const cvv::impl::SingleImageCall &call)
: imageCall_{ call }
{
setName(imageCall_->description());
createGui();
}
ImageCallTab::ImageCallTab(const QString &tabName,
const cvv::impl::SingleImageCall &call)
: imageCall_{ call }
{
setName(tabName);
createGui();
}
void ImageCallTab::helpButtonClicked() const
{
cvv::qtutil::openHelpBrowser("SingleImageView");
}
size_t ImageCallTab::getId() const
{
return imageCall_->getId();
}
void ImageCallTab::createGui()
{
hlayout_ = new QHBoxLayout{ this };
hlayout_->setAlignment(Qt::AlignTop);
hlayout_->addWidget(new QLabel{ "Single Image View" });
helpButton_ = new QPushButton{ "Help", this };
hlayout_->addWidget(helpButton_);
connect(helpButton_, SIGNAL(clicked()), this,
SLOT(helpButtonClicked()));
upperBar_ = new QWidget{ this };
upperBar_->setLayout(hlayout_);
vlayout_ = new QVBoxLayout{ this };
vlayout_->addWidget(upperBar_);
setView();
setLayout(vlayout_);
imageView_->showFullImage();
}
void ImageCallTab::setView()
{
imageView_ = new cvv::view::ImageView{ imageCall_->mat(), this };
vlayout_->addWidget(imageView_);
}
}
} // namespaces
+93
View File
@@ -0,0 +1,93 @@
#ifndef CVVISUAL_IMAGE_CALL_TAB_HPP
#define CVVISUAL_IMAGE_CALL_TAB_HPP
#include <QHBoxLayout>
#include <QString>
#include <QPushButton>
#include <QVBoxLayout>
#include <QWidget>
#include "call_tab.hpp"
#include "../view/image_view.hpp"
#include "../controller/view_controller.hpp"
#include "../impl/single_image_call.hpp"
namespace cvv
{
namespace gui
{
/** Single Image Call Tab.
* @brief Inner part of a tab, contains an IageView.
* The inner part of a tab or window
* containing an ImageView.
* Allows to access the help.
*/
class ImageCallTab : public CallTab
{
Q_OBJECT
public:
/**
* @brief Short constructor named after the Call.
* Initializes the ImageCallTab and names it after the associated
* FilterCall.
* @param call the SingleImageCall containing the information to be
* visualized.
*/
ImageCallTab(const cvv::impl::SingleImageCall &call);
/**
* @brief Constructor using default view.
* Short constructor..
* @param tabName.
* @param call the SingleImageCall containing the information to be
* visualized.
* @attention might be deleted.
*/
ImageCallTab(const QString &tabName,
const cvv::impl::SingleImageCall &call);
/**
* @brief get ID.
* @return the ID of the CallTab.
* (ID is equal to the ID of the associated call).
* Overrides CallTab's getId.
*/
size_t getId() const override;
private
slots:
/**
* @brief Help Button clicked.
* Called when the help button is clicked.
*/
void helpButtonClicked() const;
private:
/**
* @brief Sets up the visible parts.
* Called by the constructors.
*/
void createGui();
/**
* @brief sets up View referred to by viewId.
* @param viewId ID of the view to be set.
* @throw std::out_of_range if no view named viewId was registered.
*/
void setView();
util::Reference<const cvv::impl::SingleImageCall> imageCall_;
cvv::view::ImageView *imageView_;
QPushButton *helpButton_;
QHBoxLayout *hlayout_;
QVBoxLayout *vlayout_;
QWidget *upperBar_;
};
}
} // namespaces
#endif
+38
View File
@@ -0,0 +1,38 @@
#include "main_call_window.hpp"
#include <QApplication>
#include <QPoint>
#include "../util/util.hpp"
#include "../stfl/stringutils.hpp"
namespace cvv
{
namespace gui
{
MainCallWindow::MainCallWindow(
util::Reference<controller::ViewController> controller, size_t id,
OverviewPanel *ovPanel)
: CallWindow(controller, id), ovPanel{ ovPanel }
{
tabOffset = 1;
QString name = "Overview";
tabWidget->insertTab(0, ovPanel, name);
auto *tabBar = tabWidget->getTabBar();
tabBar->tabButton(0, QTabBar::RightSide)->hide();
setWindowTitle(QString("CVVisual | main window"));
}
void MainCallWindow::showOverviewTab()
{
tabWidget->setCurrentWidget(ovPanel);
}
void MainCallWindow::closeEvent(QCloseEvent *event)
{
(void)event;
controller->setMode(controller::Mode::HIDE);
}
}
}
+66
View File
@@ -0,0 +1,66 @@
#ifndef CVVISUAL_MAINCALLWINDOW_HPP
#define CVVISUAL_MAINCALLWINDOW_HPP
#include <memory>
#include <QCloseEvent>
#include "call_window.hpp"
#include "overview_panel.hpp"
#include "../controller/view_controller.hpp"
#include "../util/util.hpp"
namespace cvv
{
namespace controller
{
class ViewController;
}
namespace gui
{
class OverviewPanel;
/**
* @brief A call window also inheriting the overview panel.
*/
class MainCallWindow : public CallWindow
{
Q_OBJECT
public:
/**
* @brief Constructs a new main call window.
* @param controller view controller inheriting this main window
* @param id id of this main window
* @param ovPanel inherited overview panel
*/
MainCallWindow(util::Reference<controller::ViewController> controller,
size_t id, OverviewPanel *ovPanel);
~MainCallWindow()
{
}
/**
* @brief Show the overview tab.
*/
void showOverviewTab();
/**
* @brief Hides the close window.
*/
void hideCloseWindow();
protected:
void closeEvent(QCloseEvent *event) CV_OVERRIDE;
private:
OverviewPanel *ovPanel;
};
}
}
#endif
+107
View File
@@ -0,0 +1,107 @@
#ifndef CVVISUAL_MATCH_CALL_TAB_HPP
#define CVVISUAL_MATCH_CALL_TAB_HPP
#include <memory>
#include <QString>
#include <QWidget>
#include "multiview_call_tab.hpp"
#include "../view/match_view.hpp"
#include "../impl/match_call.hpp"
#include "../util/util.hpp"
namespace cvv
{
namespace gui
{
/** Match Call Tab.
* @brief Inner part of a tab, contains a MatchView.
* The inner part of a tab or window
* containing a MatchView.
* Allows to switch views and to access the help.
*/
class MatchCallTab
: public MultiViewCallTab<cvv::view::MatchView, cvv::impl::MatchCall>
{
Q_OBJECT
public:
/**
* @brief Short constructor named after Call and using the requested View
* from the Call or, if no or invalid request, default view.
* Initializes the MatchCallTab with the requested or default view and names it after
* the associated MatchCall.
* @param matchCall - the MatchCall containing the information to be
* visualized.
*/
MatchCallTab(const cvv::impl::MatchCall &matchCall)
: MatchCallTab{
matchCall, matchCall.requestedView()
}
{
}
/**
* @brief Constructor with possibility to select view.
* Note that the default view is still created first.
* @param matchCall - the MatchCall containing the information to be
* visualized.
* @param matchViewId - ID of the View to be set up. If a view of this name does
* not exist, the default view will be used.
*/
MatchCallTab(const cvv::impl::MatchCall& matchCall, const QString& matchViewId)
: MultiViewCallTab<cvv::view::MatchView, cvv::impl::MatchCall>{
matchCall, matchViewId, QString{ "default_match_view" }, QString{ "LineMatchView" }
}
{
oldView_ = view_;
connect(&this->viewSet, SIGNAL(signal()), this, SLOT(viewChanged()));
}
~MatchCallTab()
{
}
/**
* @brief Register the template class to the map of MatchViews.
* View needs to offer a constructor of the form View(const
* cvv::impl::MatchCall&, QWidget*).
* @param name to register the class under.
* @tparam View - Class to register.
* @return true when the view was registered and false when the name was
* already taken.
*/
template <class View> static bool registerMatchView(const QString &name)
{
return registerView<View>(name);
}
private slots:
/**
* @brief Slot called when the view has completely changed.
*/
void viewChanged()
{
if(oldView_ != nullptr)
{
view_->setKeyPointSelection(oldView_->getKeyPointSelection());
view_->setMatchSelection(oldView_->getMatchSelection());
}
oldView_ = view_;
}
private:
/**
* @brief usually equal to view_, but not immediately changed when view_ is changed.
*/
cvv::view::MatchView* oldView_;
};
}
} // namespaces
#endif
+260
View File
@@ -0,0 +1,260 @@
#ifndef CVVISUAL_MULTIVIEW_CALL_TAB_HPP
#define CVVISUAL_MULTIVIEW_CALL_TAB_HPP
#include <vector>
#include <memory>
#include <QObject>
#include <QString>
#include <QMap>
#include <QPushButton>
#include <QComboBox>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QLabel>
#include "call_tab.hpp"
#include "../util/util.hpp"
#include "../qtutil/registerhelper.hpp"
#include "../qtutil/signalslot.hpp"
#include "../qtutil/util.hpp"
namespace cvv
{
namespace gui
{
/** Call Tab for multiple views.
* @brief Inner part of a tab, contains a View.
* The inner part of a tab or window
* containing a View.
* Allows to switch between different views and to access the help.
* @tparam ViewType A type of View.
* @tparam CallType A type of Call.
*/
template <class ViewType, class CallType>
class MultiViewCallTab
: public CallTab,
public cvv::qtutil::RegisterHelper<ViewType, const CallType &, QWidget *>
{
public:
/**
* @brief Short constructor named after Call and using the default view.
* Initializes the MultiViewCallTab with the default view and names it after
* the associated Call.
* @param call - the Call containing the information to be
* visualized.
* @param default_key - Key under which the default view is to be saved.
* @param standard_default - Standard default view.
*/
MultiViewCallTab(const CallType &call, const QString& default_key, const QString& standard_default)
: MultiViewCallTab{ call.description(), call, default_key, standard_default }
{
}
/**
* @brief Constructor using the default view.
* Initializes the MultiViewCallTab with the default view.
* @param name - Name to give the CallTab.
* @param call - the Call containing the information to be
* visualized.
* @param default_key - Key under which the default view is to be saved.
* @param standard_default - Standard default view.
*/
MultiViewCallTab(const QString &tabName, const CallType &call, const QString& default_key, const QString& standard_default)
: call_{ call }, currentIndexChanged{ [&]()
{
vlayout_->removeWidget(view_);
view_->setVisible(false);
setView();
} },
helpButtonClicked{ [&]()
{ qtutil::openHelpBrowser(viewId_); } },
setAsDefaultButtonClicked{ [&]()
{ qtutil::setSetting(default_scope_, default_key_, viewId_); } }
{
setName(tabName);
default_scope_ = QString{ "default_views" };
default_key_ = default_key;
standard_default_ = standard_default;
// Sets standard_default_ as default in case no other default is
// set:
qtutil::setDefaultSetting(default_scope_, default_key_,
standard_default_);
viewId_ = qtutil::getSetting(default_scope_, default_key_);
createGui();
}
/**
* @brief Constructor with possibility to select view.
* Note that the default view is still created first.
* @param call - the Call containing the information to be
* visualized.
* @param viewId - ID of the View to be set up. If a view of this name does
* not exist, the default view will be used.
* @param default_key - Key under which the default view is to be saved.
* @param standard_default - Standard default view.
*/
MultiViewCallTab(const CallType& call, const QString& viewId, const QString& default_key, const QString& standard_default)
: MultiViewCallTab{call, default_key, standard_default}
{
this->select(viewId);
}
~MultiViewCallTab()
{
}
/**
* @brief get ID.
* @return the ID of the CallTab.
* (ID is equal to the ID of the associated call).
* Overrides CallTab's getId.
*/
size_t getId() const override
{
return call_->getId();
}
/**
* @brief Register the template class to the map of Views.
* View needs to offer a constructor of the form View(const
* cvv::impl::CallType&, QWidget*).
* @param name to register the class under.
* @tparam View - Class to register.
* @return true when the view was registered and false when the name was
* already taken.
*/
template <class View> static bool registerView(const QString &name)
{
return MultiViewCallTab<ViewType, CallType>::registerElement(
name, [](const CallType &call, QWidget *parent)
{
return cvv::util::make_unique<View>(call, parent);
});
}
protected:
/**
* @brief Scope to search the default view in.
*/
QString default_scope_;
/**
* @brief Key under which the default view is saved.
*/
QString default_key_;
/**
* @brief standard default view.
*/
QString standard_default_;
/**
* @brief Sets up the visible parts.
* Called by the constructors.
*/
void createGui()
{
if (!this->select(viewId_))
{
this->select(standard_default_);
viewId_ = this->selection();
setAsDefaultButtonClicked.slot(); // Set as default.
/* If viewId_ does not name a valid View, it will be
* attempted to set standard_default_.
* If that was not registered either, the current
* selection of the ComboBox will be used automatically.
* Whichever was chosen will be set as the new default.
*/
}
hlayout_ = new QHBoxLayout{};
hlayout_->setAlignment(Qt::AlignTop | Qt::AlignRight);
hlayout_->addWidget(new QLabel{ "View:" });
hlayout_->addWidget(this->comboBox_);
setAsDefaultButton_ = new QPushButton{ "Set as default", this };
hlayout_->addWidget(setAsDefaultButton_);
helpButton_ = new QPushButton{ "Help", this };
hlayout_->addWidget(helpButton_);
upperBar_ = new QWidget{ this };
upperBar_->setLayout(hlayout_);
vlayout_ = new QVBoxLayout{};
vlayout_->addWidget(upperBar_);
setView();
setLayout(vlayout_);
QObject::connect(setAsDefaultButton_, SIGNAL(clicked()),
&setAsDefaultButtonClicked, SLOT(slot()));
QObject::connect(helpButton_, SIGNAL(clicked()),
&helpButtonClicked, SLOT(slot()));
QObject::connect(&this->signalElementSelected(),
SIGNAL(signal(QString)), &currentIndexChanged,
SLOT(slot()));
}
/**
* @brief sets up the View currently selected in the ComboBox inherited
* from RegisterHelper.
*/
void setView()
{
viewId_ = this->selection();
if (viewHistory_.count(this->selection()))
{
view_ = viewHistory_.at(this->selection());
vlayout_->addWidget(view_);
view_->setVisible(true);
}
else
{
viewHistory_.emplace(
this->selection(),
((*this)()(*call_, this).release()));
view_ = viewHistory_.at(this->selection());
vlayout_->addWidget(view_);
}
viewSet.emitSignal();
}
util::Reference<const CallType> call_;
QString viewId_;
ViewType *view_;
std::map<QString, ViewType *> viewHistory_;
QPushButton *helpButton_;
QPushButton *setAsDefaultButton_;
QHBoxLayout *hlayout_;
QVBoxLayout *vlayout_;
QWidget *upperBar_;
//signals:
/**
* @brief signal emitted whem view is completely set up.
*/
qtutil::Signal viewSet;
// slots:
/**
* @brief View selection change.
* Slot called when the index of the view selection changes.
*/
qtutil::Slot currentIndexChanged;
/**
* @brief Help Button clicked.
* Called when the help button is clicked.
*/
const qtutil::Slot helpButtonClicked;
/**
* @brief setAsDefaultButton clicked.
* Called when the setAsDefaultButton,which sets the current view as
* default, is clicked.
*/
qtutil::Slot setAsDefaultButtonClicked;
};
}
} // namespaces
#endif
@@ -0,0 +1,318 @@
#include "overview_group_subtable.hpp"
#include <utility>
#include <algorithm>
#include <sstream>
#include <QVBoxLayout>
#include <QStringList>
#include <QModelIndex>
#include <QMenu>
#include <QAction>
#include <QHeaderView>
#include <QList>
#include <QSize>
#include "call_window.hpp"
#include "overview_table.hpp"
#include "../controller/view_controller.hpp"
namespace cvv
{
namespace gui
{
OverviewGroupSubtable::OverviewGroupSubtable(
util::Reference<controller::ViewController> controller,
OverviewTable *parent, stfl::ElementGroup<OverviewTableRow> group)
: controller{ controller }, parent{ parent }, group{ std::move(group) }
{
controller->setDefaultSetting("overview", "imgsize",
QString::number(100));
initUI();
}
void OverviewGroupSubtable::initUI()
{
controller->setDefaultSetting("overview", "imgzoom", "30");
qTable = new QTableWidget(this);
qTable->setSelectionBehavior(QAbstractItemView::SelectRows);
qTable->setSelectionMode(QAbstractItemView::SingleSelection);
qTable->setTextElideMode(Qt::ElideNone);
auto verticalHeader = qTable->verticalHeader();
verticalHeader->setVisible(false);
auto horizontalHeader = qTable->horizontalHeader();
horizontalHeader->setSectionResizeMode(QHeaderView::ResizeToContents);
horizontalHeader->setStretchLastSection(false);
connect(qTable, SIGNAL(cellDoubleClicked(int, int)), this,
SLOT(rowClicked(int, int)));
qTable->setContextMenuPolicy(Qt::CustomContextMenu);
connect(qTable, SIGNAL(customContextMenuRequested(QPoint)), this,
SLOT(customMenuRequested(QPoint)));
auto *layout = new QVBoxLayout;
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(qTable);
setLayout(layout);
updateUI();
}
void OverviewGroupSubtable::updateUI()
{
imgSize = controller->getSetting("overview", "imgzoom").toInt() *
width() / 400;
QStringList list{};
list << "ID";
maxImages = 0;
for (auto element : group.getElements())
{
if (maxImages < element.call()->matrixCount())
{
maxImages = element.call()->matrixCount();
}
}
if (parent->isShowingImages())
{
for (auto element : group.getElements())
{
if (maxImages < element.call()->matrixCount())
{
maxImages = element.call()->matrixCount();
}
}
for (size_t i = 0; i < maxImages; i++)
{
list << QString("Image ") + QString::number(i + 1);
}
}
list << "Description"
<< "Function"
<< "File"
<< "Line"
<< "Type";
qTable->setRowCount(group.size());
qTable->setColumnCount(list.size());
qTable->setHorizontalHeaderLabels(list);
int textRowHeight = qTable->fontMetrics().height() + 5;
if (textRowHeight >= imgSize)
{
qTable->setVerticalScrollMode(QAbstractItemView::ScrollPerItem);
}
else
{
qTable->setVerticalScrollMode(
QAbstractItemView::ScrollPerPixel);
}
rowHeight = std::max(imgSize, textRowHeight);
for (size_t i = 0; i < group.size(); i++)
{
group.get(i).addToTable(qTable, i, parent->isShowingImages(),
maxImages, imgSize, imgSize);
qTable->setRowHeight(i, rowHeight);
}
auto header = qTable->horizontalHeader();
header->setSectionResizeMode(0, QHeaderView::ResizeToContents);
for (size_t i = 1; i < maxImages + 1; i++)
{
header->setSectionResizeMode(i, QHeaderView::ResizeToContents);
}
for (size_t i = maxImages + 1; i < maxImages + 4; i++)
{
header->setSectionResizeMode(i, QHeaderView::Stretch);
}
header->setSectionResizeMode(maxImages + 4,
QHeaderView::ResizeToContents);
header->setSectionResizeMode(maxImages + 5,
QHeaderView::ResizeToContents);
updateMinimumSize();
}
void OverviewGroupSubtable::rowClicked(int row, int collumn)
{
(void)collumn;
size_t tabId = group.get(row).id();
controller->showAndOpenCallTab(tabId);
}
void OverviewGroupSubtable::customMenuRequested(QPoint location)
{
if (qTable->rowCount() == 0)
{
return;
}
controller->removeEmptyWindows();
QMenu *menu = new QMenu(this);
auto windows = controller->getTabWindows();
menu->addAction(new QAction("Open in new window", this));
for (auto window : windows)
{
menu->addAction(new QAction(
QString("Open in '%1'").arg(window->windowTitle()), this));
}
menu->addAction(new QAction("Remove call", this));
QModelIndex index = qTable->indexAt(location);
if (!index.isValid())
{
return;
}
int row = index.row();
QString idStr = qTable->item(row, 0)->text();
connect(menu, SIGNAL(triggered(QAction *)), this,
SLOT(customMenuAction(QAction *)));
std::stringstream{ idStr.toStdString() } >> currentCustomMenuCallTabId;
currentCustomMenuCallTabIdValid = true;
menu->popup(mapToGlobal(location));
}
void OverviewGroupSubtable::customMenuAction(QAction *action)
{
if (!currentCustomMenuCallTabIdValid)
{
return;
}
QString actionText = action->text();
if (actionText == "Open in new window")
{
controller->moveCallTabToNewWindow(currentCustomMenuCallTabId);
currentCustomMenuCallTabId = -1;
return;
}
else if (actionText == "Remove call")
{
controller->removeCallTab(currentCustomMenuCallTabId, true,
true);
currentCustomMenuCallTabId = -1;
return;
}
auto windows = controller->getTabWindows();
for (auto window : windows)
{
if (actionText ==
QString("Open in '%1'").arg(window->windowTitle()))
{
controller->moveCallTabToWindow(
currentCustomMenuCallTabId, window->getId());
break;
}
}
currentCustomMenuCallTabId = -1;
}
void OverviewGroupSubtable::resizeEvent(QResizeEvent *event)
{
(void)event;
imgSize = controller->getSetting("overview", "imgzoom").toInt() *
width() / 400;
rowHeight = std::max(imgSize, qTable->fontMetrics().height() + 5);
for (size_t row = 0; row < group.size(); row++)
{
group.get(row).resizeInTable(qTable, row,
parent->isShowingImages(), maxImages,
imgSize, imgSize);
qTable->setRowHeight(row, rowHeight);
}
updateMinimumSize();
event->accept();
}
void OverviewGroupSubtable::removeRow(size_t id)
{
for (size_t i = 0; i < group.size(); i++)
{
if (group.get(i).id() == id)
{
group.removeElement(i);
updateUI();
break;
}
}
}
bool OverviewGroupSubtable::hasRow(size_t id)
{
for (size_t i = 0; i < group.size(); i++)
{
if (group.get(i).id() == id)
{
return true;
}
}
return false;
}
void OverviewGroupSubtable::setRowGroup(
stfl::ElementGroup<OverviewTableRow> &newGroup)
{
auto compFunc = [](const OverviewTableRow &first,
const OverviewTableRow &second)
{ return first.id() == second.id(); };
if (group.hasSameElementList(newGroup, compFunc))
{
return;
}
// Now both groups aren't the same
size_t newMax = 0;
for (auto row : newGroup.getElements())
{
if (row.call()->matrixCount() > newMax)
{
newMax = row.call()->matrixCount();
}
}
if (newMax == maxImages)
{
group = newGroup;
updateUI();
return;
}
// Now both groups have the same maximum number of images within their
// elements
size_t minLength = std::min(group.size(), newGroup.size());
for (size_t i = 0; i < minLength; i++)
{
if (group.get(i).id() != newGroup.get(i).id())
{
group = newGroup;
updateUI();
return;
}
}
// Now the bigger group's element lists starts with the smaller group's
// one
if (group.size() < newGroup.size())
{
// Now the new group appends the current group
for (size_t row = group.size(); row < newGroup.size(); row++)
{
newGroup.get(row)
.addToTable(qTable, row, parent->isShowingImages(),
maxImages, imgSize, imgSize);
qTable->setRowHeight(row, rowHeight);
}
}
else
{
// Now the new group deletes elements from the current group
for (size_t row = group.size() - 1; row >= newGroup.size();
row--)
{
qTable->removeRow(row);
}
}
group = newGroup;
updateMinimumSize();
}
void OverviewGroupSubtable::updateMinimumSize()
{
int width = qTable->sizeHint().width();
setMinimumWidth(width);
int height = qTable->horizontalHeader()->height();
for (int a = 0; a < qTable->rowCount(); ++a)
{
height += qTable->rowHeight(a);
}
setMinimumHeight(height + (qTable->rowCount() * 1));
}
}
}
@@ -0,0 +1,106 @@
#ifndef CVVISUAL_OVERVIEW_GROUP_SUBTABLE_HPP
#define CVVISUAL_OVERVIEW_GROUP_SUBTABLE_HPP
#include <memory>
#include <QWidget>
#include <QTableWidget>
#include <QAction>
#include <QResizeEvent>
#include "../stfl/element_group.hpp"
#include "overview_table_row.hpp"
#include "../util/util.hpp"
#include "../controller/view_controller.hpp"
namespace cvv
{
namespace controller
{
class ViewController;
}
}
namespace cvv
{
namespace gui
{
class OverviewTable;
/**
* @brief A table for the a group of overview data sets.
*/
class OverviewGroupSubtable : public QWidget
{
Q_OBJECT
public:
/**
* @brief Constructs an over group subtable.
* @param controller view controller
* @param parent parent table
* @param group the displayed group of overview data sets
*/
OverviewGroupSubtable(
util::Reference<controller::ViewController> controller,
OverviewTable *parent, stfl::ElementGroup<OverviewTableRow> group);
~OverviewGroupSubtable()
{
}
/**
* @brief Updates the displayed table UI.
*/
void updateUI();
/**
* @brief Remove the row with the given id.
* @param given table row id
*/
void removeRow(size_t id);
/**
* @brief Checks whether or not the table shows the row with the given
* id.
* @param id given row id
* @return Does the table show the row with the given id?
*/
bool hasRow(size_t id);
/**
* @brief Set the displayed rows.
* @note This method does some optimisations to only fully rebuild all
* rows if necessary.
* @param newGroup new group of rows that will be displayed
*/
void setRowGroup(stfl::ElementGroup<OverviewTableRow> &newGroup);
protected:
void resizeEvent(QResizeEvent *event) CV_OVERRIDE;
private slots:
void rowClicked(int row, int collumn);
void customMenuRequested(QPoint location);
void customMenuAction(QAction *action);
private:
util::Reference<controller::ViewController> controller;
OverviewTable *parent;
stfl::ElementGroup<OverviewTableRow> group;
QTableWidget *qTable;
size_t currentCustomMenuCallTabId = 0;
bool currentCustomMenuCallTabIdValid = false;
size_t maxImages = 0;
int imgSize = 0;
int rowHeight = 0;
void initUI();
void updateMinimumSize();
};
}
}
#endif
+214
View File
@@ -0,0 +1,214 @@
#include "overview_panel.hpp"
#include <functional>
#include <math.h>
#include <memory>
#include <iostream>
#include <QMap>
#include <QSet>
#include <QString>
#include <QVBoxLayout>
#include <QWidget>
#include <QScrollArea>
#include "../controller/view_controller.hpp"
#include "../qtutil/stfl_query_widget.hpp"
#include "../qtutil/util.hpp"
#include "../stfl/element_group.hpp"
namespace cvv
{
namespace gui
{
OverviewPanel::OverviewPanel(
util::Reference<controller::ViewController> controller)
: controller{ controller }
{
qtutil::setDefaultSetting("overview", "imgzoom", "20");
QVBoxLayout *layout = new QVBoxLayout{};
setLayout(layout);
layout->setContentsMargins(0, 0, 0, 0);
queryWidget = new qtutil::STFLQueryWidget();
layout->addWidget(queryWidget);
table = new OverviewTable(controller);
layout->addWidget(table);
auto bottomArea = new QWidget{ this };
auto bottomLayout = new QHBoxLayout;
imgSizeSliderLabel = new QLabel{ "Zoom", bottomArea };
imgSizeSliderLabel->setMaximumWidth(50);
imgSizeSliderLabel->setAlignment(Qt::AlignRight);
bottomLayout->addWidget(imgSizeSliderLabel);
imgSizeSlider = new QSlider{ Qt::Horizontal, bottomArea };
imgSizeSlider->setMinimumWidth(50);
imgSizeSlider->setMaximumWidth(200);
imgSizeSlider->setMinimum(0);
imgSizeSlider->setMaximum(100);
imgSizeSlider->setSliderPosition(
qtutil::getSetting("overview", "imgzoom").toInt());
connect(imgSizeSlider, SIGNAL(valueChanged(int)), this,
SLOT(imgSizeSliderAction()));
bottomLayout->addWidget(imgSizeSlider);
bottomArea->setLayout(bottomLayout);
layout->addWidget(bottomArea);
initEngine();
connect(queryWidget, SIGNAL(showHelp(QString)), this,
SLOT(showHelp(QString)));
// connect(queryWidget, SIGNAL(userInputUpdate(QString)), this,
// SLOT(updateQuery(QString)));
connect(queryWidget, SIGNAL(filterSignal(QString)), this,
SLOT(filterQuery(QString)));
connect(queryWidget, SIGNAL(requestSuggestions(QString)), this,
SLOT(requestSuggestions(QString)));
}
void OverviewPanel::initEngine()
{
// raw and description filter
auto rawFilter = [](const OverviewTableRow &elem)
{
return elem.description();
};
queryEngine.addStringCmdFunc("raw", rawFilter, false);
queryEngine.addStringCmdFunc("description", rawFilter, false);
// file filter
queryEngine.addStringCmdFunc("file", [](const OverviewTableRow &elem)
{
return elem.file();
});
// function filter
queryEngine.addStringCmdFunc("function",
[](const OverviewTableRow &elem)
{
return elem.function();
});
// line filter
queryEngine.addIntegerCmdFunc("line", [](const OverviewTableRow &elem)
{ return elem.line(); });
// id filter
queryEngine.addIntegerCmdFunc("id", [](const OverviewTableRow &elem)
{
return elem.id();
});
// type filter
queryEngine.addStringCmdFunc("type", [](const OverviewTableRow &elem)
{
return elem.type();
});
//"number of images" filter
queryEngine.addIntegerCmdFunc("image_count",
[](const OverviewTableRow &elem)
{
return elem.call()->matrixCount();
});
//additional commands
//open call command
queryEngine.addAdditionalCommand("open",
[&](QStringList args, std::vector<stfl::ElementGroup<OverviewTableRow>>& groups)
{
openCommand(args, groups);
}, {"first_of_group", "last_of_group", "shown"});
}
void OverviewPanel::openCommand(QStringList args,
std::vector<stfl::ElementGroup<OverviewTableRow>>& groups)
{
if (args.contains("shown"))
{
for (auto &group : groups)
{
for (auto &elem : group.getElements())
{
controller->openCallTab(elem.id());
}
}
return;
}
if (args.contains("first_of_group") || args.contains("last_of_group"))
{
bool first = args.contains("first_of_group") ;
for (auto &group : groups)
{
if (group.size() > 0)
{
size_t index = first ? 0 : group.size() - 1;
size_t id = group.get(index).id();
controller->openCallTab(id);
}
}
}
}
void OverviewPanel::addElement(const util::Reference<const impl::Call> newCall)
{
OverviewTableRow row(newCall);
queryEngine.addNewElement(row);
table->updateRowGroups(queryEngine.reexecuteLastQuery());
}
void OverviewPanel::addElementBuffered(const util::Reference<const impl::Call> newCall)
{
elementBuffer.push_back(newCall);
}
void OverviewPanel::flushElementBuffer()
{
std::vector<OverviewTableRow> rows;
for (const util::Reference<const impl::Call> call : elementBuffer)
{
rows.push_back(OverviewTableRow(call));
}
queryEngine.addElements(std::move(rows));
table->updateRowGroups(queryEngine.reexecuteLastQuery());
elementBuffer.clear();
}
void OverviewPanel::removeElement(size_t id)
{
queryEngine.removeElements([id](OverviewTableRow elem)
{
return elem.id() == id;
});
table->removeElement(id);
}
void OverviewPanel::filterQuery(QString query)
{
table->updateRowGroups(queryEngine.query(query));
}
void OverviewPanel::updateQuery(QString query)
{
filterQuery(query);
}
void OverviewPanel::requestSuggestions(QString query)
{
queryWidget->showSuggestions(queryEngine.getSuggestions(query));
}
void OverviewPanel::imgSizeSliderAction()
{
controller->setSetting("overview", "imgzoom",
QString::number(imgSizeSlider->value()));
table->updateUI();
}
void OverviewPanel::showHelp(QString topic)
{
controller->openHelpBrowser(topic);
}
}
}
+113
View File
@@ -0,0 +1,113 @@
#ifndef CVVISUAL_OVERVIEWPANEL_HPP
#define CVVISUAL_OVERVIEWPANEL_HPP
#include <vector>
#include <QWidget>
#include <QString>
#include <QSlider>
#include <QLabel>
#include <QPushButton>
#include "../stfl/stfl_engine.hpp"
#include "../impl/call.hpp"
#include "overview_table.hpp"
#include "overview_table_row.hpp"
#include "../util/util.hpp"
#include "../controller/view_controller.hpp"
namespace cvv
{
namespace controller
{
class ViewController;
}
namespace qtutil
{
class STFLQueryWidget;
}
namespace gui
{
class OverviewTable;
class OverviewTableRow;
/**
* @brief The overview showing a filterable table displaying the different
* calls.
*/
class OverviewPanel : public QWidget
{
Q_OBJECT
public:
/**
* @brief Contructs an OverviewPanel.
* @param controller ViewController that inherits this overview
*/
OverviewPanel(util::Reference<controller::ViewController> controller);
/**
* @brief Adds the given call to the shown overview table.
* @param newCall given call
*/
void addElement(const util::Reference<const impl::Call> newCall);
/**
* @brief Changes the "Resume program execution" button label to "Exit
* Application."
*/
void showExitApplicationButton();
/**
* @brief Adds the given call buffered to the shown overview table.
* @note Be sure to flush the buffer via flushElementBuffer() later.
* @param newCall given call
*/
void addElementBuffered(const util::Reference<const impl::Call> newCall);
/**
* @brief Flushes the element buffer and shows its elements in the overview table.
*/
void flushElementBuffer();
/**
* @brief Removes and deletes the element with the given id.
* @param id given element id
*/
void removeElement(size_t id);
private slots:
void filterQuery(QString query);
void updateQuery(QString query);
void requestSuggestions(QString query);
void imgSizeSliderAction();
void showHelp(QString topic);
private:
stfl::STFLEngine<OverviewTableRow> queryEngine{"Overview"};
qtutil::STFLQueryWidget *queryWidget;
OverviewTable *table;
util::Reference<controller::ViewController> controller;
QLabel *imgSizeSliderLabel;
QSlider *imgSizeSlider;
std::vector<util::Reference<const impl::Call>> elementBuffer;
void initEngine();
void openCommand(QStringList args,
std::vector<stfl::ElementGroup<OverviewTableRow>>& groups);
};
}
}
#endif
+125
View File
@@ -0,0 +1,125 @@
#include "overview_table.hpp"
#include <utility>
#include <algorithm>
#include <QVBoxLayout>
#include <QStringList>
#include "../stfl/element_group.hpp"
#include "overview_table_row.hpp"
#include "overview_group_subtable.hpp"
#include "../qtutil/accordion.hpp"
namespace cvv
{
namespace gui
{
OverviewTable::OverviewTable(
util::Reference<controller::ViewController> controller)
: controller{ controller }
{
subtableAccordion = new qtutil::Accordion{};
auto *layout = new QVBoxLayout{};
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(subtableAccordion);
setLayout(layout);
}
void OverviewTable::updateRowGroups(
std::vector<stfl::ElementGroup<OverviewTableRow>> newGroups)
{
bool startTheSame = true;
for (size_t i = 0; i < std::min(groups.size(), newGroups.size()); i++)
{
if (!newGroups.at(i).hasSameTitles(groups.at(i)))
{
startTheSame = false;
break;
}
}
if (startTheSame && groups.size() <= newGroups.size())
{
for (size_t i = 0;
i < std::min(groups.size(), newGroups.size()); i++)
{
subTables.at(i)->setRowGroup(newGroups.at(i));
subTables.at(i)->updateUI();
}
for (size_t i = groups.size(); i < newGroups.size(); i++)
{
appendRowGroupToTable(newGroups.at(i));
subTables.at(i)->setRowGroup(newGroups.at(i));
}
}
else
{
subtableAccordion->clear();
subTables.clear();
for (auto &group : newGroups)
{
appendRowGroupToTable(group);
}
}
groups = newGroups;
}
void OverviewTable::hideImages()
{
doesShowImages = false;
updateUI();
}
void OverviewTable::showImages()
{
doesShowImages = true;
updateUI();
}
bool OverviewTable::isShowingImages()
{
return doesShowImages;
}
void OverviewTable::updateUI()
{
for (auto *subTable : subTables)
{
subTable->updateUI();
}
}
void OverviewTable::removeElement(size_t id)
{
for (auto *subTable : subTables)
{
if (subTable->hasRow(id))
{
subTable->removeRow(id);
break;
}
}
}
void
OverviewTable::appendRowGroupToTable(stfl::ElementGroup<OverviewTableRow> group)
{
if (group.size() > 0)
{
auto subtable = util::make_unique<OverviewGroupSubtable>(
controller, this, std::move(group));
auto subtablePtr = subtable.get();
auto titles = group.getTitles();
QString title =
"No grouping specified, use #group to specify one";
if (titles.size() != 0)
{
title = titles.join(", ");
}
subtableAccordion->push_back(title, std::move(subtable), false);
subTables.push_back(subtablePtr);
}
}
}
}
+90
View File
@@ -0,0 +1,90 @@
#ifndef CVVISUAL_OVERVIEWTABLE_HPP
#define CVVISUAL_OVERVIEWTABLE_HPP
#include <vector>
#include <QWidget>
#include <QList>
#include "overview_panel.hpp"
#include "overview_table_row.hpp"
#include "../stfl/element_group.hpp"
#include "../qtutil/accordion.hpp"
#include "../util/util.hpp"
#include "../controller/view_controller.hpp"
#include "overview_group_subtable.hpp"
namespace cvv
{
namespace gui
{
class OverviewPanel;
class OverviewTableRow;
/**
* @brief A table displaying the different calls in the overview.
* It's actually an accordion of subtables to support grouping.
*/
class OverviewTable : public QWidget
{
Q_OBJECT
public:
/**
* @brief Constructs a new OverviewTable.
* @param controller it's ViewController
*/
OverviewTable(util::Reference<controller::ViewController> controller);
~OverviewTable()
{
}
/**
* @brief Update the inherited groups of rows and rebuild the UI fully.
* @param newGroups new groups for this table
*/
void updateRowGroups(
const std::vector<stfl::ElementGroup<OverviewTableRow>> newGroups);
/**
* @brief Hide the thumbnail images in the tables.
*/
void hideImages();
/**
* @brief Show thumbnail images in the tables.
*/
void showImages();
/**
* @brief Does this the tables show thumbnail images?
*/
bool isShowingImages();
/**
* @brief Updates the UI.
* Updates all subtables.
*/
void updateUI();
/**
* @brief Removes the table element with the given id.
* @param id given element id
*/
void removeElement(size_t id);
private:
util::Reference<controller::ViewController> controller;
bool doesShowImages = true;
qtutil::Accordion *subtableAccordion;
std::vector<OverviewGroupSubtable *> subTables{};
std::vector<stfl::ElementGroup<OverviewTableRow>> groups;
void appendRowGroupToTable(stfl::ElementGroup<OverviewTableRow> group);
};
}
}
#endif
+104
View File
@@ -0,0 +1,104 @@
#include "overview_table_row.hpp"
#include <algorithm>
#include <memory>
#include <QTableWidgetItem>
#include <QImage>
#include "../qtutil/util.hpp"
#include "../stfl/stringutils.hpp"
namespace cvv
{
namespace gui
{
OverviewTableRow::OverviewTableRow(util::Reference<const impl::Call> call)
: call_{ call }
{
id_ = call_->getId();
idStr = QString::number(call_->getId());
for (size_t i = 0; i < 2 && i < call->matrixCount(); i++)
{
QPixmap img;
std::tie(std::ignore, img) =
qtutil::convertMatToQPixmap(call->matrixAt(i));
imgs.push_back(std::move(img));
}
description_ = QString(call_->description());
if (call_->metaData().isKnown)
{
const auto &data = call_->metaData();
line_ = data.line;
lineStr = QString::number(data.line);
fileStr = data.file;
functionStr = data.function;
}
typeStr = QString(call_->type());
}
void OverviewTableRow::addToTable(QTableWidget *table, size_t row,
bool showImages, size_t maxImages,
int imgHeight, int imgWidth)
{
std::vector<std::unique_ptr<QTableWidgetItem>> items{};
items.push_back(util::make_unique<QTableWidgetItem>(idStr));
if (showImages)
{
for (size_t i = 0; i < imgs.size() && i < maxImages; i++)
{
auto imgWidget = util::make_unique<QTableWidgetItem>("");
imgWidget->setData(
Qt::DecorationRole,
imgs.at(i).scaled(imgHeight, imgWidth,
Qt::KeepAspectRatio,
Qt::SmoothTransformation));
imgWidget->setTextAlignment(Qt::AlignHCenter);
items.push_back(std::move(imgWidget));
}
}
size_t emptyImagesToAdd =
showImages ? maxImages - std::min(maxImages, imgs.size())
: maxImages;
for (size_t i = 0; i < emptyImagesToAdd; i++)
{
items.push_back(util::make_unique<QTableWidgetItem>(""));
}
items.push_back(util::make_unique<QTableWidgetItem>(description_));
items.push_back(util::make_unique<QTableWidgetItem>(functionStr, 30));
items.push_back(util::make_unique<QTableWidgetItem>(fileStr));
items.push_back(util::make_unique<QTableWidgetItem>(lineStr));
items.push_back(util::make_unique<QTableWidgetItem>(typeStr));
for (size_t i = 0; i < items.size(); i++)
{
items[i]->setFlags(items[i]->flags() ^ Qt::ItemIsEditable);
table->setItem(row, i, items[i].release());
}
}
void OverviewTableRow::resizeInTable(QTableWidget *table, size_t row,
bool showImages, size_t maxImages,
int imgHeight, int imgWidth)
{
if (showImages)
{
for (size_t i = 0; i < imgs.size() && i < maxImages; i++)
{
auto imgWidget = util::make_unique<QTableWidgetItem>("");
imgWidget->setData(
Qt::DecorationRole,
imgs.at(i).scaled(imgHeight, imgWidth,
Qt::KeepAspectRatio,
Qt::SmoothTransformation));
imgWidget->setTextAlignment(Qt::AlignHCenter);
imgWidget->setFlags(imgWidget->flags() ^ Qt::ItemIsEditable);
table->setItem(row, i + 1, imgWidget.release());
}
}
}
}
}
+143
View File
@@ -0,0 +1,143 @@
#ifndef CVVISUAL_OVERVIEWTABLEROW_HPP
#define CVVISUAL_OVERVIEWTABLEROW_HPP
#include <vector>
#include <QTableWidget>
#include <QString>
#include <QPixmap>
#include "../impl/call.hpp"
#include "../util/util.hpp"
namespace cvv
{
namespace gui
{
/**
* @brief A UI wrapper for an impl::Call, providing utility and UI functions.
*
* Also allowing it to add its data to a table.
*/
class OverviewTableRow
{
public:
/**
* @brief Constructor of this class.
* @param call call this row is based on
*/
OverviewTableRow(util::Reference<const impl::Call> call);
~OverviewTableRow()
{
}
/**
* @brief Adds the inherited data set to the given table.
* @param table given table
* @param row row index at which the data will be shown
* @param showImages does the table show images?
* @param maxImages the maximum number of images the table shows
* @param imgHeight height of the shown images
* @param imgWidth width of the shown images
*/
void addToTable(QTableWidget *table, size_t row, bool showImages,
size_t maxImages, int imgHeight = 100,
int imgWidth = 100);
/**
* @brief Resizes the images in the given row.
* Make sure to call this after (!) you called addToTable() with the
* same row parameter on this object some time.
* @param table given table
* @param row row index at which the data will be shown
* @param showImages does the table show images?
* @param maxImages the maximum number of images the table shows
* @param imgHeight height of the shown images
* @param imgWidth width of the shown images
*/
void resizeInTable(QTableWidget *table, size_t row, bool showImages,
size_t maxImages, int imgHeight = 100,
int imgWidth = 100);
/**
* @brief Get the inherited call.
* @return the inherited call
*/
util::Reference<const impl::Call> call() const
{
return call_;
}
/**
* @brief Returns the description of the inherited call.
* @return description of the inherited call.
*/
QString description() const
{
return description_;
}
/**
* @brief Returns the id of the inherited call.
* @return id of the inherited call.
*/
size_t id() const
{
return id_;
}
/**
* @brief Returns the function name property of the inherited call.
* @return function name property of the inherited call.
*/
QString function() const
{
return functionStr;
}
/**
* @brief Returns the file name of the inherited call.
* @return file name of the inherited call.
*/
QString file() const
{
return fileStr;
}
/**
* @brief Returns the line property of the inherited call.
* @return line property of the inherited call.
*/
size_t line() const
{
return line_;
}
/**
* @brief Returns the type of the inherited call.
* @return type of the inherited call.
*/
QString type() const
{
return typeStr;
}
private:
util::Reference<const impl::Call> call_;
size_t id_ = 0;
size_t line_ = 0;
QString idStr = "";
QString description_ = "";
std::vector<QPixmap> imgs{};
QString functionStr = "";
QString fileStr = "";
QString lineStr = "";
QString typeStr = "";
};
}
}
#endif
@@ -0,0 +1,301 @@
#include "rawview_group_subtable.hpp"
#include <utility>
#include <algorithm>
#include <opencv2/core.hpp>
#include <opencv2/features.hpp>
#include <QVBoxLayout>
#include <QStringList>
#include <QModelIndexList>
#include <QModelIndex>
#include <QMenu>
#include <QAction>
#include <QHeaderView>
#include <QApplication>
#include <QClipboard>
#include <QTableWidgetSelectionRange>
#include "call_window.hpp"
#include "../view/rawview.hpp"
#include "rawview_table.hpp"
#include "../controller/view_controller.hpp"
namespace cvv
{
namespace gui
{
RawviewGroupSubtable::RawviewGroupSubtable(
RawviewTable *parent, stfl::ElementGroup<RawviewTableRow> group)
: parent{ parent }, group{ std::move(group) }
{
qTable = new QTableWidget(this);
qTable->setSelectionBehavior(QAbstractItemView::SelectRows);
qTable->setSelectionMode(QAbstractItemView::ExtendedSelection);
auto horizontalHeader = qTable->horizontalHeader();
horizontalHeader->setSectionResizeMode(QHeaderView::Stretch);
horizontalHeader->setStretchLastSection(false);
qTable->setContextMenuPolicy(Qt::CustomContextMenu);
connect(qTable, SIGNAL(customContextMenuRequested(QPoint)), this,
SLOT(customMenuRequested(QPoint)));
connect(qTable, SIGNAL(itemSelectionChanged()), this,
SLOT(selectionChanged()));
auto *layout = new QVBoxLayout;
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(qTable);
setLayout(layout);
QStringList list{};
if (group.getTitles().contains("single key point"))
{
list << "x"
<< "y"
<< "size"
<< "angle"
<< "response"
<< "octave"
<< "class id"
<< "img number";
}
else
{
list << "match distance"
<< "img idx"
<< "query idx"
<< "train idx";
list << "key point 1 x"
<< "y 1"
<< "size 1"
<< "angle 1"
<< "response 1"
<< "octave 1"
<< "class id 1";
list << "key point 2 x"
<< "y 2"
<< "size 2"
<< "angle 2"
<< "response 2"
<< "octave 2"
<< "class id 2";
}
qTable->setColumnCount(list.size());
qTable->setHorizontalHeaderLabels(list);
updateUI();
}
void RawviewGroupSubtable::updateUI()
{
qTable->setRowCount(group.size());
for (size_t i = 0; i < group.size(); i++)
{
group.get(i).addToTable(qTable, i);
}
}
void RawviewGroupSubtable::selectionChanged()
{
QModelIndexList indexList = qTable->selectionModel()->selectedIndexes();
currentRowIndexes.clear();
for (QModelIndex index : indexList)
{
if (index.isValid())
{
auto row = index.row();
if (row < qTable->rowCount() && row >= 0)
{
currentRowIndexes.insert(row);
}
}
}
}
void RawviewGroupSubtable::customMenuRequested(QPoint location)
{
QModelIndex index = qTable->indexAt(location);
if (!index.isValid())
{
return;
}
QMenu *menu = new QMenu(this);
connect(menu, SIGNAL(triggered(QAction *)), this,
SLOT(customMenuAction(QAction *)));
if (parent->getParent()->doesShowShowInViewMenu())
{
menu->addAction(new QAction("Show selected rows in view", this));
}
auto formats = RawviewTableRow::getAvailableTextFormats();
for (auto format : formats)
{
menu->addAction(
new QAction(QString("Copy as %1").arg(format), this));
}
int row = index.row();
if (currentRowIndexes.size() == 0)
{
currentRowIndexes = { row };
}
menu->popup(qTable->viewport()->mapToGlobal(location));
}
void RawviewGroupSubtable::customMenuAction(QAction *action)
{
bool single = group.getTitles().contains("single key point");
if (currentRowIndexes.size() > 0)
{
auto rows = getSelectedRows();
QString text = action->text();
if (text == "Show selected rows in view")
{
if (single)
{
std::vector<cv::KeyPoint> keyPoints;
for (auto row : rows)
{
keyPoints.push_back(row.getKeyPoint1());
}
parent->getParent()->keyPointsSelected(
keyPoints);
std::vector<cv::DMatch> emptyVec{};
parent->getParent()->matchesSelected(emptyVec); //unselect matches
}
else
{
std::vector<cv::DMatch> matches;
for (auto row : rows)
{
matches.push_back(row.getMatch());
}
parent->getParent()->matchesKeyPointsSelected(matches);
}
}
else
{
auto formats =
RawviewTableRow::getAvailableTextFormats();
for (auto format : formats)
{
if (text == QString("Copy as %1").arg(format))
{
QString formattedRows;
formattedRows =
RawviewTableRow::rowsToText(
rows, format, single);
QApplication::clipboard()->setText(
formattedRows);
break;
}
}
}
}
}
std::vector<cv::DMatch> RawviewGroupSubtable::getMatchSelection()
{
std::vector<cv::DMatch> matches;
for (RawviewTableRow row : getSelectedRows())
{
if (!row.hasSingleKeyPoint())
{
matches.push_back(row.getMatch());
}
}
return matches;
}
std::vector<cv::KeyPoint> RawviewGroupSubtable::getKeyPointSelection()
{
std::vector<cv::KeyPoint> keyPoints;
for (RawviewTableRow row : getSelectedRows())
{
if (row.hasSingleKeyPoint())
{
keyPoints.push_back(row.getKeyPoint1());
}
}
return keyPoints;
}
void RawviewGroupSubtable::setMatchSelection(std::vector<cv::DMatch> matches)
{
std::set<int> indexes;
for (size_t i = 0; i < group.size(); i++)
{
RawviewTableRow elem = group.get(i);
if (!elem.hasSingleKeyPoint())
{
for (auto &match : matches)
{
if (match.distance == elem.matchDistance() &&
match.imgIdx == elem.matchImgIdx() &&
match.queryIdx == elem.matchQueryIdx() &&
match.trainIdx == elem.matchTrainIdx())
{
indexes.insert(i);
break;
}
}
}
}
setSelectedRows(indexes);
}
void RawviewGroupSubtable::setKeyPointSelection(std::vector<cv::KeyPoint> keyPoints)
{
std::set<int> indexes;
for (size_t i = 0; i < group.size(); i++)
{
RawviewTableRow elem = group.get(i);
if (elem.hasSingleKeyPoint())
{
for (auto &keyPoint : keyPoints)
{
if (keyPoint.pt.x == elem.keyPoint1XCoord() &&
keyPoint.pt.y == elem.keyPoint1YCoord() &&
keyPoint.size == elem.keyPoint1Size() &&
keyPoint.angle == elem.keyPoint1Angle() &&
keyPoint.response == elem.keyPoint1Response() &&
keyPoint.octave == elem.keyPoint1Octave() &&
keyPoint.class_id == elem.keyPoint1ClassId())
{
indexes.insert(i);
break;
}
}
}
}
setSelectedRows(indexes);
}
std::vector<RawviewTableRow> RawviewGroupSubtable::getSelectedRows()
{
std::vector<RawviewTableRow> rows;
for (auto index : currentRowIndexes)
{
if (index < qTable->rowCount() && index >= 0)
{
rows.push_back(group.get(index));
}
}
return rows;
}
void RawviewGroupSubtable::setSelectedRows(std::set<int> rowIndexes)
{
currentRowIndexes = rowIndexes;
QTableWidgetSelectionRange clearSelectionRange(0, 0, qTable->rowCount(), qTable->columnCount());
qTable->setRangeSelected(clearSelectionRange, false);
for (int i : rowIndexes)
{
QTableWidgetSelectionRange range(i, 0, i + 1, qTable->columnCount());
qTable->setRangeSelected(range, true);
}
}
}
}
@@ -0,0 +1,83 @@
#ifndef CVVISUAL_RAWVIEW_GROUP_SUBTABLE_HPP
#define CVVISUAL_RAWVIEW_GROUP_SUBTABLE_HPP
#include <memory>
#include <set>
#include <vector>
#include <QWidget>
#include <QTableWidget>
#include <QAction>
#include <QItemSelection>
#include "../stfl/element_group.hpp"
#include "rawview_table_row.hpp"
#include "../util/util.hpp"
namespace cvv
{
namespace controller
{
class ViewController;
}
}
namespace cvv
{
namespace gui
{
class RawviewTable;
/**
* @brief A table for the a group of overview data sets.
*/
class RawviewGroupSubtable : public QWidget
{
Q_OBJECT
public:
/**
* @brief Constructs an over group subtable.
* @param controller view controller
* @param parent parent table
* @param group the displayed group of overview data sets
*/
RawviewGroupSubtable(RawviewTable *parent,
stfl::ElementGroup<RawviewTableRow> group);
/**
* @brief Updates the displayed table UI.
*/
void updateUI();
std::vector<cv::DMatch> getMatchSelection();
std::vector<cv::KeyPoint> getKeyPointSelection();
public slots:
void setMatchSelection(std::vector<cv::DMatch> matches);
void setKeyPointSelection(std::vector<cv::KeyPoint> keyPoints);
private slots:
void customMenuRequested(QPoint location);
void customMenuAction(QAction *action);
void selectionChanged();
private:
RawviewTable *parent;
stfl::ElementGroup<RawviewTableRow> group;
QTableWidget *qTable;
std::set<int> currentRowIndexes;
std::vector<RawviewTableRow> getSelectedRows();
void setSelectedRows(std::set<int> rowIndexes);
};
}
}
#endif
+99
View File
@@ -0,0 +1,99 @@
#include "rawview_table.hpp"
#include <utility>
#include <QVBoxLayout>
#include <QStringList>
#include "../stfl/element_group.hpp"
#include "rawview_table_row.hpp"
#include "rawview_group_subtable.hpp"
#include "../qtutil/accordion.hpp"
namespace cvv
{
namespace gui
{
RawviewTable::RawviewTable(view::Rawview *parent) : parent{ parent }
{
subtableAccordion = new qtutil::Accordion{};
auto *layout = new QVBoxLayout{};
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(subtableAccordion);
setLayout(layout);
}
void RawviewTable::updateRowGroups(
std::vector<stfl::ElementGroup<RawviewTableRow>> newGroups)
{
subtableAccordion->clear();
subTables.clear();
for (auto &group : newGroups)
{
if (group.size() > 0)
{
auto subtable = util::make_unique<RawviewGroupSubtable>(
this, std::move(group));
auto subtablePtr = subtable.get();
auto titles = group.getTitles();
QString title =
"No grouping specified, use #group to do specify";
if (titles.size() != 0)
{
title = titles.join(", ");
}
subtableAccordion->push_back(title, std::move(subtable),
false);
subTables.push_back(subtablePtr);
}
}
}
void RawviewTable::updateUI()
{
for (auto *subTable : subTables)
{
subTable->updateUI();
}
}
std::vector<cv::DMatch> RawviewTable::getMatchSelection()
{
std::vector<cv::DMatch> matches;
for (auto subTable : subTables)
{
auto subMatches = subTable->getMatchSelection();
matches.insert(matches.end(), subMatches.begin(), subMatches.end());
}
return matches;
}
std::vector<cv::KeyPoint> RawviewTable::getKeyPointSelection()
{
std::vector<cv::KeyPoint> keyPoints;
for (auto subTable : subTables)
{
auto subKeyPoints = subTable->getKeyPointSelection();
keyPoints.insert(keyPoints.end(), subKeyPoints.begin(), subKeyPoints.end());
}
return keyPoints;
}
void RawviewTable::setMatchSelection(std::vector<cv::DMatch> matches)
{
for (auto subTable : subTables)
{
subTable->setMatchSelection(matches);
}
}
void RawviewTable::setKeyPointSelection(std::vector<cv::KeyPoint> keyPoints)
{
for (auto subTable : subTables)
{
subTable->setKeyPointSelection(keyPoints);
}
}
}
}
+83
View File
@@ -0,0 +1,83 @@
#ifndef CVVISUAL_RAWVIEWTABLE_HPP
#define CVVISUAL_RAWVIEWTABLE_HPP
#include <vector>
#include <QWidget>
#include <QList>
#include "../view/rawview.hpp"
#include "rawview_table_row.hpp"
#include "../stfl/element_group.hpp"
#include "../qtutil/accordion.hpp"
#include "../util/util.hpp"
#include "rawview_group_subtable.hpp"
namespace cvv
{
namespace view
{
class Rawview;
}
namespace gui
{
class RawviewTableCollumn;
/**
* @brief A table (consisting of subtables) displaying raw match data.
*/
class RawviewTable : public QWidget
{
Q_OBJECT
public:
/**
* @brief Constructor of this class.
* @param parent parent view
*/
RawviewTable(view::Rawview *parent);
/**
* @brief Update the inherited groups of rows and rebuild the UI fully.
* @param newGroups new groups for this table
*/
void updateRowGroups(
const std::vector<stfl::ElementGroup<RawviewTableRow>> newGroups);
/**
* @brief Updates the UI
*/
void updateUI();
/**
* @brief Returns the parent view.
* @return parent view
*/
view::Rawview *getParent()
{
return parent;
}
std::vector<cv::DMatch> getMatchSelection();
std::vector<cv::KeyPoint> getKeyPointSelection();
public slots:
void setMatchSelection(std::vector<cv::DMatch> matches);
void setKeyPointSelection(std::vector<cv::KeyPoint> keyPoints);
private:
view::Rawview *parent;
qtutil::Accordion *subtableAccordion;
std::vector<RawviewGroupSubtable *> subTables{};
};
}
}
#endif
+325
View File
@@ -0,0 +1,325 @@
#include "rawview_table_row.hpp"
#include <functional>
#include <iostream>
#include <utility>
#include <QTableWidgetItem>
#include <QImage>
#include "../qtutil/util.hpp"
#include "../stfl/stringutils.hpp"
#include <set>
namespace cvv
{
namespace gui
{
RawviewTableRow::RawviewTableRow(cv::DMatch match, cv::KeyPoint keyPoint1,
cv::KeyPoint keyPoint2)
: match{ match }, keyPoint1{ keyPoint1 }, keyPoint2{ keyPoint2 },
hasLonelyKeyPoint_{ false }
{
}
RawviewTableRow::RawviewTableRow(cv::KeyPoint keyPoint, bool left)
: keyPoint1{ keyPoint }, hasLonelyKeyPoint_{ true }, left{ left }
{
}
void RawviewTableRow::addToTable(QTableWidget *table, size_t row)
{
std::vector<QString> items;
if (!hasLonelyKeyPoint_)
{
items = { QString::number(matchDistance()),
QString::number(matchImgIdx()),
QString::number(matchQueryIdx()),
QString::number(matchTrainIdx()),
QString::number(keyPoint1XCoord()),
QString::number(keyPoint1YCoord()),
QString::number(keyPoint1Size()),
QString::number(keyPoint1Angle()),
QString::number(keyPoint1Response()),
QString::number(keyPoint1Octave()),
QString::number(keyPoint1ClassId()),
QString::number(keyPoint2XCoord()),
QString::number(keyPoint2YCoord()),
QString::number(keyPoint2Size()),
QString::number(keyPoint2Angle()),
QString::number(keyPoint2Response()),
QString::number(keyPoint2Octave()),
QString::number(keyPoint2ClassId()) };
}
else
{
items = { QString::number(keyPoint1XCoord()),
QString::number(keyPoint1YCoord()),
QString::number(keyPoint1Size()),
QString::number(keyPoint1Angle()),
QString::number(keyPoint1Response()),
QString::number(keyPoint1Octave()),
QString::number(keyPoint1ClassId()),
QString::number(left ? 1 : 2) };
}
for (size_t i = 0; i < items.size(); i++)
{
auto *item = new QTableWidgetItem(items[i]);
item->setFlags(item->flags() ^ Qt::ItemIsEditable);
table->setItem(row, i, item);
}
}
QString RawviewTableRow::rowsToText(const std::vector<RawviewTableRow> &rows,
const QString format,
bool singleKeyPointRows)
{
QStringList lines;
// header
if (format == "CSV")
{
QStringList header;
if (singleKeyPointRows)
{
header << "x"
<< "y"
<< "size"
<< "angle"
<< "response"
<< "octave"
<< "class id";
}
else
{
header << "match distance"
<< "img idx"
<< "query idx"
<< "train idx"
<< "key point 1 x"
<< "y 1"
<< "size 1"
<< "angle 1"
<< "response 1"
<< "octave 1"
<< "class id 1"
<< "key point 2 x"
<< "y 2"
<< "size 2"
<< "angle 2"
<< "response 2"
<< "octave 2"
<< "class id 2";
}
lines << header.join(",");
}
// These functions are simple macros
auto intstr = [](int number)
{ return QString::number(number); };
auto floatstr = [](float number)
{ return QString::number(number); };
// combine a name and a value string
auto combinestr = [format](QString name, QString value)
{
QString fmtStr = "";
if (format == "JSON")
{
fmtStr = "\"%1\": %2";
}
else if (format == "PYTHON")
{
fmtStr = "'%1': %2";
}
else if (format == "RUBY")
{
fmtStr = "\"%1\" => %2";
}
return fmtStr.arg(name, value);
};
// data sets
for (auto &row : rows)
{
if (format == "CSV" && !singleKeyPointRows)
{
QStringList line;
line << floatstr(row.matchDistance())
<< intstr(row.matchImgIdx())
<< intstr(row.matchQueryIdx())
<< intstr(row.matchTrainIdx())
<< floatstr(row.keyPoint1XCoord())
<< floatstr(row.keyPoint1YCoord())
<< floatstr(row.keyPoint1Size())
<< floatstr(row.keyPoint1Angle())
<< floatstr(row.keyPoint1Response())
<< intstr(row.keyPoint1Octave())
<< floatstr(row.keyPoint1ClassId())
<< floatstr(row.keyPoint2XCoord())
<< floatstr(row.keyPoint2YCoord())
<< floatstr(row.keyPoint2Size())
<< floatstr(row.keyPoint2Angle())
<< floatstr(row.keyPoint2Response())
<< intstr(row.keyPoint2Octave())
<< floatstr(row.keyPoint2ClassId());
lines << line.join(",");
}
else if ((format == "JSON" || format == "PYTHON" ||
format == "RUBY") &&
!singleKeyPointRows)
{
QStringList match;
match << combinestr("match distance",
floatstr(row.matchDistance()))
<< combinestr("img idx",
intstr(row.matchImgIdx()))
<< combinestr("query idx",
intstr(row.matchQueryIdx()))
<< combinestr("train idx",
intstr(row.matchTrainIdx()));
QStringList keyPoint1;
keyPoint1
<< combinestr("x", floatstr(row.keyPoint1XCoord()))
<< combinestr("y", floatstr(row.keyPoint1YCoord()))
<< combinestr("size", floatstr(row.keyPoint1Size()))
<< combinestr("angle",
floatstr(row.keyPoint1Angle()))
<< combinestr("response",
floatstr(row.keyPoint1Response()))
<< combinestr("octave",
intstr(row.keyPoint1Octave()))
<< combinestr("class id",
floatstr(row.keyPoint1ClassId()));
QStringList keyPoint2;
keyPoint2
<< combinestr("x", floatstr(row.keyPoint2XCoord()))
<< combinestr("y", floatstr(row.keyPoint2YCoord()))
<< combinestr("size", floatstr(row.keyPoint2Size()))
<< combinestr("angle",
floatstr(row.keyPoint2Angle()))
<< combinestr("response",
floatstr(row.keyPoint2Response()))
<< combinestr("octave",
intstr(row.keyPoint2Octave()))
<< combinestr("class id",
floatstr(row.keyPoint2ClassId()));
lines << QString("{%1, %2, %3}").arg(
combinestr("match", QString("{%1}").arg(
match.join(", "))),
combinestr("key point 1",
QString("{%1}")
.arg(keyPoint1.join(", "))),
combinestr("key point 2",
QString("{%1}").arg(
keyPoint2.join(", "))));
}
if (format == "CSV" && singleKeyPointRows)
{
QStringList line;
line << floatstr(row.keyPoint1XCoord())
<< floatstr(row.keyPoint1YCoord())
<< floatstr(row.keyPoint1Size())
<< floatstr(row.keyPoint1Angle())
<< floatstr(row.keyPoint1Response())
<< intstr(row.keyPoint1Octave())
<< floatstr(row.keyPoint1ClassId())
<< intstr(row.isLeftSingleKeyPoint() ? 1 : 2);
lines << line.join(",");
}
else if ((format == "JSON" || format == "PYTHON" ||
format == "RUBY") &&
singleKeyPointRows)
{
QStringList keyPoint;
keyPoint
<< combinestr("x", floatstr(row.keyPoint1XCoord()))
<< combinestr("y", floatstr(row.keyPoint1YCoord()))
<< combinestr("size", floatstr(row.keyPoint1Size()))
<< combinestr("angle",
floatstr(row.keyPoint1Angle()))
<< combinestr("response",
floatstr(row.keyPoint1Response()))
<< combinestr("octave",
intstr(row.keyPoint1Octave()))
<< combinestr("class id",
floatstr(row.keyPoint1ClassId()))
<< combinestr(
"img number",
intstr(row.isLeftSingleKeyPoint() ? 1 : 2));
lines << QString("{%1}").arg(keyPoint.join(", "));
}
}
QString text = "";
// join the data sets with the header to a single block of text
if (format == "CSV")
{
text = lines.join("\r\n"); // see RFC 4180
}
else if (format == "JSON" || format == "PYTHON" || format == "RUBY")
{
text = QString("[%1]").arg(lines.join(",\r\n"));
}
return text;
}
std::vector<QString> RawviewTableRow::getAvailableTextFormats()
{
return { QString("CSV"), QString("JSON"),
QString("PYTHON"), QString("RUBY") };
}
std::pair<QList<RawviewTableRow>, QList<RawviewTableRow>>
createRawviewTableRows(const std::vector<cv::KeyPoint> &keyPoints1,
const std::vector<cv::KeyPoint> &keyPoints2,
const std::vector<cv::DMatch> &matches,
bool usesTrainDescriptor)
{
QList<RawviewTableRow> matchRowList;
QList<RawviewTableRow> singleRowList;
std::set<size_t> usedKeyPoints1;
std::set<size_t> usedKeyPoints2;
for (auto &match : matches)
{
int leftIndex = match.queryIdx;
int rightIndex = usesTrainDescriptor ? match.trainIdx : match.imgIdx;
if (leftIndex >= (int)keyPoints1.size() || rightIndex >= (int)keyPoints2.size())
{
continue;
}
usedKeyPoints1.insert(match.queryIdx);
usedKeyPoints2.insert(match.trainIdx);
matchRowList.append(
RawviewTableRow(match, keyPoints1.at(leftIndex),
keyPoints2.at(rightIndex)));
}
for (size_t i = 0; i < usedKeyPoints1.size(); i++)
{
if (usedKeyPoints1.find(i) != usedKeyPoints1.end())
{
singleRowList.push_back(
RawviewTableRow(keyPoints1.at(i), true));
}
}
for (size_t i = 0; i < usedKeyPoints2.size(); i++)
{
if (usedKeyPoints2.find(i) != usedKeyPoints2.end())
{
singleRowList.push_back(
RawviewTableRow(keyPoints2.at(i), false));
}
}
return std::make_pair(matchRowList, singleRowList);
}
QList<RawviewTableRow>
createSingleKeyPointRawviewTableRows(const std::vector<cv::KeyPoint> &keyPoints,
bool left)
{
auto retList = QList<RawviewTableRow>();
for (auto &keyPoint : keyPoints)
{
retList.append(RawviewTableRow(keyPoint, left));
}
return retList;
}
}
}
+245
View File
@@ -0,0 +1,245 @@
#ifndef CVVISUAL_RAWVIEWTABLEROW_HPP
#define CVVISUAL_RAWVIEWTABLEROW_HPP
#include <vector>
#include <utility>
#include <opencv2/core.hpp>
#include <opencv2/features.hpp>
#include <QTableWidget>
#include <QString>
#include <QList>
namespace cvv
{
namespace gui
{
/**
* @brief A simple container wrapper for the cv::DMatch and cv::KeyPoint class.
* See the opencv documentation for more information on the getter methods.
*/
class RawviewTableRow
{
public:
/**
* @brief Constructor of this class.
* @param match match that this row inherits.
* @param keyPoint1 "left" key point of the match
* @param keyPoint2 "right" key point of the match
*/
RawviewTableRow(cv::DMatch match, cv::KeyPoint keyPoint1,
cv::KeyPoint keyPoint2);
/**
* @brief Constructor of this class for a single key point.
* The keypoint is stored as the first key point.
* @param keyPoint only key point of this object,
* @param left is the given key point is a left one?
*/
RawviewTableRow(cv::KeyPoint keyPoint, bool left = true);
/**
* @brief Add this row to the given table.
* @note It does only fills the row in the table with the given index
* with its data.
* @param table given table
* @param row given row index
*/
void addToTable(QTableWidget *table, size_t row);
float matchDistance() const
{
return match.distance;
}
int matchImgIdx() const
{
return match.imgIdx;
}
int matchQueryIdx() const
{
return match.queryIdx;
}
int matchTrainIdx() const
{
return match.trainIdx;
}
float keyPoint1XCoord() const
{
return keyPoint1.pt.x;
}
float keyPoint1YCoord() const
{
return keyPoint1.pt.y;
}
cv::Point2f keyPoint1Coords() const
{
return keyPoint1.pt;
}
float keyPoint1Size() const
{
return keyPoint1.size;
}
float keyPoint1Angle() const
{
return keyPoint1.angle;
}
float keyPoint1Response() const
{
return keyPoint1.response;
}
int keyPoint1Octave() const
{
return keyPoint1.octave;
}
int keyPoint1ClassId() const
{
return keyPoint1.class_id;
}
float keyPoint2XCoord() const
{
return keyPoint2.pt.x;
}
float keyPoint2YCoord() const
{
return keyPoint2.pt.y;
}
cv::Point2f keyPoint2Coords() const
{
return keyPoint2.pt;
}
float keyPoint2Size() const
{
return keyPoint2.size;
}
float keyPoint2Angle() const
{
return keyPoint2.angle;
}
float keyPoint2Response() const
{
return keyPoint2.response;
}
int keyPoint2Octave() const
{
return keyPoint2.octave;
}
int keyPoint2ClassId() const
{
return keyPoint2.class_id;
}
cv::DMatch getMatch() const
{
return match;
}
cv::KeyPoint getKeyPoint1() const
{
return keyPoint1;
}
cv::KeyPoint getKeyPoint2() const
{
return keyPoint2;
}
bool hasSingleKeyPoint() const
{
return hasLonelyKeyPoint_;
}
bool isLeftSingleKeyPoint() const
{
return left;
}
/**
* @brief Serealizes the given rows into a single block of text.
* The currently supported formats are:
* - `CSV` : Valid RFC 4180 CSV, with the same columns like the
* table.
* - `JSON` : Valid JSON (each row is an object consisting of three
* sub objects:
* `match`, `keypoint 1` and `keypoint 2`).
* - `PYTHON`: Valid python code (see JSON).
* - `RUBY` : Valid ruby code (see JSON).
* @param rows given rows
* @param format the format of the resulting representation (see above)
* @param singleKeyPointRows do the given rows consist of single key
* point rows?
* @return block representation of the given rows.
*/
static QString rowsToText(const std::vector<RawviewTableRow> &rows,
const QString format,
bool singleKeyPointRows = false);
/**
* @brief Returns the currently available text formats for the
* rowsToText method.
* @return {"CSV", "JSON", "PYTHON", "RUBY"}
*/
static std::vector<QString> getAvailableTextFormats();
private:
cv::DMatch match;
cv::KeyPoint keyPoint1;
cv::KeyPoint keyPoint2;
bool hasLonelyKeyPoint_;
bool left = false;
};
/**
* @brief Create a list of rows from the given key points and matches.
* And one for the single key points.
*
* It creates a row for each match and uses the key points to get the two
* locations of each one..
* @param keyPoints1 given "left" key points
* @param keyPoints2 given "right" key points
* @param matches given matches
* @param usesTrainDescriptor Use the trainIdx property of each match to get the
* "right" key points?
* @return first element is the match row list, second is the single key point
*row list
*/
std::pair<QList<RawviewTableRow>, QList<RawviewTableRow>>
createRawviewTableRows(const std::vector<cv::KeyPoint> &keyPoints1,
const std::vector<cv::KeyPoint> &keyPoints2,
const std::vector<cv::DMatch> &matches,
bool usesTrainDescriptor = true);
/**
* @brief Create a list of rows from the given key points.
* It creates a row for each key point, that does only contain this key point.
* @param keyPoints given key points
* @param left are the given key points are "left" ones?
* @return resulting list
*/
QList<RawviewTableRow>
createSingleKeyPointRawviewTableRows(const std::vector<cv::KeyPoint> &keyPoints,
bool left = true);
}
}
#endif
+42
View File
@@ -0,0 +1,42 @@
#ifndef CVVISUAL_TABWIDGET
#define CVVISUAL_TABWIDGET
#include <QTabWidget>
#include <QTabBar>
namespace cvv
{
namespace gui
{
/**
* @brief A simple to QTabWidget Subclass, enabling the access to protected
* members.
*/
class TabWidget : public QTabWidget
{
public:
/**
* @brief Constructor of this class.
*/
TabWidget(QWidget *parent) : QTabWidget(parent)
{
};
/**
* @brief Returns the shown tab bar.
* This method helps to access the member tabBar which has by default
* only a protected setter.
* @return shown tab bar.
*/
QTabBar *getTabBar() const
{
return tabBar();
}
};
}
}
#endif
+28
View File
@@ -0,0 +1,28 @@
#include "call.hpp"
#include <atomic>
namespace cvv
{
namespace impl
{
size_t newCallId()
{
static std::atomic_size_t nextId (1);
return nextId++;
}
Call::Call() : metaData_{}, id{ newCallId() }, calltype{}
{
}
Call::Call(impl::CallMetaData callData, QString type, QString description,
QString requestedView)
: metaData_{ std::move(callData) }, id{ newCallId() },
calltype{ std::move(type) }, description_{ std::move(description) },
requestedView_{ std::move(requestedView) }
{
}
}
} // namespaces cvv::impl
+113
View File
@@ -0,0 +1,113 @@
#ifndef CVVISUAL_CALL_HPP
#define CVVISUAL_CALL_HPP
#include <utility>
#include <QString>
#include "opencv2/core.hpp"
#include "opencv2/features.hpp"
#include "opencv2/cvv/call_meta_data.hpp"
namespace cvv
{
namespace impl
{
/**
* @brief Returns a new, unique id for calls.
*/
size_t newCallId();
/**
* @brief Baseclass for all calls. Provides access to the common functionality.
*/
class Call
{
public:
virtual ~Call()
{
}
/**
* @brief Returns the unique id of the call.
*/
size_t getId() const
{
return id;
}
/**
* Returns a string that identifies what kind of call this is.
*/
const QString &type() const
{
return calltype;
}
/**
* @brief Returns the number of images that are part of the call.
*/
virtual size_t matrixCount() const = 0;
/**
* Returns the n'th matrix that is involved in the call.
*
* @throws std::out_of_range if index is higher then matrixCount().
*/
virtual const cv::Mat &matrixAt(size_t index) const = 0;
/**
* @brief provides a description of the call.
*/
const QString &description() const
{
return description_;
}
/**
* @brief Returns a string which view was requested by the caller of the
* API.
*/
const QString &requestedView() const
{
return requestedView_;
}
/**
* @brief Provides read-access to the meta-data of the call (from where
* it came).
*/
const CallMetaData &metaData() const
{
return metaData_;
}
protected:
/**
* @brief Default-construcs a new Call with a new, unique ID.
*/
Call();
/**
* @brief Construcs a new Call with a new, unique ID and the provided
* data.
*/
Call(impl::CallMetaData callData, QString type, QString description,
QString requestedView);
Call(const Call &) = default;
Call(Call &&) = default;
impl::CallMetaData metaData_;
size_t id;
QString calltype;
QString description_;
QString requestedView_;
};
}
} // namespaces
#endif
+116
View File
@@ -0,0 +1,116 @@
#include "data_controller.hpp"
#include <stdexcept>
namespace cvv
{
namespace impl
{
namespace
{
class CallEquality
{
public:
CallEquality(size_t Id) : Id{ Id }
{
}
bool operator()(const std::unique_ptr<Call> &call) const
{
return call->getId() == Id;
}
private:
size_t Id;
};
}
void DataController::addCall(std::unique_ptr<Call> call)
{
auto ref = util::makeRef(*call);
calls.push_back(std::move(call));
viewController.addCall(ref);
callUI();
}
void DataController::removeCall(size_t Id)
{
auto it = std::find_if(calls.begin(), calls.end(), CallEquality{ Id });
if (it == calls.end())
{
throw std::invalid_argument{ "there is no call with this id" };
}
calls.erase(it);
}
const Call &DataController::getCall(size_t Id) const
{
auto it = std::find_if(calls.begin(), calls.end(), CallEquality{ Id });
if (it == calls.end())
{
throw std::invalid_argument{ "there is no call with this id" };
}
return **it;
}
Call &DataController::getCall(size_t Id)
{
auto it = std::find_if(calls.begin(), calls.end(), CallEquality{ Id });
if (it == calls.end())
{
throw std::invalid_argument{ "there is no call with this id" };
}
return **it;
}
bool DataController::hasCall(size_t Id)
{
auto it = std::find_if(calls.begin(), calls.end(), CallEquality{ Id });
return it != calls.end();
}
size_t DataController::numCalls() const
{
return calls.size();
}
void DataController::callUI()
{
viewController.exec();
}
void DataController::lastCall()
{
viewController.showExitProgramButton();
callUI();
}
/**
* @brief Actual implementation of the global DataController.
*
* This is required to be able to delete it.
*/
static std::unique_ptr<DataController> &realSingleton()
{
static std::unique_ptr<DataController> var = nullptr;
return var;
}
void deleteDataController()
{
realSingleton().reset();
}
DataController &dataController()
{
auto& controller = realSingleton();
if(!realSingleton().get())
{
controller = util::make_unique<DataController>();
}
return *controller;
}
}
} // namespaces cvv::impl
+94
View File
@@ -0,0 +1,94 @@
#ifndef CVVISUAL_DATA_CONTROLLER_HPP
#define CVVISUAL_DATA_CONTROLLER_HPP
#include <memory>
#include <vector>
#include "opencv2/core.hpp"
#include "call.hpp"
#include "../controller/view_controller.hpp"
namespace cvv
{
namespace impl
{
/**
* @brief The central controller of the debug-framework that owns all the
* calldata.
*/
class DataController
{
public:
DataController() = default;
~DataController()
{
}
/**
* Add a new call to the calls-list.
*/
void addCall(std::unique_ptr<Call> call);
/**
* Remove a call.
* @throws std::invalid_argument if no such call exists
*/
void removeCall(size_t Id);
/**
* Get read-access to a certain call.
*/
const Call &getCall(size_t Id) const;
/**
* Get read/write-access to a certain call.
*/
Call &getCall(size_t Id);
bool hasCall(size_t Id);
/**
* Get the number of currently managed calls.
*/
size_t numCalls() const;
/**
* Passes control to the View-controller.
*
* Returns when the ViewController signals that normal program-execution
*shall continue.
*
*/
void callUI();
/**
* @brief Replace the continue-buttons with close-buttons and show the
*UI.
*
* This function is intended to be called directly before main returns
*after all the actual
* work is done.
*/
void lastCall();
private:
std::vector<std::unique_ptr<Call>> calls;
controller::ViewController viewController;
};
/**
* @brief Destructs the global Singleton.
*/
void deleteDataController();
/**
* Provides access to a global DataController that is created upon the first
* call.
*/
DataController &dataController();
}
} // namespaces cvv::impl
#endif
+22
View File
@@ -0,0 +1,22 @@
#include "opencv2/cvv/dmatch.hpp"
#include "opencv2/cvv/call_meta_data.hpp"
#include "match_call.hpp"
namespace cvv
{
namespace impl
{
void debugDMatch(cv::InputArray img1, std::vector<cv::KeyPoint> keypoints1,
cv::InputArray img2, std::vector<cv::KeyPoint> keypoints2,
std::vector<cv::DMatch> matches, const CallMetaData &data,
const char *description, const char *view,
bool useTrainDescriptor)
{
debugMatchCall(img1, std::move(keypoints1), img2, std::move(keypoints2),
std::move(matches), data, description, view,
useTrainDescriptor);
}
}
} // namespaces
+18
View File
@@ -0,0 +1,18 @@
#include "opencv2/cvv/filter.hpp"
#include "opencv2/cvv/call_meta_data.hpp"
#include "filter_call.hpp"
namespace cvv
{
namespace impl
{
void debugFilter(cv::InputArray original, cv::InputArray result,
const CallMetaData &data, const char *description,
const char *view)
{
debugFilterCall(original, result, data, description, view, "filter");
}
}
} // namespaces
+45
View File
@@ -0,0 +1,45 @@
#include "filter_call.hpp"
#include "data_controller.hpp"
#include "../util/util.hpp"
namespace cvv
{
namespace impl
{
FilterCall::FilterCall(cv::InputArray in, cv::InputArray out,
impl::CallMetaData data, QString type,
QString description, QString requestedView)
: Call( data, std::move(type),
std::move(description), std::move(requestedView) ),
input_{ in.getMat().clone() }, output_{ out.getMat().clone() }
{
}
const cv::Mat &FilterCall::matrixAt(size_t index) const
{
switch (index)
{
case 0:
return original();
case 1:
return result();
default:
throw std::out_of_range{ "" };
}
}
void debugFilterCall(cv::InputArray original, cv::InputArray result,
const CallMetaData &data, const char *description,
const char *view, const char *filter)
{
dataController().addCall(util::make_unique<FilterCall>(
original, result, data, filter,
description ? QString::fromLocal8Bit(description)
: QString{ "<no description>" },
view ? QString::fromLocal8Bit(view) : QString{}));
}
}
} // namespaces cvv::impl
+66
View File
@@ -0,0 +1,66 @@
#ifndef CVVISUAL_FILTER_CALL_HPP
#define CVVISUAL_FILTER_CALL_HPP
#include <QString>
#include "call.hpp"
#include "opencv2/core.hpp"
namespace cvv
{
namespace impl
{
/**
* All data of a filter-call: Location, original image and result.
*/
class FilterCall : public Call
{
public:
/**
* @brief Constructs a FilterCall.
*/
FilterCall(cv::InputArray in, cv::InputArray out,
impl::CallMetaData data, QString type, QString description,
QString requestedView);
size_t matrixCount() const override
{
return 2;
}
const cv::Mat &matrixAt(size_t index) const override;
/**
* @returns the original image
*/
const cv::Mat &original() const
{
return input_;
}
/**
* @returns the filtered image
*/
const cv::Mat &result() const
{
return output_;
}
private:
// TODO: in case we REALLY want to support several input-images: make
// this a std::vector
// TODO: those are typedefs for references, make it clean:
cv::Mat input_;
cv::Mat output_;
};
/**
* Constructs a FilterCall and adds it to the global data-controller.
*/
void debugFilterCall(cv::InputArray original, cv::InputArray result,
const CallMetaData &data, const char *description,
const char *view, const char *filter);
}
} // namespaces
#endif
+20
View File
@@ -0,0 +1,20 @@
#include "opencv2/cvv/final_show.hpp"
#include "data_controller.hpp"
namespace cvv
{
namespace impl
{
void finalShow()
{
auto &controller = impl::dataController();
if (controller.numCalls() != 0)
{
controller.lastCall();
}
impl::deleteDataController();
}
}
} // namespaces cvv::impl
+108
View File
@@ -0,0 +1,108 @@
#include "init.hpp"
// filters
#include "../qtutil/filterselectorwidget.hpp"
#include "../qtutil/filter/grayfilterwidget.hpp"
#include "../qtutil/filter/sobelfilterwidget.hpp"
#include "../qtutil/filter/channelreorderfilter.hpp"
#include "../qtutil/filter/diffFilterWidget.hpp"
#include "../qtutil/filter/overlayfilterwidget.hpp"
#include "../qtutil/filter/changed_pixels_widget.hpp"
#include "../gui/filter_call_tab.hpp"
#include "../view/filter_view.hpp"
#include "../view/defaultfilterview.hpp"
#include "../view/dual_filter_view.hpp"
#include "../view/singlefilterview.hpp"
#include "../gui/match_call_tab.hpp"
#include "../view/match_view.hpp"
#include "../view/linematchview.hpp"
#include "../view/rawview.hpp"
#include "../view/translationsmatchview.hpp"
#include "../view/pointmatchview.hpp"
#include "../qtutil/matchview/matchselectionselector.hpp"
#include "../qtutil/matchview/matchintervallselection.hpp"
#include "../qtutil/matchview/matchportionselector.hpp"
#include "../qtutil/matchview/matchsettingsselector.hpp"
#include "../qtutil/matchview/singlecolormatchpen.hpp"
#include "../qtutil/matchview/falsecolormatchpen.hpp"
#include "../qtutil/matchview/matchshowsetting.hpp"
#include "../qtutil/matchview/keypointselectionselector.hpp"
#include "../qtutil/matchview/keypointintervallselection.hpp"
#include "../qtutil/matchview/keypointportionselector.hpp"
#include "../qtutil/matchview/keypointsettingsselector.hpp"
#include "../qtutil/matchview/singlecolorkeypointpen.hpp"
#include "../qtutil/matchview/falsecolorkeypointpen.hpp"
#include "../qtutil/matchview/keypointshowsetting.hpp"
namespace cvv
{
namespace impl
{
void initializeFilterAndViews()
{
static bool alreadyCalled = false;
if (alreadyCalled)
{
return;
}
alreadyCalled = true;
// filter for filter-selector-widget
qtutil::registerFilter<1, 1, qtutil::GrayFilterWidget>("Gray filter");
qtutil::registerFilter<1, 1, qtutil::SobelFilterWidget>("Sobel");
qtutil::registerFilter<1, 1, qtutil::ChannelReorderFilter>(
"Reorder channels");
qtutil::registerFilter<2, 1, qtutil::DiffFilterFunction>("Difference");
qtutil::registerFilter<2, 1, qtutil::OverlayFilterWidget>("Overlay");
qtutil::registerFilter<2, 1, qtutil::ChangedPixelsWidget>("Changed Pixels");
// filter-views:
cvv::gui::FilterCallTab::registerFilterView<
cvv::view::DefaultFilterView> ("DefaultFilterView");
cvv::gui::FilterCallTab::registerFilterView<cvv::view::DualFilterView> (
"DualFilterView");
cvv::gui::FilterCallTab::registerFilterView<
cvv::view::SingleFilterView>("SingleFilterView");
// match-views:
cvv::gui::MatchCallTab::registerMatchView<cvv::view::LineMatchView>(
"LineMatchView");
cvv::gui::MatchCallTab::registerMatchView<
cvv::view::TranslationMatchView>("TranslationMatchView");
cvv::gui::MatchCallTab::registerMatchView<cvv::view::PointMatchView>(
"PointMatchView");
cvv::gui::MatchCallTab::registerMatchView<cvv::view::Rawview>(
"RawView");
//match Settings
cvv::qtutil::registerMatchSettings<cvv::qtutil::SingleColorMatchPen>("Single Color");
cvv::qtutil::registerMatchSettings<cvv::qtutil::FalseColorMatchPen>("False Color");
//cvv::qtutil::registerMatchSettings<cvv::qtutil::MatchShowSetting>("Show/Hide");
//match Selector
cvv::qtutil::registerMatchSelection<cvv::qtutil::MatchIntervallSelector>("Intervall Selector");
cvv::qtutil::registerMatchSelection<cvv::qtutil::MatchPortionSelection>("Portion Selector");
//keypoint Settings
cvv::qtutil::registerKeyPointSetting<cvv::qtutil::SingleColorKeyPen>("Single Color");
cvv::qtutil::registerKeyPointSetting<cvv::qtutil::FalseColorKeyPointPen>("False Color");
//cvv::qtutil::registerKeyPointSetting<cvv::qtutil::KeyPointShowSetting>("Show/Hide");
//keypoint Selection
cvv::qtutil::registerKeyPointSelection<cvv::qtutil::KeyPointIntervallSelector>("Intervall Selector");
cvv::qtutil::registerKeyPointSelection<cvv::qtutil::KeyPointPortionSelection>("Portion Selector");
}
}
}
+14
View File
@@ -0,0 +1,14 @@
#ifndef CVVISUAL_INIT_HPP
#define CVVISUAL_INIT_HPP
namespace cvv
{
namespace impl
{
/**
* @brief Initializes filters and views.
*/
void initializeFilterAndViews();
}
}
#endif // CVVISUAL_INIT_HPP
+57
View File
@@ -0,0 +1,57 @@
#include "match_call.hpp"
#include <stdexcept>
#include <QString>
#include "data_controller.hpp"
#include "../util/util.hpp"
namespace cvv
{
namespace impl
{
MatchCall::MatchCall(cv::InputArray img1, std::vector<cv::KeyPoint> keypoints1,
cv::InputArray img2, std::vector<cv::KeyPoint> keypoints2,
std::vector<cv::DMatch> matches, impl::CallMetaData data,
QString type, QString description, QString requestedView,
bool useTrainDescriptor)
: Call( data, std::move(type),
std::move(description), std::move(requestedView) ),
img1_{ img1.getMat().clone() }, keypoints1_{ std::move(keypoints1) },
img2_{ img2.getMat().clone() }, keypoints2_{ std::move(keypoints2) },
matches_{ std::move(matches) }, usesTrainDescriptor_{ useTrainDescriptor }
{
}
const cv::Mat &MatchCall::matrixAt(size_t index) const
{
switch (index)
{
case 0:
return img1();
case 1:
return img2();
default:
throw std::out_of_range{ "" };
}
}
void debugMatchCall(cv::InputArray img1, std::vector<cv::KeyPoint> keypoints1,
cv::InputArray img2, std::vector<cv::KeyPoint> keypoints2,
std::vector<cv::DMatch> matches, const CallMetaData &data,
const char *description, const char *view,
bool useTrainDescriptor)
{
dataController().addCall(util::make_unique<MatchCall>(
img1, std::move(keypoints1), img2, std::move(keypoints2),
std::move(matches), data, "match",
description ? QString::fromLocal8Bit(description)
: QString{ "<no description>" },
view ? QString::fromLocal8Bit(view) : QString{},
useTrainDescriptor));
}
}
} // namespaces cvv::impl
+104
View File
@@ -0,0 +1,104 @@
#ifndef CVVISUAL_MATCH_CALL_HPP
#define CVVISUAL_MATCH_CALL_HPP
#include <vector>
#include <utility>
#include <type_traits>
#include "opencv2/core.hpp"
#include "opencv2/features.hpp"
#include "call.hpp"
namespace cvv
{
namespace impl
{
/**
* Contains all the calldata (= location, images and their keypoints).
*/
class MatchCall : public Call
{
public:
/**
* @brief Constructs a MatchCall.
*/
MatchCall(cv::InputArray img1, std::vector<cv::KeyPoint> keypoints1,
cv::InputArray img2, std::vector<cv::KeyPoint> keypoints2,
std::vector<cv::DMatch> matches, impl::CallMetaData data,
QString type, QString description, QString requestedView,
bool useTrainDescriptor);
size_t matrixCount() const override
{
return 2;
}
const cv::Mat &matrixAt(size_t index) const override;
/**
* @brief Returns the first Mat.
*/
const cv::Mat &img1() const
{
return img1_;
}
/**
* @brief Returns the second Mat.
*/
const cv::Mat &img2() const
{
return img2_;
}
/**
* @brief Returns the keypoints for the first Mat.
*/
const std::vector<cv::KeyPoint> &keyPoints1() const
{
return keypoints1_;
}
/**
* @brief Returns the keypoints for the second Mat.
*/
const std::vector<cv::KeyPoint> &keyPoints2() const
{
return keypoints2_;
}
/**
* @brief Returns the matches.
*/
const std::vector<cv::DMatch> &matches() const
{
return matches_;
}
bool usesTrainDescriptor() const
{
return usesTrainDescriptor_;
}
private:
cv::Mat img1_;
std::vector<cv::KeyPoint> keypoints1_;
cv::Mat img2_;
std::vector<cv::KeyPoint> keypoints2_;
std::vector<cv::DMatch> matches_;
bool usesTrainDescriptor_;
};
/**
* Constructs a MatchCall and adds it to the global data-controller.
*/
void debugMatchCall(cv::InputArray img1, std::vector<cv::KeyPoint> keypoints1,
cv::InputArray img2, std::vector<cv::KeyPoint> keypoints2,
std::vector<cv::DMatch> matches, const CallMetaData &data,
const char *description, const char *view,
bool useTrainDescriptor);
}
} // namespaces cvv::impl
#endif
+17
View File
@@ -0,0 +1,17 @@
#include "opencv2/cvv/show_image.hpp"
#include "opencv2/cvv/call_meta_data.hpp"
#include "single_image_call.hpp"
namespace cvv
{
namespace impl
{
void showImage(cv::InputArray img, const CallMetaData &data,
const char *description, const char *view)
{
debugSingleImageCall(img, data, description, view, "singleImage");
}
}
} // namespaces
@@ -0,0 +1,42 @@
#include "single_image_call.hpp"
#include <QString>
#include "data_controller.hpp"
#include "../util/util.hpp"
namespace cvv
{
namespace impl
{
SingleImageCall::SingleImageCall(cv::InputArray img, impl::CallMetaData data,
QString type, QString description,
QString requestedView)
: Call( data, std::move(type),
std::move(description), std::move(requestedView) ),
img{ img.getMat().clone() }
{
}
const cv::Mat &SingleImageCall::matrixAt(size_t index) const
{
if (index)
{
throw std::out_of_range{ "" };
}
return img;
}
void debugSingleImageCall(cv::InputArray img, const CallMetaData &data,
const char *description, const char *view,
const char *filter)
{
dataController().addCall(util::make_unique<SingleImageCall>(
img, data, filter, description ? QString::fromLocal8Bit(description)
: QString{ "<no description>" },
view ? QString::fromLocal8Bit(view) : QString{}));
}
}
} // namespaces cvv::impl
@@ -0,0 +1,55 @@
#ifndef CVVISUAL_SINGLE_IMAGE_CALL_HPP
#define CVVISUAL_SINGLE_IMAGE_CALL_HPP
#include "call.hpp"
#include <QString>
#include "opencv2/core.hpp"
namespace cvv
{
namespace impl
{
/**
* All data of a filter-call: Location, original image and result.
*/
class SingleImageCall : public Call
{
public:
/**
* @brief Constructs a SingleImageCall.
*/
SingleImageCall(cv::InputArray img, impl::CallMetaData data,
QString type, QString description,
QString requestedView);
size_t matrixCount() const override
{
return 1;
}
const cv::Mat &matrixAt(size_t index) const override;
/**
* @returns the original image
*/
const cv::Mat &mat() const
{
return img;
}
private:
cv::Mat img;
};
/**
* Constructs a SingleImageCall and adds it to the global data-controller.
*/
void debugSingleImageCall(cv::InputArray img, const CallMetaData &data,
const char *description, const char *view,
const char *filter);
}
} // namespaces
#endif
+110
View File
@@ -0,0 +1,110 @@
#include "accordion.hpp"
#include <QScrollArea>
namespace cvv
{
namespace qtutil
{
Accordion::Accordion(QWidget *parent)
: QWidget{ parent }, elements_{}, layout_{ nullptr }
{
auto lay = util::make_unique<QVBoxLayout>();
layout_ = *lay;
layout_->setAlignment(Qt::AlignTop);
// needed because scrollArea->setLayout(layout_); does not work
auto resizehelper = util::make_unique<QWidget>();
resizehelper->setLayout(lay.release());
auto scrollArea = util::make_unique<QScrollArea>();
scrollArea->setWidget(resizehelper.release());
// needed because no contained widget demands a size
scrollArea->setWidgetResizable(true);
auto mainLayout = util::make_unique<QVBoxLayout>();
mainLayout->addWidget(scrollArea.release());
setLayout(mainLayout.release());
}
void Accordion::collapseAll(bool b)
{
for (auto &elem : elements_)
{
elem.second->collapse(b);
}
}
void Accordion::hideAll(bool b)
{
for (auto &elem : elements_)
{
elem.second->setVisible(!b);
}
}
Accordion::Handle Accordion::insert(const QString &title,
std::unique_ptr<QWidget> widget,
bool isCollapsed, std::size_t position)
{
// create element
auto widgetPtr = widget.get();
elements_.emplace(
widgetPtr, util::make_unique<Collapsable>(title, std::move(widget),
isCollapsed).release());
// insert element
layout_->insertWidget(position, &element(widgetPtr));
return widgetPtr;
}
void Accordion::remove(Handle handle)
{
Collapsable *elem = &element(handle);
layout_->removeWidget(elem);
elements_.erase(handle);
elem->setParent(0);
elem->deleteLater();
}
void Accordion::clear()
{
// clear layout
for (auto &elem : elements_)
{
layout_->removeWidget(elem.second);
elem.second->setParent(nullptr);
elem.second->deleteLater();
}
elements_.clear();
}
std::pair<QString, Collapsable *> Accordion::pop(Handle handle)
{
Collapsable *elem = &element(handle);
// remove from layout
layout_->removeWidget(elem);
std::pair<QString, Collapsable *> result{ element(handle).title(),
elem };
// remove from map
elements_.erase(handle);
return result;
}
std::vector<std::pair<QString, Collapsable *>> Accordion::popAll()
{
std::vector<std::pair<QString, Collapsable *>> result{};
for (auto &elem : elements_)
{
// remove from layout
layout_->removeWidget(elem.second);
result.push_back(
std::pair<QString, Collapsable *>{ elem.second->title(),
elem.second });
}
// remove from map
elements_.clear();
return result;
}
}
} // end namespaces qtutil, cvv
+292
View File
@@ -0,0 +1,292 @@
#ifndef CVVISUAL_ACCORDION_HPP
#define CVVISUAL_ACCORDION_HPP
// STD
#include <memory>
#include <stdexcept>
#include <map>
#include <limits>
// QT
#include <QWidget>
#include <QString>
#include <QVBoxLayout>
// CVV
#include "collapsable.hpp"
#include "../util/util.hpp"
#include "../util/observer_ptr.hpp"
namespace cvv
{
namespace qtutil
{
/**
* @brief The Accordion class.
*
* Contains multiple widgets and their title. These get stored in collapsables.
* The collapsables are stored in a collumn.
*/
class Accordion : public QWidget
{
Q_OBJECT
public:
/**
* @brief The handle type to access elements
*/
using Handle = QWidget *;
/**
* @brief Constructs an empty accordion.
* @param parent The parent widget
*/
explicit Accordion(QWidget *parent = nullptr);
~Accordion()
{
}
/**
* @brief Returns the element corrsponding to handle
* @throw std::out_of_range If there is no element corresponding to
* handle
* @return The element corrsponding to handle
*/
Collapsable &element(Handle handle)
{
return *elements_.at(handle);
}
const Collapsable &element(Handle handle) const
{
return *elements_.at(handle);
}
/**
* @brief Sets the title above the element.
* @param handle The element
* @param title The new title.
* @throw std::out_of_range If there is no element corresponding to
* handle
*/
void setTitle(Handle handle, const QString &title)
{
element(handle).setTitle(title);
}
/**
* @brief Returns the current title above the element.
* @param handle The element
* @throw std::out_of_range If there is no element corresponding to
* handle
* @return The current title above the element.
*/
QString title(Handle handle) const
{
return element(handle).title();
}
/**
* @brief Collapses an element
* @param handle The element to collapse
* @param b
* @parblock
* true: collapses the widget
* false: expands the widget
* @endparblock
* @throw std::out_of_range If there is no element corresponding to
* handle
*/
void collapse(Handle handle, bool b = true)
{
element(handle).collapse(b);
}
/**
* @brief Expands an element
* @param handle Element to expand
* @param b
* @parblock
* true: expands the widget
* false: collapses the widget
* @endparblock
* @throw std::out_of_range If there is no element corresponding to
* handle
*/
void expand(Handle handle, bool b = true)
{
collapse(handle, !b);
}
/**
* @brief Collapses all elements
* @param b
* @parblock
* true: collapses all elements
* false: expands all elements
* @endparblock
*/
void collapseAll(bool b = true);
/**
* @brief Expands all elements
* @param b
* @parblock
* true: expands all elements
* false: collapses all elements
* @endparblock
*/
void expandAll(bool b = true)
{
collapseAll(!b);
}
/**
* @brief Makes the element invisible
* @param handle The element
* @param b
* @parblock
* true: makes the element invisible
* false: makes the element visible
* @endparblock
* @throw std::out_of_range If there is no element corresponding to
* handle
*/
void hide(Handle handle, bool b = true)
{
element(handle).setVisible(!b);
}
/**
* @brief Makes the element visible
* @param handle The element
* @param b
* @parblock
* true: makes the element visible
* false: makes the element invisible
* @endparblock
* @throw std::out_of_range If there is no element corresponding to
* handle
*/
void show(Handle handle, bool b = true)
{
hide(handle, !b);
}
/**
* @brief Sets all elements' visibility to !b
* @param b
* @parblock
* true: makes all elements invisible
* false: makes all elements visible
* @endparblock
*/
void hideAll(bool b = true);
/**
* @brief Sets all elements' visibility to b
* @param b
* @parblock
* true: makes all elements visible
* false: makes all elements invisible
* @endparblock
*/
void showAll(bool b = true)
{
hideAll(!b);
}
/**
* @brief Inserts a widget at the given position
* @param title The title to display
* @param widget The widget to display
* @param isCollapsed Whether the widget is collapsed after creation
* @param position The position. If it is greater than the number of
*elements the widget
* will be added to the end
* @return The handle to access the element
*/
Handle insert(const QString &title, std::unique_ptr<QWidget> widget,
bool isCollapsed = true,
std::size_t position =
std::numeric_limits<std::size_t>::max());
/**
* @brief Adds a widget to the end of the Accordion
* @param title The title to display
* @param widget The widget to display
* @param isCollapsed Whether the widget is collapsed after creation
* @return The handle to access the element
*/
Handle push_back(const QString &title, std::unique_ptr<QWidget> widget,
bool isCollapsed = true)
{
return insert(title, std::move(widget), isCollapsed);
}
/**
* @brief Adds a widget to the front of the Accordion
* @param title The title to display
* @param widget The widget to display
* @param isCollapsed Whether the widget is collapsed after creation
* @return The handle to access the element
*/
Handle push_front(const QString &title, std::unique_ptr<QWidget> widget,
bool isCollapsed = true)
{
return insert(title, std::move(widget), isCollapsed, 0);
}
/**
* @brief Removes the element and deletes it immediately.
* @param handle Handle of the element
* @param del
* @throw std::out_of_range If there is no element corresponding to
* handle
*/
void remove(Handle handle);
/**
* @brief Removes all elements and deletes them immediately.
* @param del
*/
void clear();
/**
* @brief Removes an element and returns its title and Collapsable.
* (ownership remains)
* @param handle Handle of the element
* @throw std::out_of_range If there is no element corresponding to
* handle
* @return Title and reference
*/
std::pair<QString, Collapsable *> pop(Handle handle);
/**
* @brief Removes all elements from the Accordion and returns their
*titles
* and Collapsables (ownership remains)
* @return A vector containing all titles and references
*/
std::vector<std::pair<QString, Collapsable *>> popAll();
/**
* @brief Returns the number of elements
* @return The number of elements
*/
std::size_t size() const
{
return elements_.size();
}
private:
/**
* @brief Storage for all elements
*/
std::map<Handle, Collapsable *> elements_;
/**
* @brief Layout for all elements
*/
util::ObserverPtr<QVBoxLayout> layout_;
}; // Accordion
}
} // end namespaces qtutil, cvv
#endif // CVVISUAL_ACCORDION_HPP
+475
View File
@@ -0,0 +1,475 @@
#ifndef CVVISUAL_AUTOFILTERWIDGET_HPP
#define CVVISUAL_AUTOFILTERWIDGET_HPP
#include <array>
#include <vector>
#include <chrono>
#include "opencv2/core.hpp"
#include <QWidget>
#include <QCheckBox>
#include <QVBoxLayout>
#include <QLabel>
#include <QString>
#include "filterselectorwidget.hpp"
#include "signalslot.hpp"
#include "../util/util.hpp"
#include "../util/observer_ptr.hpp"
#include "signalslot.hpp"
namespace cvv
{
namespace qtutil
{
template <std::size_t In, std::size_t Out> class AutoFilterWidget;
/**
* @brief Contains internal structures or classes.
*
* Stores the image input/output, the name and update signals for all output
*images.
* Also provides the label to pass messages from the filter and provides a check
*box to select
* the input to be filtered (can be deactivated).
*/
namespace structures
{
/**
* @brief Represents an entry of an autofilterwidget.
*/
template <std::size_t In, std::size_t Out>
class AutoFilterWidgetEntry : public QWidget
{
public:
/**
* The input type for a filter.
*/
using InputArray = typename AutoFilterWidget<In, Out>::InputArray;
/**
* The type of an output parameter of a filter.
*/
using OutputArray = typename AutoFilterWidget<In, Out>::OutputArray;
/**
* @brief Constructor
* @param name The name shown to the user.
* @param in Image input
* @param out Image output
* @param parent Parent widget
*/
AutoFilterWidgetEntry(const QString &name, InputArray in,
OutputArray out, QWidget *parent = nullptr)
: QWidget{ parent }, name_{ name }, checkBox_{ nullptr },
message_{ nullptr }, in_(in), out_(out), signals_()
{
auto box = util::make_unique<QCheckBox>(name);
checkBox_ = *box;
auto msg = util::make_unique<QLabel>();
message_ = *msg;
auto lay = util::make_unique<QVBoxLayout>();
lay->setAlignment(Qt::AlignTop);
lay->setSpacing(0);
lay->setContentsMargins(0, 0, 0, 0);
lay->addWidget(box.release());
lay->addWidget(msg.release());
message_->setVisible(false);
setLayout(lay.release());
enableUserSelection(true);
}
/**
* @brief Destructor
*/
~AutoFilterWidgetEntry()
{
}
/**
* @brief Checks wheather the check box is checked.
*/
operator bool() const
{
return checkBox_->isChecked();
}
/**
* @brief Returns the image input.
* @return The image input.
*/
InputArray input() const
{
return in_;
}
/**
* @brief Returns the image output.
* @return The image output.
*/
OutputArray output()
{
return out_;
}
/**
* @brief Returns references to the update signals.
* @return References to the update signals.
*/
std::vector<util::Reference<const SignalMatRef>> signalsRef() const
{
std::vector<util::Reference<const SignalMatRef>> result{};
for (auto &elem : signals_)
{
result.emplace_back(elem);
}
return result;
}
/**
* @brief Emits all update signals.
*/
void emitAll() const
{
for (std::size_t i = 0; i < Out; i++)
{
signals_.at(i).emitSignal(out_.at(i).get());
}
}
/**
* @brief Sets the message to display.
* @param msg The message to display (if msg == "" no message will be
* shown.
*/
void setMessage(const QString &msg = "")
{
if (msg == "")
{
message_->setVisible(false);
return;
}
message_->setVisible(true);
message_->setText(QString("<font color='red'>") + name_ +
QString(": ") + msg + QString("</font>"));
}
/**
* @brief Enables/disables the checkbox.
* @param enabled If true the box will be enabled.
* If false the box will be disabled and checked.
*/
void enableUserSelection(bool enabled = true)
{
if (!enabled)
{
checkBox_->setChecked(true);
}
checkBox_->setVisible(enabled);
}
/**
* @brief The display name.
*/
QString name_;
/**
* @brief The check box.
*/
util::ObserverPtr<QCheckBox> checkBox_;
/**
* @brief The label to display messages.
*/
util::ObserverPtr<QLabel> message_;
/**
* @brief Image input.
*/
InputArray in_;
/**
* @brief Image output.
*/
OutputArray out_;
/**
* @brief The update signals for the output.
*/
std::array<const SignalMatRef, Out> signals_;
};
} // structures
/**
* @brief The AutoFilterWidget class automatically applies the selected filter
* to all added entries.
*/
template <std::size_t In, std::size_t Out>
class AutoFilterWidget : public FilterSelectorWidget<In, Out>
{
public:
/**
* The input type for a filter.
*/
using InputArray = typename FilterSelectorWidget<In, Out>::InputArray;
/**
* The type of an output parameter of a filter.
*/
using OutputArray = typename FilterSelectorWidget<In, Out>::OutputArray;
/**
* @brief Constructor.
* @param parent The parent widget.
*/
AutoFilterWidget(QWidget *parent = nullptr)
: FilterSelectorWidget<In, Out>{ parent },
slotEnableUserSelection_{ [this](bool b)
{
this->enableUserSelection(b);
} },
slotUseFilterIndividually_{ [this](bool b)
{
this->useFilterIndividually(b);
} },
entryLayout_{ nullptr }, applyFilterIndividually_{ false },
entries_{}, earliestActivationTime_{}, slotApplyFilter_{ [this]()
{
this->autoApplyFilter();
} },
userSelection_{ true }
{
// add sublayout
auto lay = util::make_unique<QVBoxLayout>();
entryLayout_ = *lay;
lay->setContentsMargins(0, 0, 0, 0);
this->layout_->insertLayout(0, lay.release());
// connect auto filter slot
QObject::connect(&(this->signalFilterSettingsChanged()),
SIGNAL(signal()), &(this->slotApplyFilter_),
SLOT(slot()));
}
/**
* @brief Adds an entry.
* @param name The name of the enty.
* @param in The image input.
* @param out The image output.
* @return The update signals for all output images.
*/
std::vector<util::Reference<const SignalMatRef>>
addEntry(const QString &name, InputArray in, OutputArray out)
{
auto elem = util::make_unique<
structures::AutoFilterWidgetEntry<In, Out>>(name, in, out);
auto result = elem->signalsRef();
elem->enableUserSelection(userSelection_);
// store element
entries_.emplace_back(*elem);
// add it to the widget
entryLayout_->addWidget(elem.release());
return result;
}
/**
* @brief Removes all entries.
*/
void removeAll()
{
structures::AutoFilterWidgetEntry<In, Out> *elemToDelete;
for (auto &elem : entries_)
{
elemToDelete = elem.getPtr();
// remove from layout
entryLayout_->removeWidget(elemToDelete);
// delete the element
elemToDelete->deleteLater();
}
entries_.clear();
}
/**
* @brief Enabels / disables the user to select entries to filter per
* combo boxes.
* @param enabled If true it will be enabled.
*/
void enableUserSelection(bool enabled = true)
{
userSelection_ = enabled;
for (auto &elem : entries_)
{
elem.get().enableUserSelection(userSelection_);
}
}
/**
* @brief Sets whether the filter will be applied to entries it can be
* applied to
* even when one other entry cant apply the filter.
* @param individually If true each entry that can apply the filter does
* so.
*/
void useFilterIndividually(bool individually = true)
{
applyFilterIndividually_ = individually;
}
/**
* @brief Returns a slot object that calls enableUserSelection.
* @return A slot object that calls enableUserSelection.
*/
const SlotBool &slotEnableUserSelection() const
{
return slotEnableUserSelection_;
}
/**
* @brief Returns a slot object that calls seFilterIndividually.
* @return A slot object that calls seFilterIndividually.
*/
const SlotBool &slotUseFilterIndividually() const
{
return slotUseFilterIndividually_;
}
private:
/**
* @brief calls enableUserSelection
*/
const SlotBool slotEnableUserSelection_;
/**
* @brief calls seFilterIndividually.
*/
const SlotBool slotUseFilterIndividually_;
/**
* @brief Applies the filter when some settings where changed.
*/
void autoApplyFilter()
{
auto start = std::chrono::high_resolution_clock::now();
// activate again?
if (start < earliestActivationTime_)
{
return;
}
// apply filter
if (!applyFilterIndividually_)
{
// only apply all filters at once
// check wheather all filters can be applied
std::size_t failed = 0;
for (auto &elem : entries_)
{
// activated?
if (elem.get())
{
auto check = this->checkInput(
elem.get().input());
if (!check.first)
{
// elem cant apply filter
failed++;
elem.get().setMessage(
check.second);
}
else
{
// elem can apply filter. delete
// message
elem.get().setMessage("");
}
}
else
{
// delete message
elem.get().setMessage("");
}
}
if (failed)
{
// one filter failed
return;
}
// all can apply filter
// apply filters
for (auto &elem : entries_)
{
// activated?
if (elem.get())
{
this->applyFilter(elem.get().input(),
elem.get().output());
elem.get().emitAll();
};
}
}
else
{ // applyFilterIndividually_==true
// filters can be applied individually
for (auto &elem : entries_)
{
// activated?
if (elem.get())
{
auto check = this->checkInput(
elem.get().input());
if (!check.first)
{
// set message
elem.get().setMessage(
check.second);
}
else
{
// apply filter+set message
elem.get().setMessage("");
this->applyFilter(
elem.get().input(),
elem.get().output());
elem.get().emitAll();
}
}
else
{
// delete message
elem.get().setMessage("");
}
}
}
// update activation time
earliestActivationTime_ =
std::chrono::high_resolution_clock::now() +
(std::chrono::high_resolution_clock::now() -
start); // duration
}
/**
* @brief The layout containing the entries.
*/
util::ObserverPtr<QVBoxLayout> entryLayout_;
/**
* @brief Each entry that can apply the filter does so.
*/
bool applyFilterIndividually_;
/**
* @brief The entries.
*/
std::vector<util::Reference<structures::AutoFilterWidgetEntry<In, Out>>>
entries_;
/**
* @brief Time for the earliest next activation for the filter.
*/
std::chrono::time_point<std::chrono::high_resolution_clock>
earliestActivationTime_;
/**
* @brief Slot called when filter settings change.
*/
Slot slotApplyFilter_;
/**
* @brief Whether user selection is enabled
*/
bool userSelection_;
};
}
}
#endif // CVVISUAL_AUTOFILTERWIDGET_HPP
+70
View File
@@ -0,0 +1,70 @@
#include "collapsable.hpp"
namespace cvv
{
namespace qtutil
{
Collapsable::Collapsable(const QString &title, std::unique_ptr<QWidget> widget,
bool isCollapsed, QWidget *parent)
: QFrame{ parent }, widget_{ widget.get() }, layout_{ nullptr }
{
auto lay = util::make_unique<QVBoxLayout>();
layout_ = *lay;
// set alignment+border
setLineWidth(1);
setFrameStyle(QFrame::Box);
layout_->setAlignment(Qt::AlignTop);
layout_->setContentsMargins(0, 0, 0, 0);
// build header
auto tmpButton = util::make_unique<QPushButton>();
button_ = tmpButton.get();
button_->setEnabled(true);
button_->setText(title);
button_->setCheckable(true);
// build widget
setLayout(lay.release());
layout_->addWidget(tmpButton.release());
layout_->addWidget(widget.release());
// connect signals and slots
QObject::connect(button_, SIGNAL(clicked()), this,
SLOT(toggleVisibility()));
// collapse/ expand according to isCollapsed
collapse(isCollapsed);
}
// Collapsable::Collapsable(const QString& title,QWidget& widget, bool
// isCollapsed, QWidget *parent):
// Collapsable{title, std::unique_ptr<QWidget>{&widget}, isCollapsed,
//parent} {}
void Collapsable::collapse(bool b)
{
button_->setChecked(!b);
if (b)
{
widget_->hide();
}
else
{
widget_->show();
}
}
QWidget *Collapsable::detachWidget()
{
if (!widget_)
{
return nullptr;
}
layout_->removeWidget(widget_);
QWidget *tmp = widget_;
widget_ = nullptr;
return tmp;
}
}
} // end namespaces qtutil, cvv
+139
View File
@@ -0,0 +1,139 @@
#ifndef CVVISUAL_COLLAPSABLE_H
#define CVVISUAL_COLLAPSABLE_H
// std
#include <cstddef>
// QT
#include <QString>
#include <QWidget>
#include <QPushButton>
#include <QVBoxLayout>
#include <QLabel>
#include <QFrame>
#include "../util/util.hpp"
#include "../util/observer_ptr.hpp"
namespace cvv
{
namespace qtutil
{
/**
* @brief Contains a widget and a title.
*
* The widget can be collapsed and expanded with a button.
* If the widget is collapsed only button and title are shown.
*/
class Collapsable : public QFrame
{
Q_OBJECT
public:
/**
* @brief Constructs a collapsable
* @param title The title above the widget.
* @param widget The widget to store.
* @param isCollapsed If true the contained widget will be collapsed.
* (It will be shown
* otherwise.)
*/
// explicit Collapsable(const QString& title, QWidget& widget, bool
// isCollapsed = true,
// QWidget *parent = 0);
explicit Collapsable(const QString &title,
std::unique_ptr<QWidget> widget,
bool isCollapsed = true, QWidget *parent = 0);
~Collapsable()
{
}
/**
* @brief Collapses the contained widget.
* @param b
* @parblock
* true: collapses the widget
* false: expands the widget
* @endparblock
*/
void collapse(bool b = true);
/**
* @brief Expands the contained widget.
* @param b
* @parblock
* true: expands the widget
* false: collapses the widget
* @endparblock
*/
void expand(bool b = true)
{
collapse(!b);
}
/**
* @brief Sets the title above the widget.
*/
void setTitle(const QString &title)
{
button_->setText(title);
}
/**
* @brief Returns the current title above the widget.
* @return The current title above the widget
*/
QString title() const
{
return button_->text();
}
/**
* @brief Returns a reference to the contained widget.
* @return A reference to the contained widget.
*/
QWidget &widget()
{
return *widget_;
}
const QWidget &widget() const
{
return *widget_;
}
/**
* @brief Detaches the contained widget. (ownership remains)
* @return The contained widget
*/
QWidget *detachWidget();
private
slots:
/**
* @brief Toggles the visibility.
*/
void toggleVisibility()
{
collapse(widget_->isVisible());
}
private:
/**
* @brief The contained widget
*/
QWidget *widget_;
/**
* @brief The button to toggle the widget
*/
QPushButton *button_;
/**
* @brief The layout containing the header and widget
*/
util::ObserverPtr<QVBoxLayout> layout_;
}; // Collapsable
}
} // end namespaces qtutil, cvv
#endif // CVVISUAL_COLLAPSABLE_H
@@ -0,0 +1,160 @@
#include "changed_pixels_widget.hpp"
#include <QLabel>
#include <QVBoxLayout>
#include "../types.hpp"
//forward
template<int Depth>
void changedPixelImage(const cv::Mat& mat0, const cv::Mat& mat1, cv::Mat& out);
template<int Depth, int Channels>
void changedPixelImage(const cv::Mat& mat0, const cv::Mat& mat1, cv::Mat& out);
static void changedPixelImage(const cv::Mat& mat0, const cv::Mat& mat1, cv::Mat& out)
{
// need same size
if (mat0.size() != mat1.size())
{
return;
}
//need same # of channels
if (mat0.channels()!=mat1.channels())
{
return;
}
//need same depth
if (mat0.depth()!=mat1.depth())
{
return;
}
//split depth
switch(mat0.depth())
{
case CV_8U :changedPixelImage<CV_8U >(mat0,mat1,out); break;
case CV_8S :changedPixelImage<CV_8S >(mat0,mat1,out); break;
case CV_16U:changedPixelImage<CV_16U>(mat0,mat1,out); break;
case CV_16S:changedPixelImage<CV_16S>(mat0,mat1,out); break;
case CV_32S:changedPixelImage<CV_32S>(mat0,mat1,out); break;
case CV_32F:changedPixelImage<CV_32F>(mat0,mat1,out); break;
case CV_64F:changedPixelImage<CV_64F>(mat0,mat1,out); break;
}
}
template<int Depth>
void changedPixelImage(const cv::Mat& mat0, const cv::Mat& mat1, cv::Mat& out)
{
switch(mat0.channels())
{
case 1 :changedPixelImage<Depth,1 >(mat0,mat1,out); break;
case 2 :changedPixelImage<Depth,2 >(mat0,mat1,out); break;
case 3 :changedPixelImage<Depth,3 >(mat0,mat1,out); break;
case 4 :changedPixelImage<Depth,4 >(mat0,mat1,out); break;
case 5 :changedPixelImage<Depth,5 >(mat0,mat1,out); break;
case 6 :changedPixelImage<Depth,6 >(mat0,mat1,out); break;
case 7 :changedPixelImage<Depth,7 >(mat0,mat1,out); break;
case 8 :changedPixelImage<Depth,8 >(mat0,mat1,out); break;
case 9 :changedPixelImage<Depth,9 >(mat0,mat1,out); break;
case 10 :changedPixelImage<Depth,10>(mat0,mat1,out); break;
}
}
template<int Depth, int Channels>
void changedPixelImage(const cv::Mat& mat0, const cv::Mat& mat1, cv::Mat& out)
{
using PixelInType=cvv::qtutil::PixelType<Depth,Channels>;
using PixelOutType=cvv::qtutil::DepthType<CV_8U>;
cv::Mat result=cv::Mat::zeros(mat0.rows, mat0.cols,CV_8U);
bool same;
const PixelInType* in0;
const PixelInType* in1;
for(int i=0;i<result.rows;i++)
{
for(int j=0;j<result.cols;j++)
{
same=true;
in0=&(mat0.at<PixelInType>(i,j));
in1=&(mat1.at<PixelInType>(i,j));
for(int chan=0;chan<Channels;chan++)
{
same = same && (in0[chan]==in1[chan]);
}
//same color => set pixel color white
if(same)
{
(result.at<PixelOutType>(i,j))=255;
}
}
}
out=result;
}
namespace cvv
{
namespace qtutil
{
ChangedPixelsWidget::ChangedPixelsWidget(QWidget* parent): FilterFunctionWidget<2, 1>{parent}
{
auto lay=util::make_unique<QVBoxLayout>();
lay->addWidget(util::make_unique<QLabel>("Changed pixels will be black<br>"
" unchanged white.").release());
setLayout(lay.release());
}
void ChangedPixelsWidget::applyFilter(ChangedPixelsWidget::InputArray in,
ChangedPixelsWidget::OutputArray out) const
{
changedPixelImage(in.at(0).get(),in.at(1).get(),out.at(0).get());
}
std::pair<bool, QString> ChangedPixelsWidget::checkInput(InputArray in) const
{
if (in.at(0).get().size() != in.at(1).get().size())
{
return std::make_pair(false, "images need to have same size");
}
size_t inChannels = in.at(0).get().channels();
if (inChannels != static_cast<size_t>(in.at(1).get().channels()))
{
return std::make_pair(
false, "images need to have same number of channels");
}
if (inChannels>10 || inChannels<1)
{
return std::make_pair(
false, "images need to have 1 up to 10 channels");
}
int i0depth=in.at(0).get().depth();
if (i0depth!=in.at(1).get().depth())
{
return std::make_pair(
false, "images need to have the same depth");
}
if (!((i0depth==CV_8U)||(i0depth==CV_8S)||(i0depth==CV_16U)||(i0depth==CV_16S)||
(i0depth==CV_32S)||(i0depth==CV_32F)||(i0depth==CV_64F)))
{
return std::make_pair(false, "images have unknown depth");
}
return std::make_pair(true, "");
}
} // qtutil
} // cvv
@@ -0,0 +1,50 @@
#ifndef CVVISUAL_CHANGED_PIXELS_WIDGET_HPP
#define CVVISUAL_CHANGED_PIXELS_WIDGET_HPP
#include "../filterfunctionwidget.hpp"
namespace cvv
{
namespace qtutil
{
/**
* @brief A Comparator that will create a Mat that highlights exactly the changed
* pixels (black) and leaves unchanged pixels white.
*/
class ChangedPixelsWidget : public FilterFunctionWidget<2, 1>
{
Q_OBJECT
public:
/**
* @brief Constructor
*/
ChangedPixelsWidget(QWidget* parent = nullptr);
/**
* @brief Applys the filter to in and saves the result in out.
* @param in The input images.
* @param out The output images.
*/
void applyFilter(InputArray in, OutputArray out) const override;
/**
* @brief Checks whether input can be progressed by the applyFilter
*function.
* @param in The input images.
* @return bool = true: the filter can be executed.
* bool = false: the filter cant be executed (e.g. images
*have wrong depth)
* QString = message for the user (e.g. why the filter can't
*be progressed.)
*/
std::pair<bool, QString> checkInput(InputArray in) const override;
};
}
}
#endif
@@ -0,0 +1,105 @@
#include "channelreorderfilter.hpp"
#include <QLabel>
#include "../util.hpp"
namespace cvv
{
namespace qtutil
{
ChannelReorderFilter::ChannelReorderFilter(QWidget *parent)
: FilterFunctionWidget<1, 1>{ parent }, layout_{ nullptr },
channel_{ nullptr }, channelAssignment_{}
{
setToolTip(
"nonexistant channels from source will be seen as a zero mat");
auto lay = util::make_unique<QVBoxLayout>();
layout_ = *lay;
auto channel = util::make_unique<QSpinBox>();
channel_ = *channel;
// channelselector
channel_->setRange(1, 10);
QObject::connect(channel_.getPtr(), SIGNAL(valueChanged(int)), this,
SLOT(setChannel(int)));
// build ui
layout_->addWidget(
util::make_unique<QLabel>("Number of channels").release());
layout_->addWidget(channel.release());
layout_->addWidget(util::make_unique<QLabel>(
"Assignment for the old channels").release());
setLayout(lay.release());
channel_->setValue(3);
}
std::pair<bool, QString> ChannelReorderFilter::checkInput(InputArray in) const
{
if (in.at(0)->channels() < 1)
{
return { false, "<1 channel" };
}
return { true, "" };
}
void ChannelReorderFilter::applyFilter(InputArray in, OutputArray out) const
{
auto chans = splitChannels(in.at(0).get());
cv::Mat zeros = cv::Mat::zeros(chans.front().rows, chans.front().cols,
chans.front().type());
std::vector<cv::Mat> toMerge{};
for (std::size_t i = 0; i < channelAssignment_.size(); i++)
{
if (static_cast<std::size_t>(
channelAssignment_.at(i)->value()) < chans.size())
{
toMerge.push_back(
chans.at(channelAssignment_.at(i)->value()));
}
else
{
toMerge.push_back(zeros);
}
}
out.at(0).get() = mergeChannels(toMerge);
}
void ChannelReorderFilter::setChannel(std::size_t n)
{
if (n == channelAssignment_.size())
{
// stop rec + update
signalFilterSettingsChanged().emitSignal();
return;
}
else if (n < channelAssignment_.size())
{
// remove one channel
QSpinBox *box = channelAssignment_.back().getPtr();
channelAssignment_.pop_back();
layout_->removeWidget(box);
box->setParent(nullptr);
box->deleteLater();
}
else
{
// add one channel
auto box = util::make_unique<QSpinBox>();
box->setRange(0, 9);
box->setSingleStep(1);
box->setValue(channelAssignment_.size());
channelAssignment_.emplace_back(*box);
// connect
QObject::connect(box.get(), SIGNAL(valueChanged(int)),
&(this->signalFilterSettingsChanged()),
SIGNAL(signal()));
layout_->addWidget(box.release());
}
// rec
setChannel(n);
}
}
}
@@ -0,0 +1,100 @@
#ifndef CVVISUAL_CHANNELREORDERFILTER_HPP
#define CVVISUAL_CHANNELREORDERFILTER_HPP
#include <vector>
#include <QVBoxLayout>
#include <QSpinBox>
#include "../filterfunctionwidget.hpp"
#include "../../util/observer_ptr.hpp"
namespace cvv
{
namespace qtutil
{
/**
* @brief Class providing a filter that reorders an input mat's channels.
*/
class ChannelReorderFilter : public FilterFunctionWidget<1, 1>
{
Q_OBJECT
public:
/**
* @brief The input type.
*/
using InputArray = FilterFunctionWidget<1, 1>::InputArray;
/**
* @brief The output type.
*/
using OutputArray = FilterFunctionWidget<1, 1>::OutputArray;
/**
* @brief Constructor
*/
ChannelReorderFilter(QWidget *parent = nullptr);
/**
* @brief Applys the filter to in and saves the result in out.
* @param in The input images.
* @param out The output images.
*/
virtual void applyFilter(InputArray in, OutputArray out) const override;
/**
* @brief Checks whether input can be progressed by the applyFilter
*function.
* @param in The input images.
* @return bool = true: the filter can be executed.
* bool = false: the filter cant be executed (e.g. images
*have wrong depth)
* QString = message for the user (e.g. why the filter can't
*be progressed.)
*/
virtual std::pair<bool, QString> checkInput(InputArray) const override;
/**
* @brief Returns the number of output channels.
* @return the number of output channels.
*/
int outputChannels()
{
return channel_->value();
}
private
slots:
/**
* @brief Sets the number of channels.
* @param n The number of channels.
*/
void setChannel(int n)
{
setChannel(static_cast<std::size_t>(n));
}
/**
* @brief Sets the number of channels.
* @param n The number of channels.
*/
void setChannel(std::size_t n);
private:
/**
* @brief The layout.
*/
util::ObserverPtr<QVBoxLayout> layout_;
/**
* @brief The spinbox to select the number of channels.
*/
util::ObserverPtr<QSpinBox> channel_;
/**
* @brief Spin boxes for the channel reordering.
*/
std::vector<util::ObserverPtr<QSpinBox>> channelAssignment_;
};
}
}
#endif // CVVISUAL_CHANNELREORDERFILTER_HPP
@@ -0,0 +1,132 @@
#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <unordered_map>
#include <QComboBox>
#include <QLabel>
#include <QString>
#include <QVBoxLayout>
#include "../../util/util.hpp"
#include "diffFilterWidget.hpp"
namespace cvv
{
using namespace cv;
namespace qtutil
{
DiffFilterFunction::DiffFilterFunction(QWidget *parent)
: FilterFunctionWidget<2, 1>{ parent },
filterType_{ DiffFilterType::GRAYSCALE }
{
auto layout = util::make_unique<QVBoxLayout>();
auto comboBox = util::make_unique<QComboBox>();
filterMap_.insert(
std::make_pair<std::string, std::function<void(void)>>(
"Hue", [this]()
{ filterType_ = DiffFilterType::HUE; }));
filterMap_.insert(
std::make_pair<std::string, std::function<void(void)>>(
"Saturation", [this]()
{ filterType_ = DiffFilterType::SATURATION; }));
filterMap_.insert(
std::make_pair<std::string, std::function<void(void)>>(
"Value", [this]()
{ filterType_ = DiffFilterType::VALUE; }));
filterMap_.insert(
std::make_pair<std::string, std::function<void(void)>>(
"Grayscale", [this]()
{ filterType_ = DiffFilterType::GRAYSCALE; }));
// Register filter names at comboBox
comboBox->addItems(DiffFilterFunction::extractStringListfromMap());
connect(comboBox.get(), SIGNAL(currentIndexChanged(const QString &)),
this, SLOT(updateFilterType(const QString &)));
// Add title of comboBox and comboBox to the layout
layout->addWidget(
util::make_unique<QLabel>("Select a filter").release());
layout->addWidget(comboBox.release());
setLayout(layout.release());
}
void DiffFilterFunction::applyFilter(InputArray in, OutputArray out) const
{
auto check = checkInput(in);
if (!check.first)
{
return;
}
if (filterType_ == DiffFilterType::GRAYSCALE)
{
out.at(0).get() = cv::abs(in.at(0).get() - in.at(1).get());
return;
}
cv::Mat originalHSV, filteredHSV;
cv::cvtColor(in.at(0).get(), originalHSV, COLOR_BGR2HSV);
cv::cvtColor(in.at(1).get(), filteredHSV, COLOR_BGR2HSV);
auto diffHSV = cv::abs(originalHSV - filteredHSV);
std::array<cv::Mat, 3> splitVector;
cv::split(diffHSV, splitVector.data());
out.at(0).get() = splitVector.at(static_cast<size_t>(filterType_));
}
std::pair<bool, QString> DiffFilterFunction::checkInput(InputArray in) const
{
if (in.at(0).get().size() != in.at(1).get().size())
{
return std::make_pair(false, "Images need to have same size");
}
size_t inChannels = in.at(0).get().channels();
if (inChannels != static_cast<size_t>(in.at(1).get().channels()))
{
return std::make_pair(
false, "Images need to have same number of channels");
}
if (inChannels == 1 && filterType_ != DiffFilterType::GRAYSCALE)
{
return std::make_pair(false, "Images are grayscale, but "
"selected Filter can only "
"progress 3-channel images");
}
if (inChannels != 1 && inChannels != 3 && inChannels != 4)
{
return std::make_pair(
false, "Images must have one, three or four channels");
}
return std::make_pair(true, "Images can be converted");
}
QStringList DiffFilterFunction::extractStringListfromMap() const
{
QStringList stringList{};
for (auto mapElem : filterMap_)
{
stringList << QString::fromStdString(mapElem.first);
}
return stringList;
}
void DiffFilterFunction::updateFilterType(const QString &name)
{
filterMap_.find(name.toStdString())->second();
signalFilterSettingsChanged().emitSignal();
}
}
}
@@ -0,0 +1,88 @@
#ifndef CVVISUAL_DIFF_FILTER_WIDGET_HPP
#define CVVISUAL_DIFF_FILTER_WIDGET_HPP
#include <unordered_map>
#include "../filterselectorwidget.hpp"
namespace cvv
{
namespace qtutil
{
/**
* @brief Enum of the possible types of difference filters.
*/
enum class DiffFilterType
{
HUE = 0,
SATURATION = 1,
VALUE = 2,
LUMINANCE = VALUE,
GRAYSCALE = 3
};
/**
* @brief Class providing functionality to compute a difference image of two
* input matrices.
*/
class DiffFilterFunction : public FilterFunctionWidget<2, 1>
{
Q_OBJECT
public:
/**
* @brief The input type.
*/
using InputArray = FilterFunctionWidget<2, 1>::InputArray;
// std::array<util::Reference<const cv::Mat>,2>
/**
* @brief The output type.
*/
using OutputArray = FilterFunctionWidget<2, 1>::OutputArray;
// std::array<util::Reference<cv::Mat>,1>
/**
* @brief Constructs DiffFilterFunction with default filter grayscale.
* @param parent The parent of the widget
*/
DiffFilterFunction(QWidget *parent = nullptr);
/**
* @brief Applys difference filter specified by filterType_.
* @param in Array of input matrices
* @param out Array of output matrices
*/
void applyFilter(InputArray in, OutputArray out) const CV_OVERRIDE;
/**
* @brief Checks whether matrices in 'in' can be processed by this
* DiffFilter
*/
std::pair<bool, QString> checkInput(InputArray in) const CV_OVERRIDE;
private:
DiffFilterType filterType_;
//< Type of difference filter that is to be applied
std::unordered_map<std::string, std::function<void(void)>> filterMap_;
//< Map of all available filters with their names
/**
* @brief Extracts the names of all available filters from filterMap_.
*/
QStringList extractStringListfromMap() const;
private
slots:
/**
* @brief Sets filterType_ and emits signFilterSettingsChanged.
* @param type The name of the new DiffFilterType.
*/
void updateFilterType(const QString &type);
};
}
}
#endif
@@ -0,0 +1,156 @@
#include "grayfilterwidget.hpp"
#include <QPushButton>
#include <QLabel>
#include "../filterselectorwidget.hpp"
#include "../../util/util.hpp"
#include "../util.hpp"
namespace cvv
{
namespace qtutil
{
GrayFilterWidget::GrayFilterWidget(QWidget *parent)
: FilterFunctionWidget<1, 1>{ parent }, layout_{ nullptr },
channel_{ nullptr }, chanValues_{}
{
// set a tooltip
setToolTip(
"nonexistant channels from source will be seen as a zero mat");
// create the layout
auto lay = util::make_unique<QVBoxLayout>();
layout_ = *lay;
// create the spinbox to select the number of channels
auto channel = util::make_unique<QSpinBox>();
channel_ = *channel;
// create a button to set up the default gray filter
auto button = util::make_unique<QPushButton>("use default rgb to gray");
QObject::connect(button.get(), SIGNAL(clicked()), this, SLOT(setStd()));
// set up the spinbox to select the number of channels.
channel_->setRange(1, 10);
// and connect it with the slot setChannel.
QObject::connect(channel_.getPtr(), SIGNAL(valueChanged(int)), this,
SLOT(setChannel(int)));
// build ui (some labels for the user are added)
layout_->addWidget(button.release());
layout_->addWidget(
util::make_unique<QLabel>("Number of channels").release());
layout_->addWidget(channel.release());
layout_->addWidget(
util::make_unique<QLabel>("Percentage for channels").release());
setLayout(lay.release());
// set up the default gray filter
setStd();
}
void GrayFilterWidget::applyFilter(InputArray in, OutputArray out) const
{
// check weather the filter can be applied
if (!(checkInput(in).first))
{
return;
}
// the filter can be applied
// split the cannels of the input image
auto channels = splitChannels(in.at(0).get());
// create a zero image
cv::Mat tmp = cv::Mat::zeros(in.at(0).get().rows, in.at(0).get().cols,
in.at(0).get().depth());
// multiply all channels with their factor and add it to tmp
// if there are factors for more channels than the input image has, this
// channels
// will be ignored
for (std::size_t i = 0;
((i < channels.size()) && (i < chanValues_.size())); i++)
{
// multiply each channel with its factor and add the result to
// tmp
tmp += channels.at(i) * (chanValues_.at(i)->value());
}
// finally assign tmp to out
out.at(0).get() = tmp;
}
std::pair<bool, QString> GrayFilterWidget::checkInput(InputArray) const
{
// checks wheather the current settings are valid.
// add up all factors
double sum = 0;
for (auto &elem : chanValues_)
{
sum += (elem->value());
}
// check wheather the sum is <=1
if (sum > 1)
{
// the settings are invalid => return fale + a error message
return { false, QString{ "total : " } + QString::number(sum) +
QString{ " > 1" } };
}
// the settings are valid
return { true, "" };
}
void GrayFilterWidget::setChannel(std::size_t n)
{
/*
* this function is recursive.
*/
if (n == chanValues_.size())
{
// stop recursion
return;
}
else if (n < chanValues_.size())
{
// currently there are more channels than requested.
// => remove one channel
// remove a spin box from the vector
QDoubleSpinBox *box = chanValues_.back().getPtr();
chanValues_.pop_back();
// remove it from the layout
layout_->removeWidget(box);
// reset the parent
box->setParent(nullptr);
// finally delete it
box->deleteLater();
}
else
{
// currently less channel than requested
// => add one channel
// create a new spinbox, set its range and step size.
auto box = util::make_unique<QDoubleSpinBox>();
box->setRange(0, 1);
box->setSingleStep(0.01);
// add this box to the vector
chanValues_.emplace_back(*box);
// connect it to signFilterSettingsChanged_.
QObject::connect(box.get(), SIGNAL(valueChanged(double)),
&(this->signalFilterSettingsChanged()),
SIGNAL(signal()));
// and add it to the layout
layout_->addWidget(box.release());
}
// recursion
setChannel(n);
}
void GrayFilterWidget::setStd()
{
// use 3 channels (b g r)
channel_->setValue(3);
// set factor for b
chanValues_.at(0)->setValue(0.114);
// set factor for g
chanValues_.at(1)->setValue(0.587);
// set factor for r
chanValues_.at(2)->setValue(0.299);
}
}
}
@@ -0,0 +1,105 @@
#ifndef CVVISUAL_GRAYFILTERWIDGET_HPP
#define CVVISUAL_GRAYFILTERWIDGET_HPP
#include <vector>
#include <QVBoxLayout>
#include <QSpinBox>
#include <QDoubleSpinBox>
#include <QWidget>
#include <QObject>
#include <QString>
#include "opencv2/core.hpp"
#include "../filterfunctionwidget.hpp"
#include "../../util/observer_ptr.hpp"
namespace cvv
{
namespace qtutil
{
/**
* @brief Represents a gray filter.
*
* The user can select the factors used for every channel.
*/
class GrayFilterWidget : public FilterFunctionWidget<1, 1>
{
Q_OBJECT
public:
/**
* @brief The input type.
*/
using InputArray = FilterFunctionWidget<1, 1>::InputArray;
/**
* @brief The output type.
*/
using OutputArray = FilterFunctionWidget<1, 1>::OutputArray;
/**
* @brief Constructor
*/
GrayFilterWidget(QWidget *parent = nullptr);
/**
* @brief Applys the filter to in and saves the result in out.
* @param in The input images.
* @param out The output images.
*/
virtual void applyFilter(InputArray in, OutputArray out) const override;
/**
* @brief Checks whether input can be progressed by the applyFilter
*function.
* @param in The input images.
* @return bool = true: the filter can be executed.
* bool = false: the filter cant be executed (e.g. images
*have wrong depth)
* QString = message for the user (e.g. why the filter can't
*be progressed.)
*/
virtual std::pair<bool, QString> checkInput(InputArray) const override;
private
slots:
/**
* @brief Sets the number of channels.
* @param n The number of channels.
*/
void setChannel(int n)
{
setChannel(static_cast<std::size_t>(n));
}
/**
* @brief Sets the number of channels.
* @param n The number of channels.
*/
void setChannel(std::size_t n);
/**
* @brief Sets the standard gray filter. (0.299*R + 0.587*G + 0.114*B)
*/
void setStd();
private:
/**
* @brief The layout.
*/
util::ObserverPtr<QVBoxLayout> layout_;
/**
* @brief The spinbox to select the number of channels.
*/
util::ObserverPtr<QSpinBox> channel_;
/**
* @brief Spin boxes for the factor for each channel.
*/
std::vector<util::ObserverPtr<QDoubleSpinBox>> chanValues_;
};
}
}
#endif // CVVISUAL_GRAYFILTERWIDGET_HPP
@@ -0,0 +1,79 @@
#include <opencv2/core.hpp>
#include <Qt>
#include "QLabel"
#include "QSlider"
#include "QVBoxLayout"
#include "../../util/util.hpp"
#include "overlayfilterwidget.hpp"
namespace cvv
{
namespace qtutil
{
OverlayFilterWidget::OverlayFilterWidget(QWidget *parent)
: FilterFunctionWidget<2, 1>{ parent }, opacityOfFilterImg_{ 0.5 }
{
auto layout = util::make_unique<QVBoxLayout>();
auto slider = util::make_unique<QSlider>(Qt::Horizontal);
slider->setRange(0, 100);
slider->setSliderPosition(50);
slider->setTickPosition(QSlider::TicksAbove);
slider->setTickInterval(10);
connect(slider.get(), SIGNAL(valueChanged(int)), this,
SLOT(updateOpacity(int)));
// Add title of slider and slider to the layout
layout->addWidget(util::make_unique<QLabel>(
"Select opacity of right image").release());
layout->addWidget(slider.release());
setLayout(layout.release());
}
void OverlayFilterWidget::applyFilter(InputArray in, OutputArray out) const
{
auto check = checkInput(in);
if (!check.first)
{
return;
}
cv::addWeighted(in.at(0).get(), 1 - opacityOfFilterImg_, in.at(1).get(),
opacityOfFilterImg_, 0, out.at(0).get());
}
std::pair<bool, QString> OverlayFilterWidget::checkInput(InputArray in) const
{
// check whether images have same size
if (in.at(0).get().size() != in.at(1).get().size())
{
return std::make_pair(false, "Images need to have same size");
}
// check whether images have same number of channels
if (in.at(0).get().channels() != in.at(1).get().channels())
{
return std::make_pair(
false, "Images need to have same number of channels");
}
return std::make_pair(true, "Images can be converted");
}
void OverlayFilterWidget::updateOpacity(int newOpacity)
{
opacityOfFilterImg_ = newOpacity / 100.0;
signalFilterSettingsChanged().emitSignal();
}
}
}
@@ -0,0 +1,72 @@
#ifndef CVVISUAL_OVERLAY_FILTER_WIDGET_HPP
#define CVVISUAL_OVERLAY_FILTER_WIDGET_HPP
#include <unordered_map>
#include "../../util/observer_ptr.hpp"
#include "../filterselectorwidget.hpp"
namespace cvv
{
namespace qtutil
{
/**
* @brief Class providing functionality to compute an overlay image of two
* input matrices.
*/
class OverlayFilterWidget : public FilterFunctionWidget<2, 1>
{
Q_OBJECT
public:
/**
* @brief The input type.
*/
using InputArray = FilterFunctionWidget<2, 1>::InputArray;
// std::array<util::Reference<const cv::Mat>,2>
/**
* @brief The output type.
*/
using OutputArray = FilterFunctionWidget<2, 1>::OutputArray;
// std::array<util::Reference<cv::Mat>,1>
/**
* @brief Constructs OverlayFilterWidget with default opacity 0,5.
* @param parent The parent of the widget
*/
OverlayFilterWidget(QWidget *parent = nullptr);
/**
* The opacity of the second image while overlaying is indicated by
* opacityOfFilterImg_.
* @brief Overlays the original images
* @param in Array of input matrices
* @param out Array of output matrices
*/
void applyFilter(InputArray in, OutputArray out) const CV_OVERRIDE;
/**
* Checks whether the matrices have the same size and same number of
* channels.
* @brief Checks whether matrices in "in" can be processed by Overlayfilter
* @param in Array of input matrices
*/
std::pair<bool, QString> checkInput(InputArray in) const CV_OVERRIDE;
private:
double opacityOfFilterImg_;
//< Opacity of the second input image when ovelaying
private
slots:
/**
* @brief Sets opacityOfFilterImg_ and emits signFilterSettingsChanged.
* @param op New opacity
*/
void updateOpacity(int op);
};
}
}
#endif
@@ -0,0 +1,283 @@
#include "sobelfilterwidget.hpp"
#include "opencv2/imgproc.hpp"
#include <QVBoxLayout>
#include <QLabel>
#include "../../util/util.hpp"
#include "../filterfunctionwidget.hpp"
#include "../filterselectorwidget.hpp"
namespace cvv
{
using namespace cv;
namespace qtutil
{
SobelFilterWidget::SobelFilterWidget(QWidget *parent)
: FilterFunctionWidget<1, 1>{ parent }, dx_{ nullptr }, dy_{ nullptr },
ksize_{ nullptr }, borderType_{ nullptr }, gray_{ nullptr },
grayFilter_{ nullptr }, reorder_{ nullptr }, reorderFilter_{ nullptr }
{
auto dx = util::make_unique<QSpinBox>();
dx_ = *dx;
auto dy = util::make_unique<QSpinBox>();
dy_ = *dy;
auto ksize = util::make_unique<QComboBox>();
ksize_ = *ksize;
auto borderType = util::make_unique<QComboBox>();
borderType_ = *borderType;
// set up elements
dx_->setRange(0, 6);
dy_->setRange(0, 6);
ksize_->addItem("1");
ksize_->addItem("3");
ksize_->addItem("5");
ksize_->addItem("7");
ksize_->addItem("FILTER_SCHARR(-1)");
ksize_->setCurrentIndex(1);
borderType_->addItem("BORDER_DEFAULT");
borderType_->addItem("BORDER_CONSTANT");
borderType_->addItem("BORDER_REPLICATE");
borderType_->addItem("BORDER_REFLECT");
borderType_->addItem("BORDER_REFLECT_101");
// connect
QObject::connect(dx_.getPtr(), SIGNAL(valueChanged(int)),
&(this->signalFilterSettingsChanged()),
SIGNAL(signal()));
QObject::connect(dy_.getPtr(), SIGNAL(valueChanged(int)),
&(this->signalFilterSettingsChanged()),
SIGNAL(signal()));
QObject::connect(ksize_.getPtr(), SIGNAL(currentIndexChanged(int)),
&(this->signalFilterSettingsChanged()),
SIGNAL(signal()));
QObject::connect(borderType_.getPtr(), SIGNAL(currentIndexChanged(int)),
&(this->signalFilterSettingsChanged()),
SIGNAL(signal()));
// subfilter reorder
auto reorder = util::make_unique<QCheckBox>("Reorder channels");
reorder_ = *reorder;
auto reorderFilter = util::make_unique<ChannelReorderFilter>();
reorderFilter_ = *reorderFilter;
reorder_->setChecked(false);
reorderFilter_->setVisible(false);
// visible
QObject::connect(reorder_.getPtr(), SIGNAL(clicked(bool)),
reorderFilter_.getPtr(), SLOT(setVisible(bool)));
// settings
QObject::connect(reorder_.getPtr(), SIGNAL(clicked()),
&(this->signalFilterSettingsChanged()),
SIGNAL(signal()));
QObject::connect(
&(reorderFilter_.getPtr()->signalFilterSettingsChanged()),
SIGNAL(signal()), &(this->signalFilterSettingsChanged()),
SIGNAL(signal()));
// subfilter gray
auto gray = util::make_unique<QCheckBox>("Apply gray filter");
gray_ = *gray;
auto grayFilter = util::make_unique<GrayFilterWidget>();
grayFilter_ = *grayFilter;
gray_->setChecked(false);
grayFilter_->setVisible(false);
// visible
QObject::connect(gray_.getPtr(), SIGNAL(clicked(bool)),
grayFilter_.getPtr(), SLOT(setVisible(bool)));
// settings
QObject::connect(gray_.getPtr(), SIGNAL(clicked()),
&(this->signalFilterSettingsChanged()),
SIGNAL(signal()));
QObject::connect(&(grayFilter_.getPtr()->signalFilterSettingsChanged()),
SIGNAL(signal()),
&(this->signalFilterSettingsChanged()),
SIGNAL(signal()));
// build ui
auto lay = util::make_unique<QVBoxLayout>();
lay->addWidget(reorder.release());
lay->addWidget(reorderFilter.release());
lay->addWidget(gray.release());
lay->addWidget(grayFilter.release());
lay->addWidget(util::make_unique<QLabel>("dx").release());
lay->addWidget(dx.release());
lay->addWidget(util::make_unique<QLabel>("dy").release());
lay->addWidget(dy.release());
lay->addWidget(util::make_unique<QLabel>("ksize").release());
lay->addWidget(ksize.release());
lay->addWidget(util::make_unique<QLabel>("borderType").release());
lay->addWidget(borderType.release());
setLayout(lay.release());
// emit first update
signalFilterSettingsChanged().emitSignal();
}
void SobelFilterWidget::applyFilter(InputArray in, OutputArray out) const
{
int ksize = 3;
switch (ksize_->currentIndex())
{
case 0:
ksize = 1;
break;
case 1:
ksize = 3;
break;
case 2:
ksize = 5;
break;
case 3:
ksize = 7;
break;
case 4:
ksize = FILTER_SCHARR;
break;
}
int borderType = cv::BORDER_DEFAULT;
switch (borderType_->currentIndex())
{
case 0:
borderType = cv::BORDER_DEFAULT;
break;
case 1:
borderType = cv::BORDER_CONSTANT;
break;
case 2:
borderType = cv::BORDER_REPLICATE;
break;
case 3:
borderType = cv::BORDER_REFLECT;
break;
case 4:
borderType = cv::BORDER_REFLECT_101;
break;
}
int dx = dx_->value();
int dy = dy_->value();
// apply filter
cvv::util::Reference<const cv::Mat> inar = in.at(0).get();
cvv::util::Reference<cv::Mat> outar = out.at(0).get();
// first reorder
if (reorder_->isChecked())
{
reorderFilter_->applyFilter({ { inar } }, { { outar } });
// out should be new input
inar = outar.get();
}
// then gray
if (gray_->isChecked())
{
grayFilter_->applyFilter({ { inar } }, { { outar } });
// out should be new input
inar = outar.get();
}
Sobel(inar.get(), outar.get(), -1, dx, dy, ksize, 1, 0, borderType);
}
std::pair<bool, QString> SobelFilterWidget::checkInput(InputArray in) const
{
// check depth in CV_8U,CV_16U,CV_16S,CV_32F,CV_64F
switch (in.at(0).get().depth())
{
case CV_8U:
case CV_16U:
case CV_16S:
case CV_32F:
case CV_64F:
break;
default:
return { false, QString("unsupported depth: ") +
QString::number(in.at(0).get().depth()) };
}
// check subfilter
if (gray_->isChecked())
{
auto resultGray = grayFilter_->checkInput(in);
if (!resultGray.first)
{
return resultGray;
}
}
if (reorder_->isChecked())
{
auto resultReorder = reorderFilter_->checkInput(in);
if (!resultReorder.first)
{
return resultReorder;
}
}
// check channels
if (!(gray_->isChecked())) // gray filter => channels will be 1
{
if ((reorder_->isChecked()) &&
(reorderFilter_->outputChannels() > 4)) // no gray
{
return { false, "channels>4 (use gray filter or "
"reorder with <=4 output channels)" };
}
else if ((in.at(0).get().channels() > 4)) // no gray filter +
// reorder
{
return { false, "channels>4 (use gray filter or "
"reorder with <=4 output channels)" };
}
}
int dx = dx_->value();
int dy = dy_->value();
if (dx == 0 && dy == 0)
{
return { false, "dx=0 and dy=0" };
}
// dx,dy<ksize, if sharr: dx XOR dy
if (dx_->value() == 0 && dy_->value() == 0)
{
return { false, "dx=0 and dy=0" };
}
int ksize = 3;
switch (ksize_->currentIndex())
{
case 0:
ksize = 1;
break;
case 1:
ksize = 3;
break;
case 2:
ksize = 5;
break;
case 3:
ksize = 7;
break;
case 4:
ksize = FILTER_SCHARR;
break;
}
if (ksize == FILTER_SCHARR)
{
if (dx + dy != 1)
{
return { false, "ksize=FILTER_SCHARR but dx+dy != 1" };
}
}
else
{
if ((dx >= 3 || dy >= 3) && (dx >= ksize || dy >= ksize))
{
return { false, "dx or dy is to big" };
}
}
return { true, "" };
}
}
}
@@ -0,0 +1,97 @@
#ifndef CVVISUAL_SOBELFILTERWIDGET_HPP
#define CVVISUAL_SOBELFILTERWIDGET_HPP
#include <QSpinBox>
#include <QComboBox>
#include <QLabel>
#include <QCheckBox>
#include "../../util/observer_ptr.hpp"
#include "../filterfunctionwidget.hpp"
#include "grayfilterwidget.hpp"
#include "channelreorderfilter.hpp"
namespace cvv
{
namespace qtutil
{
/**
* @brief Represents the opencv sobel filter.
*/
class SobelFilterWidget : public FilterFunctionWidget<1, 1>
{
public:
/**
* @brief The input type.
*/
using InputArray = typename FilterFunctionWidget<1, 1>::InputArray;
/**
* @brief The output type.
*/
using OutputArray = typename FilterFunctionWidget<1, 1>::OutputArray;
/**
* @brief Constructor
*/
SobelFilterWidget(QWidget *parent = nullptr);
/**
* @brief Applys the filter to in and saves the result in out.
* @param in The input images.
* @param out The output images.
*/
virtual void applyFilter(InputArray in, OutputArray out) const override;
/**
* @brief Checks whether input can be progressed by the applyFilter
*function.
* @param in The input images.
* @return bool = true: the filter can be executed.
* bool = false: the filter cant be executed (e.g. images
*have wrong depth)
* QString = message for the user (e.g. why the filter can't
*be progressed.)
*/
virtual std::pair<bool, QString> checkInput(InputArray in) const
override;
private:
/**
* @brief Selection for parameter dx.
*/
util::ObserverPtr<QSpinBox> dx_;
/**
* @brief Selection for parameter dy.
*/
util::ObserverPtr<QSpinBox> dy_;
/**
* @brief Selection for parameter ksize.
*/
util::ObserverPtr<QComboBox> ksize_;
/**
* @brief Selection for parameter borderType.
*/
util::ObserverPtr<QComboBox> borderType_;
/**
* @brief Wheather a gray filter should be applied first (after
* reorder).
*/
util::ObserverPtr<QCheckBox> gray_;
/**
* @brief a gray filter.
*/
util::ObserverPtr<GrayFilterWidget> grayFilter_;
/**
* @brief Wheather a reorder filter should be applied first.
*/
util::ObserverPtr<QCheckBox> reorder_;
/**
* @brief a reorder filter.
*/
util::ObserverPtr<ChannelReorderFilter> reorderFilter_;
};
}
}
#endif // SOBELFILTERWIDGET_HPP
@@ -0,0 +1,102 @@
#ifndef CVVISUAL_FILTERFUNCTIONWIDGET_HPP
#define CVVISUAL_FILTERFUNCTIONWIDGET_HPP
// STD
#include <array>
#include <type_traits>
// QT
#include <QWidget>
#include <QString>
// OCV
#include "opencv2/core.hpp"
// cvv
#include "signalslot.hpp"
#include "../util/util.hpp"
namespace cvv
{
namespace qtutil
{
/**
* @brief The input type for FilterFunctionWidgets.
*/
template <std::size_t In>
using CvvInputArray = std::array<util::Reference<const cv::Mat>, In>;
/**
* @brief The output type for FilterFunctionWidgets.
*/
template <std::size_t Out>
using CvvOutputArray = std::array<util::Reference<cv::Mat>, Out>;
/**
* @brief The type for the input of the filter.
*
* Inherit from it if you want to provide an image filter.
* Use the widget to let the user choose parameters.
* Emit stateChanged when user input leads to different parameters.
*
* @tparam In The number of input images.
* @tparam Out The number of output images.
*/
template <std::size_t In, std::size_t Out>
class FilterFunctionWidget : public QWidget
{
static_assert(Out > 0, "Out should be >0.");
public:
/**
* @brief The input type.
*/
using InputArray = CvvInputArray<In>;
/**
* @brief The output type.
*/
using OutputArray = CvvOutputArray<Out>;
/**
* @brief Constructor
* @param parent Parent widget.
*/
FilterFunctionWidget(QWidget *parent = nullptr)
: QWidget{ parent }, signFilterSettingsChanged_{}
{
}
/**
* @brief Applys the filter to in and saves the result in out.
* @param in The input images.
* @param out The output images.
*/
virtual void applyFilter(InputArray in, OutputArray out) const = 0;
/**
* @brief Checks whether input can be progressed by the applyFilter
*function.
* @param in The input images.
* @return bool = true: the filter can be executed.
* bool = false: the filter cant be executed (e.g. images
*have wrong depth)
* QString = message for the user (e.g. why the filter can't
*be progressed.)
*/
virtual std::pair<bool, QString> checkInput(InputArray in) const = 0;
const Signal &signalFilterSettingsChanged() const
{
return signFilterSettingsChanged_;
}
private:
/**
* @brief Signal to emit when user input leads to different parameters.
*/
const Signal signFilterSettingsChanged_;
};
}
} // end namespaces qtutil, cvv
#endif // CVVISUAL_FILTERFUNCTIONWIDGET_HPP
@@ -0,0 +1,206 @@
#ifndef CVVISUAL_FILTERSELECTORWIDGET_HPP
#define CVVISUAL_FILTERSELECTORWIDGET_HPP
// STD
#include <stdexcept>
#include <array>
#include <type_traits>
// QT
#include <QWidget>
#include <QString>
#include <QComboBox>
#include <QPushButton>
// OCV
#include "opencv2/core.hpp"
// CVV
#include "signalslot.hpp"
#include "registerhelper.hpp"
#include "filterfunctionwidget.hpp"
#include "../util/util.hpp"
#include "../util/observer_ptr.hpp"
namespace cvv
{
namespace qtutil
{
// forward
template <std::size_t In, std::size_t Out, class Filter>
bool registerFilter(const QString &name);
/**
* @brief The FilterSelectorWidget class
*/
template <std::size_t In, std::size_t Out>
class FilterSelectorWidget
: public RegisterHelper<FilterFunctionWidget<In, Out>, QWidget *>,
public FilterFunctionWidget<In, Out>
{
static_assert(Out > 0, "Out must not be 0!");
public:
/**
* @brief The input type.
*/
using InputArray = typename FilterFunctionWidget<In, Out>::InputArray;
/**
* @brief The output type.
*/
using OutputArray = typename FilterFunctionWidget<In, Out>::OutputArray;
/**
* @brief Constuctor
* @param parent The parent widget.
*/
FilterSelectorWidget(QWidget *parent = nullptr)
: RegisterHelper<FilterFunctionWidget<In, Out>, QWidget *>{},
FilterFunctionWidget<In, Out>{ parent }, layout_{ nullptr },
currentFilter_{ nullptr }, slotFilterSelected_{ [this]()
{
this->updatedSelectedFilter();
} }
{
auto lay = util::make_unique<QVBoxLayout>();
layout_ = *lay;
this->layout_->setAlignment(Qt::AlignTop);
this->layout_->setSpacing(0);
this->layout_->addWidget((this->comboBox_));
// connect elem selected with update for it
QObject::connect(&(this->signalElementSelected()),
SIGNAL(signal(QString)),
&(this->slotFilterSelected_), SLOT(slot()));
this->setLayout(lay.release());
// update for initial selection (if it is valid)
if (this->has(this->selection()))
{
updatedSelectedFilter();
}
// add an apply button
auto button = util::make_unique<QPushButton>("apply");
// connect it
QObject::connect(button.get(), SIGNAL(clicked()),
&(this->signalFilterSettingsChanged()),
SIGNAL(signal()));
this->layout_->addWidget(button.release());
}
~FilterSelectorWidget()
{
}
/**
* @brief Applies the selected filter.
* @param in Input images.
* @param out Output images.
* @throw std::invalid_argument checkInput(in).first==false
*/
virtual void applyFilter(InputArray in, OutputArray out) const override
{
auto check = checkInput(in);
if (!check.first)
{
throw std::invalid_argument{
check.second.toStdString()
};
}
return currentFilter_->applyFilter(in, out);
}
/**
* @brief Checks whether input can be progressed by the selected filter.
* @param in The input images.
* @return bool = true: the filter can be executed.
* bool = false: the filter cant be executed (e.g. images
*have wrong depth)
* QString = message for the user (e.g. why the filter can't
*be progressed.)
*/
virtual std::pair<bool, QString> checkInput(InputArray in) const
override
{
if (!currentFilter_)
{
return { false, "No entry selected." };
}
return currentFilter_->checkInput(in);
}
/**
* @brief Registers a FilterFunctionWidget with a given name.
* @param name The name.
* @return true: If the function was registered. false: If the name was
* taken
* (the function was not registered!)
*/
template <class Filter> static bool registerFilter(const QString &name)
{
return qtutil::registerFilter<In, Out, Filter>(name);
}
protected:
/**
* @brief The layout
*/
util::ObserverPtr<QVBoxLayout> layout_;
private:
/**
* @brief Performs the update after a selection occurred.
*/
void updatedSelectedFilter()
{
if ((this->currentFilter_))
{
layout_->removeWidget((this->currentFilter_.getPtr()));
// disconnect
QObject::disconnect(
&(this->currentFilter_
->signalFilterSettingsChanged()),
0, &(this->signalFilterSettingsChanged()), 0);
currentFilter_->deleteLater();
}
auto filt = (*this)()(nullptr);
this->currentFilter_ = *filt;
this->layout_->insertWidget(2, filt.release());
// pass signal
QObject::connect(
&(this->currentFilter_->signalFilterSettingsChanged()),
SIGNAL(signal()), &(this->signalFilterSettingsChanged()),
SIGNAL(signal()));
// settings changed
this->signalFilterSettingsChanged().emitSignal();
}
/**
* @brief the current filter
*/
util::ObserverPtr<FilterFunctionWidget<In, Out>> currentFilter_;
/**
* @brief Slot called when user changes selection
*/
const Slot slotFilterSelected_;
}; // FilterSelectorWidget
/**
* @brief Registers a FilterFunctionWidget with a given name.
* @param name The name.
* @return true: If the function was registered. false: If the name was taken
* (the function was not registered!)
*/
template <std::size_t In, std::size_t Out, class Filter>
bool registerFilter(const QString &name)
{
return FilterSelectorWidget<In, Out>::registerElement(
name, [](QWidget *parent)
{
return std::unique_ptr<FilterFunctionWidget<In, Out>>{
new Filter{ parent }
};
});
}
}
} // end namespaces qtutil, cvv
#endif // CVVISUAL_FILTERSELECTORWIDGET_HPP
+139
View File
@@ -0,0 +1,139 @@
#include "histogram.hpp"
#include <QHBoxLayout>
#include <iostream>
#include "util.hpp"
namespace cvv
{
namespace qtutil
{
Histogram::Histogram(const cv::Mat& mat, QWidget* parent)
:QWidget{parent},
histSize_(512, 200),
histLineWidth_(2),
histBackgroundColor_(255, 255, 255)
{
setMat(mat);
zoomableImage = new ZoomableImage();
auto layout = new QHBoxLayout();
layout->addWidget(zoomableImage);
setLayout(layout);
}
void Histogram::setMat(const cv::Mat& mat)
{
mat_ = mat;
}
cv::Rect Histogram::qrect2cvrect(const cv::Mat& mat, QRectF qrect)
{
double x1, y1, x2, y2;
qrect.getCoords(&x1, &y1, &x2, &y2);
x1 = std::max(0.0, x1);
y1 = std::max(0.0, y1);
x2 = std::min(static_cast<double>(mat.size().width), x2);
y2 = std::min(static_cast<double>(mat.size().height), y2);
double width = x2 - x1;
double height = y2 - y1;
return cv::Rect(x1, y1, width, height);
}
void Histogram::setArea(QRectF rect, qreal zoom)
{
(void)zoom;
channelHists_ = calcHist(mat_, qrect2cvrect(mat_, rect));
histMat_ = drawHist(channelHists_, histSize_, histLineWidth_, histBackgroundColor_);
zoomableImage->setMat(histMat_);
zoomableImage->showFullImage();
}
std::vector<cv::Mat> Histogram::calcHist(cv::Mat mat, cv::Rect rect, int bins, float rangeMin,
float rangeMax)
{
cv::Mat rectMat(mat, rect);
cv::Mat histMat;
std::vector<cv::Mat> channelPlanes = splitChannels(rectMat);
int histSize = bins;
float range[] = {rangeMin, rangeMax};
const float* histRange = {range};
bool uniform = true;
bool accumulate = false;
std::vector<cv::Mat> channelHists(channelPlanes.size());
for (size_t chan = 0; chan < channelPlanes.size(); chan++)
{
cv::calcHist(&channelPlanes[chan], 1, 0, cv::Mat(), channelHists[chan], 1, &histSize,
&histRange, uniform, accumulate);
}
return channelHists;
}
cv::Mat Histogram::drawHist(const std::vector<cv::Mat>& channelHists, cv::Size histSize,
int lineWidth, const cv::Scalar& backgroundColor)
{
int binCount = channelHists[0].rows;
int binWidth = cvRound(double(histSize.width)/binCount);
std::vector<cv::Scalar> colors{cv::Scalar(255, 0, 0), cv::Scalar(0, 255, 0),
cv::Scalar(0, 0, 255), cv::Scalar(0, 0, 0)}; // BGR
cv::Mat histMat(histSize, CV_8UC3, backgroundColor);
double maxVal = 0;
for (auto& hist : channelHists)
{
double tmpMaxVal;
cv::minMaxLoc(hist, NULL, &tmpMaxVal);
maxVal = std::max(maxVal, tmpMaxVal);
}
double valScale = histSize.height / maxVal;
for (size_t channel = 0; channel < channelHists.size(); channel++)
{
auto& hist = channelHists[channel];
auto& color = colors[channel];
for (int bin = 1; bin < binCount; bin++)
{
//printf("%zd:%d=%f\n", channel, bin, hist.at<float>(bin));
auto pt1 = cv::Point(binWidth * (bin-1),
histSize.height - cvRound(hist.at<float>(bin-1) * valScale));
auto pt2 = cv::Point(binWidth * bin,
histSize.height - cvRound(hist.at<float>(bin) * valScale));
cv::line(histMat, pt1, pt2, color, lineWidth);
}
}
int binTextStep = binCount / 5;
binTextStep = binTextStep - (binTextStep % 10); // round to tens
int fontFace = cv::FONT_HERSHEY_SCRIPT_SIMPLEX;
double fontScale = 0.5;
auto textColor = cv::Scalar::all(0);
int thickness = 1;
for (int binTextId = 0; binTextId < binCount; binTextId += binTextStep)
{
auto text = QString::number(binTextId).toLatin1();
auto textSize = cv::getTextSize(text.data(), fontFace, fontScale, thickness, NULL);
auto textPt = cv::Point(std::max(0, binWidth * binTextId - textSize.width/2), histSize.height);
cv::putText(histMat, text.data(), textPt, fontFace, fontScale, textColor, thickness);
auto linePt1 = cv::Point(binWidth * binTextId, 0);
auto linePt2 = cv::Point(binWidth * binTextId, histSize.height - textSize.height);
cv::line(histMat, linePt1, linePt2, textColor);
}
return histMat;
}
}
}
+45
View File
@@ -0,0 +1,45 @@
#ifndef CVVISUAL_HISTOGRAM_HPP
#define CVVISUAL_HISTOGRAM_HPP
#include <QWidget>
#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include "zoomableimage.hpp"
namespace cvv
{
namespace qtutil
{
class Histogram : public QWidget
{
Q_OBJECT
public:
Histogram(const cv::Mat& mat = cv::Mat{}, QWidget* parent = nullptr);
void setMat(const cv::Mat& mat);
std::vector<cv::Mat> calcHist(cv::Mat mat, cv::Rect rect, int bins = 256, float rangeMin = 0.0, float rangeMax = 256.0);
cv::Mat drawHist(const std::vector<cv::Mat>& channelHists, cv::Size histSize, int lineWidth = 2, const cv::Scalar& backgroundColor = cv::Scalar(255, 255, 255));
public slots:
void setArea(QRectF, qreal);
private:
cv::Rect qrect2cvrect(const cv::Mat& mat, QRectF qrect);
cv::Mat mat_;
std::vector<cv::Mat> channelHists_;
cv::Mat histMat_;
cv::Size histSize_;
int histLineWidth_;
cv::Scalar histBackgroundColor_;
ZoomableImage* zoomableImage;
};
}
}
#endif // CVVISUAL_HISTOGRAM_HPP
@@ -0,0 +1,29 @@
#include "histogramoptpanel.hpp"
#include <QVBoxLayout>
#include <QCheckBox>
namespace cvv
{
namespace qtutil
{
HistogramOptPanel::HistogramOptPanel(const Histogram& hist, bool showHideButton, QWidget* parent)
:QWidget{parent}
{
auto layout = new QVBoxLayout();
layout->setContentsMargins(0,0,0,0);
if (showHideButton) {
auto showCheckbox = new QCheckBox("Show Histogram");
showCheckbox->setChecked(false);
connect(showCheckbox, SIGNAL(clicked(bool)), &hist, SLOT(setVisible(bool)));
layout->addWidget(showCheckbox);
}
setLayout(layout);
}
}
}
@@ -0,0 +1,26 @@
#ifndef CVVISUAL_HISTOGRAM_OPT_PANEL
#define CVVISUAL_HISTOGRAM_OPT_PANEL
#include <QWidget>
#include "histogram.hpp"
namespace cvv
{
namespace qtutil
{
class HistogramOptPanel
: public QWidget
{
Q_OBJECT
public:
HistogramOptPanel(const Histogram& hist, bool showHideButton = true, QWidget* parent = nullptr);
};
}
}
#endif // CVVISUAL_HISTOGRAM_OPT_PANEL
@@ -0,0 +1,134 @@
#ifndef CVVISUAL_INTERVALLSELECTOR_HPP
#define CVVISUAL_INTERVALLSELECTOR_HPP
#include <vector>
#include <algorithm>
#include <QWidget>
#include <QDoubleSpinBox>
#include <QRadioButton>
#include <QCheckBox>
#include <QLabel>
#include <QVBoxLayout>
#include <QButtonGroup>
#include "../util/util.hpp"
#include "../util/observer_ptr.hpp"
#include "signalslot.hpp"
namespace cvv
{
namespace qtutil
{
/**
* @brief Selects elements from a double range. The elements are converted to a
* double using the
* given DoubleExtractor functor.
*/
class IntervallSelector : public QWidget
{
public:
/**
* @brief Constructor
* @param min Minimal value
* @param max Maximal value
* @param parent Parent widget
*/
IntervallSelector(double min, double max, QWidget *parent = nullptr)
: QWidget{ parent }, sigSettingsChanged_{}, min_{ nullptr },
max_{ nullptr }, complement_{ nullptr }
{
auto minb = util::make_unique<QDoubleSpinBox>();
min_ = *minb;
auto maxb = util::make_unique<QDoubleSpinBox>();
max_ = *maxb;
auto complement =
util::make_unique<QCheckBox>("Select the complement");
complement_ = *complement;
// set ranges
minb->setRange(min, max);
maxb->setValue(min);
maxb->setRange(min, max);
maxb->setValue(max);
// connect
QObject::connect(min_.getPtr(), SIGNAL(valueChanged(double)),
&sigSettingsChanged_, SIGNAL(signal()));
QObject::connect(max_.getPtr(), SIGNAL(valueChanged(double)),
&sigSettingsChanged_, SIGNAL(signal()));
QObject::connect(complement_.getPtr(), SIGNAL(clicked()),
&sigSettingsChanged_, SIGNAL(signal()));
// build ui
auto lay = util::make_unique<QVBoxLayout>();
lay->setContentsMargins(0, 0, 0, 0);
lay->addWidget(
util::make_unique<QLabel>(QString{"From lower bound ("}+
QString::number(min)+QString{"):"}).release());
lay->addWidget(minb.release());
lay->addWidget(
util::make_unique<QLabel>(QString{"To upper bound ("}+
QString::number(max)+QString{"):"}).release());
lay->addWidget(maxb.release());
lay->addWidget(complement.release());
setLayout(lay.release());
}
/**
* @brief Returns elements from the selected range.
* @param selection The selection.
* @param extr Extractor functor (has to be double extr(Type))
* @return the selected values
*/
template <class Type, class DoubleExtractor>
std::vector<Type> select(std::vector<Type> selection,
DoubleExtractor extr) const
{
std::vector<Type> result;
bool complement = complement_->isChecked();
std::copy_if(selection.begin(), selection.end(),
std::back_insert_iterator<std::vector<Type>>(
result),
[=](Type t1)
{
return complement !=
// check weather the element is in the interval
(((min_->value()) <= extr(t1)) &&
(extr(t1) <= max_->value()));
});
return result;
}
/**
* @brief Returns the signal emitted when settings are changed.
* @return The signal emitted when settings are changed.
*/
const Signal &signalSettingsChanged() const
{
return sigSettingsChanged_;
}
private:
/**
* @brief Emitted when settings are changed.
*/
const Signal sigSettingsChanged_;
/**
* @brief Spinbox to select the lower bound
*/
util::ObserverPtr<QDoubleSpinBox> min_;
/**
* @brief Spinbox to select the upper bound
*/
util::ObserverPtr<QDoubleSpinBox> max_;
/**
* @brief Weather the complement should be selected
*/
util::ObserverPtr<QCheckBox> complement_;
};
}
}
#endif // CVVISUAL_INTERVALLSELECTOR_HPP
@@ -0,0 +1,81 @@
#ifndef CVVISUAL_COLOR_UTIL
#define CVVISUAL_COLOR_UTIL
#include <cstdint>
#include <vector>
#include <QColor>
#include "opencv2/core.hpp"
namespace cvv
{
namespace qtutil
{
/**
* @brief Returns the false color. (BGR)
* @param value The double to convert.
* @return The false color.
*/
cv::Vec<uint8_t,3> inline falseColor(double d)
{
static const std::vector<cv::Vec<uint8_t,3>> points{
{176,0,13}, // 0.0
{163,0,31}, // 0.1
{131,0,75}, // 0.2
{86,0,137}, // 0.3
{36,0,205}, // 0.4
{0,0,255}, // 0.5
{0,49,255}, // 0.6
{0,119,255}, // 0.7
{0,183,255}, // 0.8
{0,229,255}, // 0.9
{0,251,255} // 1.0
};
if(d<0)
{
return points[0];
}
if(d>1)
{
return points[10];
}
int low = std::floor(d/0.1);
int high = std::ceil(d/0.1);
//interp. factor
double factorHi = d/0.1 - low;
double factorLo = 1 - factorHi;
return factorLo*(points[low]) + factorHi*(points[high]);
/*{cv::saturate_cast<uint8_t>( (factorLo*(points[low][0]) + factorHi*(points[high][0]))),
cv::saturate_cast<uint8_t>( (factorLo*(points[low][1]) + factorHi*(points[high][1]))),
cv::saturate_cast<uint8_t>( (factorLo*(points[low][2]) + factorHi*(points[high][2])))};*/
}
QColor inline getFalseColor(double value, double max, double min)
{
cv::Vec<uint8_t,3> color;
if(value<=min)
{
color=falseColor(0);
} else if(value>=max)
{
color=falseColor(1);
}else if(max<=min)
{
color=falseColor(0);
}else {
double val01 = (value-min) / (max - min);
color=falseColor(val01);
}
return QColor{color[2],color[1],color[0]};
}
}
}
#endif
@@ -0,0 +1,100 @@
#include "cvvkeypoint.hpp"
namespace cvv
{
namespace qtutil
{
CVVKeyPoint::CVVKeyPoint(const cv::KeyPoint &key, qtutil::ZoomableImage *image,
QPen pen, QBrush brush, QGraphicsItem *parent)
: QGraphicsObject{ parent }, cv::KeyPoint{ key }, image_{ image }, pen_{ pen },
brush_{ brush }, show_{ true }
{
//setFlag(QGraphicsItem::ItemIsSelectable, true);
//setSelected(true);
setToolTip(QString
{ "KeyPoint size: %1 \n angle %2 \n response %3 " }
.arg(size)
.arg(angle)
.arg(response));
if (image != nullptr)
{
updateImageSet(image->visibleArea(), image->zoom());
connect(image, SIGNAL(updateArea(QRectF, qreal)), this,
SLOT(updateImageSet(QRectF, qreal)));
}
}
void CVVKeyPoint::paint(QPainter *painter, const QStyleOptionGraphicsItem *,
QWidget *)
{
painter->setPen(pen_);
painter->setBrush(brush_);
painter->drawEllipse(boundingRect());
}
void CVVKeyPoint::setZoomableImage(ZoomableImage *image)
{
image_ = image;
updateImageSet(image->visibleArea(), image->zoom());
connect(image, SIGNAL(updateArea(QRectF, qreal)), this,
SLOT(updateImageSet(const QRectF &, const qreal &)));
}
bool CVVKeyPoint::operator==(const cv::KeyPoint &o)
{
return o.pt == pt && o.size == size &&
o.angle == angle && o.response == response &&
o.octave == octave && o.class_id == class_id;
}
void CVVKeyPoint::updateSettings(KeyPointSettings &settings)
{
settings.setSettings(*this);
}
void CVVKeyPoint::setPen(const QPen &pen)
{
pen_ = pen;
update();
}
void CVVKeyPoint::setBrush(const QBrush &brush)
{
brush_ = brush;
update();
}
void CVVKeyPoint::setShow(bool b)
{
show_ = b;
if(image_){
setVisible(show_&imagePointisVisible());
}
}
QRectF CVVKeyPoint::boundingRect() const
{
// TODO throw image==nullptr
return QRectF{
QPointF{ imPointInScene().x() - 3, imPointInScene().y() - 3 },
QPointF{ imPointInScene().x() + 3, imPointInScene().y() + 3 }
};
}
void CVVKeyPoint::updateImageSet(const QRectF &, const qreal &zoom)
{
imagePointInScene_=image_->mapImagePointToParent(
QPointF{ pt.x, pt.y });
bool isInVisibleArea=imagePointisVisible();
setVisible(show_ && isInVisibleArea);
emit updatePoint(isInVisibleArea);
zoom_ = zoom;
prepareGeometryChange();
// update();
}
}
}
@@ -0,0 +1,153 @@
#ifndef CVVISUAL_CVVKEYPOINT
#define CVVISUAL_CVVKEYPOINT
#include <QGraphicsObject>
#include <QPainter>
#include <QPointF>
#include <QRectF>
#include <QStyleOptionGraphicsItem>
#include <QWidget>
#include <QGraphicsScene>
#include "opencv2/core.hpp"
#include "opencv2/features.hpp"
#include "keypointsettings.hpp"
#include "../zoomableimage.hpp"
namespace cvv
{
namespace qtutil
{
class KeyPointSettings;
/**
* @brief this class represents a Keypoint which is displayed
* a Matchscene.
**/
class CVVKeyPoint : public QGraphicsObject,public cv::KeyPoint
{
Q_OBJECT
public:
/**
* @brief the construor
* @param key the keypoint with the image point
* @param image the zoomable image
*/
CVVKeyPoint(const cv::KeyPoint &key,
qtutil::ZoomableImage *image = nullptr,
QPen pen = QPen{ Qt::red },
QBrush brush = QBrush{ Qt::red },
QGraphicsItem *parent = nullptr);
/**
* @brief this method maps the imagepoint to the scene
* @return maps the imagepoint to the scene
*/
QPointF imPointInScene() const
{return imagePointInScene_;}
/**
* @brief boundingRect
* @return the boundingRect
*/
QRectF boundingRect() const CV_OVERRIDE;
/**
* @brief returns the keypoint
* @return the keypoint
*/
cv::KeyPoint keyPoint() const
{return *this;}
/**
* @brief the paint function.
*/
void paint(QPainter *painter, const QStyleOptionGraphicsItem *,
QWidget *) CV_OVERRIDE;
/**
* @brief returns true if this keypoint is in the visble area of its
* image
* @return true if this keypoint is in the visble area of its image
*/
bool imagePointisVisible()
{return image_->visibleArea().contains(pt.x, pt.y); }
/**
* @brief if show is true this keypoint will be visible if it is the
* visibleArea
* @return the show Value
*/
bool isShown() const
{return show_;}
bool operator==(const cv::KeyPoint &o);
QPen getPen() const
{return pen_;}
QBrush getBrush() const
{return brush_; }
signals:
/**
* @brief this signal will be emitted when the imagepoint in the scene
* has changed
* @param visible it is true if this keypoint is in the visibleArea
*/
void updatePoint(bool visible);
public
slots:
/**
* @brief updates the settings of this KeyPoint
* @param settings the object which has new settings for this keypoint
*/
void updateSettings(KeyPointSettings &settings);
void setPen(const QPen &pen);
/**
* @brief updates the brush of this KeyPoint
* @param brush a new brush
*/
void setBrush(const QBrush &brush);
/**
* @brief if show is true this keypoint will be visible if it is the
* visibleArea
* @param b the new show Value
*/
void setShow(bool b);
/**
* @brief updates the coordinates and visibleState of this KeyPoint
* @param visibleArea the visibleArea of the ZoomableImage
* @param zoom the zoomfactor
*/
void updateImageSet(const QRectF &, const qreal &zoom);
/**
* @brief this method sets and connects this keypoint which the given
* ZoomableImage.
* the ZoomableImage should be in a QGraphicScene and should have same
* parent
* @param image the image
*/
void setZoomableImage(ZoomableImage *image);
private:
qtutil::ZoomableImage *image_=nullptr;
QPen pen_;
QBrush brush_;
qreal zoom_;
bool show_;
QPointF imagePointInScene_;
};
}
}
#endif
@@ -0,0 +1,91 @@
#include <algorithm>
#include "cvvmatch.hpp"
namespace cvv
{
namespace qtutil
{
CVVMatch::CVVMatch(CVVKeyPoint *left_key, CVVKeyPoint *right_key,
const cv::DMatch &match, const QPen &pen,
QGraphicsItem *parent)
: QGraphicsObject{ parent },cv::DMatch{ match }, left_key_{ left_key }, right_key_{ right_key },
pen_{ pen }, show_{ true },
left_key_visible_{ left_key->imagePointisVisible() },
right_key_visible_{ right_key_->imagePointisVisible() }
{
//setFlag(QGraphicsItem::ItemIsSelectable);
setVisible(show_ && left_key_visible_ && right_key_visible_);
//setSelected(true);
setToolTip(QString
{ "Match distance: %1 \n queryIdx %2 \n trainIdx %3 \n imIdx %4 " }
.arg(distance)
.arg(queryIdx)
.arg(trainIdx)
.arg(imgIdx));
connect(left_key_, SIGNAL(updatePoint(bool)), this,
SLOT(updateLeftKey(bool)));
connect(right_key_, SIGNAL(updatePoint(bool)), this,
SLOT(updateRightKey(bool)));
}
QRectF CVVMatch::boundingRect() const
{
// TODO minmax
return QRectF{ QPointF{ std::min(leftImPointInScene().rx(),
rightImPointInScene().rx()),
std::min(leftImPointInScene().ry(),
rightImPointInScene().ry()) },
QPointF{ std::max(leftImPointInScene().rx(),
rightImPointInScene().rx()),
std::max(leftImPointInScene().ry(),
rightImPointInScene().ry()) } };
}
void CVVMatch::paint(QPainter *painter, const QStyleOptionGraphicsItem *,
QWidget *)
{
painter->setPen(pen_);
painter->drawLine(leftImPointInScene(), rightImPointInScene());
}
bool CVVMatch::operator==(const cv::DMatch &o)
{
return o.queryIdx == queryIdx && o.trainIdx == trainIdx &&
o.imgIdx == imgIdx;
}
void CVVMatch::setPen(const QPen &pen)
{
pen_ = pen;
update();
}
void CVVMatch::setShow(const bool &b)
{
show_ = b;
setVisible(show_ && left_key_visible_ && right_key_visible_);
}
void CVVMatch::updateLeftKey(bool visible)
{
left_key_visible_ = visible;
setVisible(show_ && left_key_visible_ && right_key_visible_);
prepareGeometryChange();
// update();
}
void CVVMatch::updateRightKey(bool visible)
{
right_key_visible_ = visible;
setVisible(show_ && left_key_visible_ && right_key_visible_);
prepareGeometryChange();
// update();
}
}
}
@@ -0,0 +1,180 @@
#ifndef CVVISUAL_CVVMATCH
#define CVVISUAL_CVVMATCH
#include <QGraphicsObject>
#include <QPainter>
#include <QPointF>
#include <QRectF>
#include <QStyleOptionGraphicsItem>
#include <QWidget>
#include "opencv2/core.hpp"
#include "opencv2/features.hpp"
#include "matchsettings.hpp"
#include "cvvkeypoint.hpp"
namespace cvv
{
namespace qtutil
{
class MatchSettings;
/**
* @brief this class represents a match which is displayed
* a Matchscene.
*/
class CVVMatch : public QGraphicsObject,public cv::DMatch
{
Q_OBJECT
public:
/**
* @brief the constructor
* @param left_key the left KeyPointPen
* @param right_key the right KeyPointPen
* @param match the match
* @param pen a QPen
* @param parent the parent Widget
*/
CVVMatch(CVVKeyPoint *left_key,
CVVKeyPoint *right_key,
const cv::DMatch &match,
const QPen &pen = QPen{ Qt::red },
QGraphicsItem *parent = nullptr);
/**
* @brief returns the boundingrect of this Mathc
* @return the boundingrect of this Mathc
*/
virtual QRectF boundingRect() const CV_OVERRIDE;
/**
* @brief the paint function
*/
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *,
QWidget *) CV_OVERRIDE;
/**
* @brief returns the left keypoint.
* @return the left keypoint.
*/
cv::KeyPoint leftKeyPoint() const
{
return left_key_->keyPoint();
}
/**
* @brief returns the right keypoint.
* @return the right keypoint.
*/
cv::KeyPoint rightKeyPoint() const
{
return right_key_->keyPoint();
}
/**
* @brief maps the leftImagePoint to scene
* @return the scene point of the leftkeypoint
*/
QPointF leftImPointInScene() const
{
return left_key_->imPointInScene();
}
/**
* @brief maps the leftImagePoint to scene
* @return the scene point of the rightkeypoint
*/
QPointF rightImPointInScene() const
{
return right_key_->imPointInScene();
}
/**
* @brief returns the match value
* @return the match value
*/
const cv::DMatch match() const
{
return *this;
}
/**
* @brief returns the show value
* @return the show value
*/
bool isShown() const
{
return show_;
}
/**
* @brief operator ==
* @param o a cv::DMatch
* @return true if this has the same match
*/
bool operator==(const cv::DMatch &o);
/**
* @brief get current pen
* @return current Pen
*/
QPen getPen() const
{
return pen_;
}
public
slots:
/**
* @brief the match will call setSettings from settings
* @param settings the settings for this match
*/
void updateSettings(MatchSettings &settings)
{
settings.setSettings(*this);
}
/**
* @brief this method updates the Pen
* @param pen the new Pen
*/
void setPen(const QPen &pen);
/**
* @brief if show=true the match will be visible if both keypoints are
* in the
* visibleArea of its images
* @param b new show value
*/
void setShow(const bool &b);
/**
* @brief this slot will be called if the right keypoint has changed
* @param visible if the rightKey in the visibleArea of its image
*/
virtual void updateRightKey(bool visible);
/**
* @brief this slot will be called if the left keypoint has changed
* @param visible if the leftKey in the visibleArea of its image
*/
virtual void updateLeftKey(bool visible);
protected:
CVVKeyPoint *left_key_;
CVVKeyPoint *right_key_;
//cv::DMatch match_;
QPen pen_;
bool show_;
bool left_key_visible_;
bool right_key_visible_;
};
}
}
#endif
@@ -0,0 +1,62 @@
#include <QBrush>
#include "cvvpointmatch.hpp"
namespace cvv
{
namespace qtutil
{
CVVPointMatch::CVVPointMatch(CVVKeyPoint *left_key, CVVKeyPoint *right_key,
const cv::DMatch &match, bool isLeftKey,
qreal radius, const QPen &pen, const QBrush &brush,
QGraphicsItem *parent)
: CVVMatch{ left_key, right_key, match, pen, parent },
isLeftKey_{ isLeftKey },
radius_{ std::min(radius * match.distance, 10.0) }, brush_{ brush }
{
if (isLeftKey_)
{
right_key_visible_ = true;
setVisible(left_key_visible_);
}
else
{
left_key_visible_ = true;
setVisible(right_key_visible_);
}
}
QRectF CVVPointMatch::boundingRect() const
{
QPointF point =
(isLeftKey_ ? leftImPointInScene() : rightImPointInScene());
return QRectF{ QPointF{ point.x() - radius_, point.y() - radius_ },
QPointF{ point.x() + radius_, point.y() + radius_ } };
}
void CVVPointMatch::paint(QPainter *painter, const QStyleOptionGraphicsItem *,
QWidget *)
{
painter->setPen(pen_);
painter->setBrush(brush_);
painter->drawEllipse(boundingRect());
}
void CVVPointMatch::updateRightKey(bool visible)
{
if (!isLeftKey_)
{
CVVMatch::updateRightKey(visible);
}
}
void CVVPointMatch::updateLeftKey(bool visible)
{
if (isLeftKey_)
{
CVVMatch::updateLeftKey(visible);
}
}
}
}
@@ -0,0 +1,74 @@
#ifndef CVVISUAL_CVV_POINT_MATCH
#define CVVISUAL_CVV_POINT_MATCH
#include <QBrush>
#include "cvvmatch.hpp"
namespace cvv
{
namespace qtutil
{
/**
* @brief This CVVMatch will be shown as circles with a given Color and radius
* this CVVMatches will be used in DepthView
*/
class CVVPointMatch : public CVVMatch
{
Q_OBJECT
public:
/**
* @brief the constructor
* @param left_key the left KeyPointPen
* @param right_key the right KeyPointPen
* @param matchValue the match distance
* @param isLeftKey if true the match is at Pos of the left key,
* otherwise it is at the
* pos of the right key
* @param radius the radius of the MatchPoint
* @param pen the pen
* @param brush the brush
* @param parent the parent Widget
*/
CVVPointMatch(CVVKeyPoint *left_key, CVVKeyPoint *right_key,
const cv::DMatch &match, bool isLeftKey = true,
qreal radius = 1, const QPen &pen = QPen{ Qt::red },
const QBrush &brush = QBrush{ Qt::red },
QGraphicsItem *parent = nullptr);
/**
* @brief returns the boundingrect of this Mathc
* @return the boundingrect of this Mathc
*/
virtual QRectF boundingRect() const override;
/**
* @brief the paint function
*/
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *,
QWidget *) override;
public
slots:
/**
* @brief this slot will be called if the right keypoint has changed
* @param visible if the rightKey in the visibleArea of its image
*/
virtual void updateRightKey(bool visible) override;
/**
* @brief this slot will be called if the left keypoint has changed
* @param visible if the leftKey in the visibleArea of its image
*/
virtual void updateLeftKey(bool visible) override;
protected:
bool isLeftKey_;
qreal radius_;
QBrush brush_;
};
}
}
#endif
@@ -0,0 +1,63 @@
#include <QPushButton>
#include <QDoubleSpinBox>
#include <QLabel>
#include <QVBoxLayout>
#include "falsecolorkeypointpen.hpp"
#include "colorutil.hpp"
#include "cvvmatch.hpp"
#include "../../util/util.hpp"
namespace cvv
{
namespace qtutil
{
FalseColorKeyPointPen::FalseColorKeyPointPen(std::vector<cv::KeyPoint> univers, QWidget *parent)
: KeyPointSettings{ parent },
univers_{univers},
maxDistance_{0.0},
minDistance_{0.0}
{
auto layout = util::make_unique<QVBoxLayout>();
auto valueChooser=util::make_unique<KeyPointValueChooser>();
auto button = util::make_unique<QPushButton>("use false color");
valueChooser_=valueChooser.get();
connect(valueChooser.get(),SIGNAL(valueChanged()),this,SLOT(updateMinMax()));
connect(button.get(), SIGNAL(clicked()), this, SLOT(updateAll()));
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(valueChooser.release());
layout->addWidget(button.release());
setLayout(layout.release());
updateMinMax();
}
void FalseColorKeyPointPen::setSettings(CVVKeyPoint &key)
{
QPen pen= key.getPen();
QBrush brush=key.getBrush();
pen.setColor(getFalseColor( valueChooser_->getChoosenValue(key.keyPoint()), maxDistance_, minDistance_) );
brush.setColor(getFalseColor( valueChooser_->getChoosenValue(key.keyPoint()), maxDistance_, minDistance_) );
key.setPen(pen);
key.setBrush(brush);
}
void FalseColorKeyPointPen::updateMinMax()
{
maxDistance_=0.0;
minDistance_=0.0;
for(auto& key:univers_){
maxDistance_=std::max(maxDistance_,valueChooser_->getChoosenValue(key));
//minDistance_=std::max(minDistance_,valueChooser_->getChoosenValue(key));
}
updateAll();
}
}
}
@@ -0,0 +1,48 @@
#ifndef CVVISUAL_FALSE_COLOR_KEY_POINT_PEN
#define CVVISUAL_FALSE_COLOR_KEY_POINT_PEN
#include <vector>
#include "opencv2/features.hpp"
#include "keypointvaluechooser.hpp"
#include "keypointsettings.hpp"
namespace cvv
{
namespace qtutil
{
/**
* @brief this pen gives the falsecolor of the distance value to the key point
*/
class FalseColorKeyPointPen : public KeyPointSettings
{
Q_OBJECT
public:
/**
* @brief the constructor
* @param univers all keypoints (for max value)
* @param parent the parent Widget
*/
FalseColorKeyPointPen(std::vector<cv::KeyPoint> univers, QWidget *parent = nullptr);
/**
* @brief set the falseColor to the given keypoint
*/
virtual void setSettings(CVVKeyPoint &key) override;
private slots:
void updateMinMax();
private:
KeyPointValueChooser* valueChooser_;
std::vector<cv::KeyPoint> univers_;
double maxDistance_;
double minDistance_=0.0;//always 0
};
}
}
#endif
@@ -0,0 +1,45 @@
#include <QPushButton>
#include <QDoubleSpinBox>
#include <QLabel>
#include <QVBoxLayout>
#include "falsecolormatchpen.hpp"
#include "colorutil.hpp"
#include "cvvmatch.hpp"
#include "../../util/util.hpp"
namespace cvv
{
namespace qtutil
{
FalseColorMatchPen::FalseColorMatchPen(std::vector<cv::DMatch> univers, QWidget *parent)
: MatchSettings{parent},
maxDistance_{0.0},
minDistance_{0.0}
{
auto layout = util::make_unique<QVBoxLayout>();
auto button = util::make_unique<QPushButton>("use false color");
for(auto& match:univers){
maxDistance_=std::max(maxDistance_,static_cast<double>(match.distance));
//min_=std::max(min_,static_cast<double>(match.distance));
}
connect(button.get(), SIGNAL(clicked()), this, SLOT(updateAll()));
layout->addWidget(button.release());
setLayout(layout.release());
}
void FalseColorMatchPen::setSettings(CVVMatch &match)
{
QPen pen= match.getPen();
pen.setColor(getFalseColor( static_cast<double>(match.match().distance), maxDistance_, minDistance_) );
match.setPen(pen);
}
}
}
@@ -0,0 +1,39 @@
#ifndef CVVISUAL_FALSE_COLOR_MATCH_PEN
#define CVVISUAL_FALSE_COLOR_MATCH_PEN
#include <vector>
#include "opencv2/features.hpp"
#include "matchsettings.hpp"
namespace cvv
{
namespace qtutil
{
/**
* @brief this pen gives the falsecolor of the distance value to the match
*/
class FalseColorMatchPen : public MatchSettings
{
public:
/**
* @brief the constructor
* @param univers all matches (for max value)
* @param parent the parent Widget
*/
FalseColorMatchPen(std::vector<cv::DMatch> univers, QWidget *parent = nullptr);
/**
* @brief set the falseColor of the distance to the given match
*/
virtual void setSettings(CVVMatch &match) override;
private:
double maxDistance_;
double minDistance_=0.0;//always 0
};
}
}
#endif
@@ -0,0 +1,64 @@
#include <QVBoxLayout>
#include <algorithm>
#include <iostream>
#include "keypointintervallselection.hpp"
#include "../../util/util.hpp"
namespace cvv{ namespace qtutil{
KeyPointIntervallSelector::KeyPointIntervallSelector(std::vector<cv::KeyPoint> keypoints, QWidget *parent):
KeyPointSelection{parent},
layout_{nullptr},
selector_{nullptr},
valueChooser_{nullptr},
keypoints_{keypoints}
{
auto layout=util::make_unique<QVBoxLayout>();
auto valueChooser=util::make_unique<KeyPointValueChooser>();
valueChooser_=valueChooser.get();
connect(valueChooser_,SIGNAL(valueChanged()),this,SLOT(changeSelecteValue()));
layout->setContentsMargins(0, 0, 0, 0);
layout_=layout.get();
layout->addWidget(valueChooser.release());
setLayout(layout.release());
changeSelecteValue();
}
std::vector<cv::KeyPoint> KeyPointIntervallSelector::select(const std::vector<cv::KeyPoint> &selection)
{
return selector_->select(selection, [&](const cv::KeyPoint& key)
{return this->valueChooser_->getChoosenValue(key);}
);
}
void KeyPointIntervallSelector::changeSelecteValue()
{
if(selector_){
layout_->removeWidget(selector_);
selector_->deleteLater();
}
double min=-1;
double max=0;
for(auto& key:keypoints_)
{
min=std::min(valueChooser_->getChoosenValue(key),min);
max=std::max(valueChooser_->getChoosenValue(key),max);
}
auto selector=util::make_unique<IntervallSelector>(min,max);
selector_=selector.get();
connect(&(selector->signalSettingsChanged()),SIGNAL(signal()),this,SIGNAL(settingsChanged()));
layout_->addWidget(selector.release());
}
}}
@@ -0,0 +1,47 @@
#ifndef CVVISUAL_KEY_POINT_INTERVALL_SELECTOR
#define CVVISUAL_KEY_POINT_INTERVALL_SELECTOR
#include "opencv2/features.hpp"
#include "keypointselection.hpp"
#include "keypointvaluechooser.hpp"
#include "../intervallselector.hpp"
namespace cvv
{
namespace qtutil
{
/**
* @brief this widget select an intervall of matches from the given selection.
* it use IntervallSelector
*/
class KeyPointIntervallSelector:public KeyPointSelection{
Q_OBJECT
public:
/**
* @brief the constructor
* @param matches all matches which can be selected
* @param parent the parent widget
*/
KeyPointIntervallSelector(std::vector<cv::KeyPoint> key,QWidget*parent=nullptr);
/**
* @brief select matches from the given selecton
* @param selection the current selection
* @return the selected matches
*/
virtual std::vector<cv::KeyPoint> select(const std::vector<cv::KeyPoint>& selection)override;
private slots:
void changeSelecteValue();
private:
QLayout* layout_;
IntervallSelector* selector_;
KeyPointValueChooser * valueChooser_;
std::vector<cv::KeyPoint> keypoints_;
};
}}
#endif
@@ -0,0 +1,221 @@
#include <algorithm>
#include <QVBoxLayout>
#include <QGridLayout>
#include <QPushButton>
#include <QLabel>
#include <QFrame>
#include "keypointmanagement.hpp"
namespace cvv
{
namespace qtutil
{
KeyPointManagement::KeyPointManagement(std::vector<cv::KeyPoint> univers,QWidget *parent) :
KeyPointSettings{parent},
univers_{univers},
selection_{univers_}
{
auto basicLayout=util::make_unique<QVBoxLayout>();
auto buttonLayout=util::make_unique<QGridLayout>();
auto settingsLayout=util::make_unique<QVBoxLayout>();
auto selectorLayout=util::make_unique<QVBoxLayout>();
auto buttonFrame=util::make_unique<QFrame>();
buttonFrame->setLineWidth(1);
buttonFrame->setFrameStyle(QFrame::Box);
auto labelSettings=util::make_unique<QLabel>("Settings");
auto labelSelection=util::make_unique<QLabel>("Selection");
auto buttonAddSetting=util::make_unique<QPushButton>("Add setting");
auto buttonAddSelection=util::make_unique<QPushButton>("Add selector");
//auto buttonApply=util::make_unique<QPushButton>("Apply settings");
auto showOnlySelection=util::make_unique<QCheckBox>("Show selection only");
auto buttonApplySelection=util::make_unique<QPushButton>("Apply Selection");
auto buttonSelectAll=util::make_unique<QPushButton>("Select all");
auto buttonSelectNone=util::make_unique<QPushButton>("Select none");
connect(buttonAddSetting.get(),SIGNAL(clicked()),this,SLOT(addSetting()));
connect(buttonAddSelection.get(),SIGNAL(clicked()),this,SLOT(addSelection()));
//connect(buttonApply.get(),SIGNAL(clicked()),this,SLOT(updateAll()));
connect(showOnlySelection.get(),SIGNAL(clicked()),this,SLOT(updateAll()));
connect(buttonApplySelection.get(),SIGNAL(clicked()),this,SLOT(applySelection()));
connect(buttonSelectAll.get(),SIGNAL(clicked()),this,SLOT(selectAll()));
connect(buttonSelectNone.get(),SIGNAL(clicked()),this,SLOT(selectNone()));
settingsLayout_=settingsLayout.get();
selectorLayout_=selectorLayout.get();
showOnlySelection_=showOnlySelection.get();
showOnlySelection->setChecked(true);
buttonLayout->addWidget(buttonAddSetting.release(),0,0);
buttonLayout->addWidget(buttonAddSelection.release(),0,1);
buttonLayout->addWidget(buttonApplySelection.release(),1,0);
//buttonLayout->addWidget(buttonApply.release(),1,1);
buttonLayout->addWidget(showOnlySelection.release(),1,1);
buttonLayout->addWidget(buttonSelectAll.release(),2,0);
buttonLayout->addWidget(buttonSelectNone.release(),2,1);
buttonFrame->setLayout(buttonLayout.release());
basicLayout->addWidget(buttonFrame.release());
basicLayout->addWidget(labelSettings.release());
basicLayout->addLayout(settingsLayout.release());
basicLayout->addWidget(labelSelection.release());
basicLayout->addLayout(selectorLayout.release());
basicLayout->setContentsMargins(0, 0, 0, 0);
setLayout(basicLayout.release());
addSelection();
addSetting();
}
void KeyPointManagement::setSettings(CVVKeyPoint &key)
{
if(showOnlySelection_->isChecked())
{
if (std::find_if(selection_.begin(), selection_.end(),
[&](const cv::KeyPoint &o)
{ return key == o; }) != selection_.end())
{
key.setShow(true);
}else{
key.setShow(false);
}
}/*else{
if (std::find_if(selection_.begin(), selection_.end(),
[&](const cv::KeyPoint &o)
{ return key == o; }) != selection_.end())
{
connect(this,SIGNAL(applySettingsToSelection(KeyPointSettings&)),
&key,SLOT(updateSettings(KeyPointSettings&)));
for(auto setting: settingsList_)
{
setting->setSettings(key);
}
}else{
disconnect(this,SIGNAL(applySettingsToSelection(KeyPointSettings&)),
&key,SLOT(updateSettings(KeyPointSettings&)));
for(auto setting: settingsList_)
{
setting->setUnSelectedSettings(key);
}
}
}*/
}
void KeyPointManagement::addToSelection(const cv::KeyPoint &key)
{
selection_.push_back(key);
emit updateSelection(selection_);
updateAll();
}
void KeyPointManagement::singleSelection(const cv::KeyPoint &key)
{
selection_.clear();
selection_.push_back(key);
emit updateSelection(selection_);
updateAll();
}
void KeyPointManagement::setSelection(
const std::vector<cv::KeyPoint> &selection)
{
selection_.clear();
for (auto &key : selection)
{
selection_.push_back(key);
}
emit updateSelection(selection_);
updateAll();
}
void KeyPointManagement::addSetting()
{
addSetting(util::make_unique<KeyPointSettingsSelector>(univers_));
}
void KeyPointManagement::addSetting(std::unique_ptr<KeyPointSettingsSelector> setting)
{
connect(setting.get(),SIGNAL(settingsChanged(KeyPointSettings &)),
this,SIGNAL(settingsChanged(KeyPointSettings&)));
connect(setting.get(),SIGNAL(remove(KeyPointSettingsSelector *)),
this,SLOT(removeSetting(KeyPointSettingsSelector*)));
settingsList_.push_back(setting.get());
setting->setLineWidth(1);
setting->setFrameStyle(QFrame::Box);
settingsLayout_->addWidget(setting.release());
}
void KeyPointManagement::removeSetting(KeyPointSettingsSelector *setting)
{
auto it = std::find(settingsList_.begin(), settingsList_.end(), setting);
if(it == settingsList_.end())
{
return;
}
settingsList_.erase(it);
settingsLayout_->removeWidget(setting);
setting->deleteLater();
}
void KeyPointManagement::addSelection()
{
addSelection(util::make_unique<KeyPointSelectionSelector>(univers_));
}
void KeyPointManagement::addSelection(std::unique_ptr<KeyPointSelectionSelector> selection)
{
connect(selection.get(),SIGNAL(remove(KeyPointSelectionSelector*))
,this,SLOT(removeSelection(KeyPointSelectionSelector*)));
connect(selection.get(),SIGNAL(settingsChanged()),this,SLOT(applySelection()));
selectorList_.push_back(selection.get());
selection->setLineWidth(1);
selection->setFrameStyle(QFrame::Box);
selectorLayout_->addWidget(selection.release());
}
void KeyPointManagement::removeSelection(KeyPointSelectionSelector *selector)
{
auto it = std::find(selectorList_.begin(), selectorList_.end(), selector);
if(it == selectorList_.end())
{
return;
}
selectorList_.erase(it);
selectorLayout_->removeWidget(selector);
selector->deleteLater();
}
void KeyPointManagement::applySelection()
{
std::vector<cv::KeyPoint> currentSelection=univers_;
for(auto& selector:selectorList_){
currentSelection=selector->select(currentSelection);
}
selection_=currentSelection;
emit updateSelection(selection_);
updateAll();
}
}
}
@@ -0,0 +1,138 @@
#ifndef CVVISUAL_KEYPOINT_MANAGEMENT
#define CVVISUAL_KEYPOINT_MANAGEMENT
#include <QCheckBox>
#include "../../util/util.hpp"
#include "opencv2/features.hpp"
#include "keypointselectionselector.hpp"
#include "keypointsettingsselector.hpp"
#include "keypointsettings.hpp"
#include "cvvkeypoint.hpp"
namespace cvv
{
namespace qtutil
{
/**
* @brief the keypointmanagement class coordinates the selections and use settings for the selection.
*/
class KeyPointManagement : public KeyPointSettings
{
Q_OBJECT
public:
/**
* @brief the constructor
* @param univers all keypoints which can be selected
* @param parent the parent widget
*/
KeyPointManagement(std::vector<cv::KeyPoint> univers,QWidget *parent = nullptr);
/**
* @brief set the settings if this KeyPoint is selected
*/
virtual void setSettings(CVVKeyPoint &match) CV_OVERRIDE;
/**
* @brief add the given KeyPointSettingsSelector to the list
*/
void addSetting(std::unique_ptr<KeyPointSettingsSelector>);
/**
* @brief add the given KeyPointSelectionSelector to the list
*/
void addSelection(std::unique_ptr<KeyPointSelectionSelector>);
/**
* @brief returns the current selection.
*/
std::vector<cv::KeyPoint> getCurrentSelection()
{return selection_;}
public slots:
//selection
/**
* @brief add the given keypoint to the current selection.
*/
void addToSelection(const cv::KeyPoint &key);
/**
* @brief set the selection to the given single match
*/
void singleSelection(const cv::KeyPoint &key);
/**
* @brief set the current selection to the given selection
*/
void setSelection(const std::vector<cv::KeyPoint> &selection);
//KeyPointSettingSelector
/**
* @brief add a new Setting
*/
void addSetting();
void removeSetting(KeyPointSettingsSelector *setting);
//Match Selection
/**
* @brief add a KeyPointSelectionSelector to the list
*/
void addSelection();
/**
* @brief remove a given KeyPointSelector from the list
*/
void removeSelection(KeyPointSelectionSelector *selector);
/**
* @brief select with the selections
*/
void applySelection();
/**
* @brief set Selection to univers.
*/
void selectAll()
{setSelection(univers_);}
/**
* @brief set selection to an empty list.
*/
void selectNone()
{setSelection(std::vector<cv::KeyPoint>{});}
signals:
/**
* @brief this signal will be emitted when the selection was changed.
* it can be used for syncronisation with other selector
*/
void updateSelection(const std::vector<cv::KeyPoint> &selection);
/**
* @brief this singal has the same function like settingsChanged from KeyPointSettings,
* but this will be only connect to the current selection
*/
void applySettingsToSelection(KeyPointSettings&);
private:
std::vector<cv::KeyPoint> univers_;
std::vector<cv::KeyPoint> selection_;
std::vector<KeyPointSettingsSelector*> settingsList_;
std::vector<KeyPointSelectionSelector*> selectorList_;
QLayout *settingsLayout_;
QLayout *selectorLayout_;
QCheckBox *showOnlySelection_;
};
}}
#endif
@@ -0,0 +1,32 @@
#include <QVBoxLayout>
#include "keypointportionselector.hpp"
namespace cvv {namespace qtutil{
KeyPointPortionSelection::KeyPointPortionSelection(std::vector<cv::KeyPoint> , QWidget *parent):
KeyPointSelection{parent}
{
auto layout=util::make_unique<QVBoxLayout>();
auto valueChooser=util::make_unique<KeyPointValueChooser>();
auto selector=util::make_unique<PortionSelector>();
selector_=selector.get();
valueChooser_=valueChooser.get();
connect(&(selector->signalSettingsChanged()),SIGNAL(signal()),this,SIGNAL(settingsChanged()));
layout->addWidget(valueChooser.release());
layout->addWidget(selector.release());
setLayout(layout.release());
}
std::vector<cv::KeyPoint> KeyPointPortionSelection::select(const std::vector<cv::KeyPoint> &selection)
{
return selector_->select( selection ,
[&](cv::KeyPoint arg1,cv::KeyPoint arg2)
{return valueChooser_->getChoosenValue(arg1)<valueChooser_->getChoosenValue(arg2);});
}
}}
@@ -0,0 +1,28 @@
#ifndef CVVISUAL_KEY_POINT_PORTION_SELECTOR
#define CVVISUAL_KEY_POINT_PORTION_SELECTOR
#include <vector>
#include "opencv2/features.hpp"
#include "keypointselection.hpp"
#include "keypointvaluechooser.hpp"
#include "../portionselector.hpp"
namespace cvv {namespace qtutil{
class KeyPointPortionSelection:public KeyPointSelection{
public:
KeyPointPortionSelection(std::vector<cv::KeyPoint>, QWidget * parent=nullptr);
virtual std::vector<cv::KeyPoint> select(const std::vector<cv::KeyPoint>& selection)override;
private:
PortionSelector* selector_;
KeyPointValueChooser * valueChooser_;
};
}}
#endif

Some files were not shown because too many files have changed in this diff Show More