Merge branch 'master' of github.com:Itseez/opencv_contrib

pull/108/head
vludv 11 years ago
commit a31a70bf47
  1. 10
      .gitignore
  2. BIN
      doc/tutorials/cvv/table_of_content_cvv/images/Visual_Debugging_Introduction_Tutorial_Cover.jpg
  3. 37
      doc/tutorials/cvv/table_of_content_cvv/table_of_content_cvv.rst
  4. BIN
      doc/tutorials/cvv/visual_debugging_introduction/images/01_overview_single.jpg
  5. BIN
      doc/tutorials/cvv/visual_debugging_introduction/images/02_single_image_view.jpg
  6. BIN
      doc/tutorials/cvv/visual_debugging_introduction/images/03_overview_two.jpg
  7. BIN
      doc/tutorials/cvv/visual_debugging_introduction/images/04_default_filter_view.jpg
  8. BIN
      doc/tutorials/cvv/visual_debugging_introduction/images/05_default_filter_view_high_zoom.jpg
  9. BIN
      doc/tutorials/cvv/visual_debugging_introduction/images/06_default_filter_view_edges.jpg
  10. BIN
      doc/tutorials/cvv/visual_debugging_introduction/images/07_dual_filter_view_edges.jpg
  11. BIN
      doc/tutorials/cvv/visual_debugging_introduction/images/08_overview_all.jpg
  12. BIN
      doc/tutorials/cvv/visual_debugging_introduction/images/09_overview_filtered_type_match.jpg
  13. BIN
      doc/tutorials/cvv/visual_debugging_introduction/images/10_line_match_view-cutout-small.jpg
  14. BIN
      doc/tutorials/cvv/visual_debugging_introduction/images/10_line_match_view-cutout.jpg
  15. BIN
      doc/tutorials/cvv/visual_debugging_introduction/images/10_line_match_view.jpg
  16. BIN
      doc/tutorials/cvv/visual_debugging_introduction/images/11_line_match_view_portion_selector.jpg
  17. BIN
      doc/tutorials/cvv/visual_debugging_introduction/images/12_translation_match_view_portion_selector.jpg
  18. BIN
      doc/tutorials/cvv/visual_debugging_introduction/images/13_raw_view.jpg
  19. BIN
      doc/tutorials/cvv/visual_debugging_introduction/images/14_overview_group_by_line.jpg
  20. 372
      doc/tutorials/cvv/visual_debugging_introduction/visual_debugging_introduction.rst
  21. 74
      modules/bioinspired/include/opencv2/bioinspired/retina.hpp
  22. 2
      modules/ccalib/CMakeLists.txt
  23. 202
      modules/ccalib/doc/customPattern.rst
  24. 149
      modules/ccalib/include/opencv2/ccalib.hpp
  25. 494
      modules/ccalib/src/ccalib.cpp
  26. 51
      modules/ccalib/src/precomp.hpp
  27. 46
      modules/cvv/.clang-format
  28. 10
      modules/cvv/.gitignore
  29. 20
      modules/cvv/CMakeLists.txt
  30. 37
      modules/cvv/LICENSE
  31. 2
      modules/cvv/README.md
  32. 11
      modules/cvv/doc/cvv.rst
  33. 85
      modules/cvv/doc/cvv_api/index.rst
  34. 24
      modules/cvv/doc/cvv_gui/index.rst
  35. 68
      modules/cvv/include/opencv2/cvv/call_meta_data.hpp
  36. 6
      modules/cvv/include/opencv2/cvv/cvv.hpp
  37. 45
      modules/cvv/include/opencv2/cvv/debug_mode.hpp
  38. 80
      modules/cvv/include/opencv2/cvv/dmatch.hpp
  39. 69
      modules/cvv/include/opencv2/cvv/filter.hpp
  40. 53
      modules/cvv/include/opencv2/cvv/final_show.hpp
  41. 62
      modules/cvv/include/opencv2/cvv/show_image.hpp
  42. 138
      modules/cvv/samples/cvv_demo.cpp
  43. 384
      modules/cvv/src/controller/view_controller.cpp
  44. 309
      modules/cvv/src/controller/view_controller.hpp
  45. 16
      modules/cvv/src/extension_api/api.cpp
  46. 63
      modules/cvv/src/extension_api/api.hpp
  47. 58
      modules/cvv/src/gui/call_tab.hpp
  48. 285
      modules/cvv/src/gui/call_window.cpp
  49. 165
      modules/cvv/src/gui/call_window.hpp
  50. 80
      modules/cvv/src/gui/filter_call_tab.hpp
  51. 75
      modules/cvv/src/gui/image_call_tab.cpp
  52. 93
      modules/cvv/src/gui/image_call_tab.hpp
  53. 38
      modules/cvv/src/gui/main_call_window.cpp
  54. 66
      modules/cvv/src/gui/main_call_window.hpp
  55. 107
      modules/cvv/src/gui/match_call_tab.hpp
  56. 260
      modules/cvv/src/gui/multiview_call_tab.hpp
  57. 318
      modules/cvv/src/gui/overview_group_subtable.cpp
  58. 106
      modules/cvv/src/gui/overview_group_subtable.hpp
  59. 214
      modules/cvv/src/gui/overview_panel.cpp
  60. 113
      modules/cvv/src/gui/overview_panel.hpp
  61. 125
      modules/cvv/src/gui/overview_table.cpp
  62. 90
      modules/cvv/src/gui/overview_table.hpp
  63. 104
      modules/cvv/src/gui/overview_table_row.cpp
  64. 143
      modules/cvv/src/gui/overview_table_row.hpp
  65. 301
      modules/cvv/src/gui/rawview_group_subtable.cpp
  66. 83
      modules/cvv/src/gui/rawview_group_subtable.hpp
  67. 99
      modules/cvv/src/gui/rawview_table.cpp
  68. 83
      modules/cvv/src/gui/rawview_table.hpp
  69. 323
      modules/cvv/src/gui/rawview_table_row.cpp
  70. 245
      modules/cvv/src/gui/rawview_table_row.hpp
  71. 42
      modules/cvv/src/gui/tabwidget.hpp
  72. 27
      modules/cvv/src/impl/call.cpp
  73. 116
      modules/cvv/src/impl/call.hpp
  74. 116
      modules/cvv/src/impl/data_controller.cpp
  75. 94
      modules/cvv/src/impl/data_controller.hpp
  76. 22
      modules/cvv/src/impl/dmatch.cpp
  77. 18
      modules/cvv/src/impl/filter.cpp
  78. 45
      modules/cvv/src/impl/filter_call.cpp
  79. 66
      modules/cvv/src/impl/filter_call.hpp
  80. 20
      modules/cvv/src/impl/final_show.cpp
  81. 108
      modules/cvv/src/impl/init.cpp
  82. 14
      modules/cvv/src/impl/init.hpp
  83. 57
      modules/cvv/src/impl/match_call.cpp
  84. 104
      modules/cvv/src/impl/match_call.hpp
  85. 17
      modules/cvv/src/impl/show_image.cpp
  86. 42
      modules/cvv/src/impl/single_image_call.cpp
  87. 55
      modules/cvv/src/impl/single_image_call.hpp
  88. 110
      modules/cvv/src/qtutil/accordion.cpp
  89. 292
      modules/cvv/src/qtutil/accordion.hpp
  90. 475
      modules/cvv/src/qtutil/autofilterwidget.hpp
  91. 70
      modules/cvv/src/qtutil/collapsable.cpp
  92. 139
      modules/cvv/src/qtutil/collapsable.hpp
  93. 160
      modules/cvv/src/qtutil/filter/changed_pixels_widget.cpp
  94. 50
      modules/cvv/src/qtutil/filter/changed_pixels_widget.hpp
  95. 105
      modules/cvv/src/qtutil/filter/channelreorderfilter.cpp
  96. 100
      modules/cvv/src/qtutil/filter/channelreorderfilter.hpp
  97. 132
      modules/cvv/src/qtutil/filter/diffFilterWidget.cpp
  98. 88
      modules/cvv/src/qtutil/filter/diffFilterWidget.hpp
  99. 156
      modules/cvv/src/qtutil/filter/grayfilterwidget.cpp
  100. 105
      modules/cvv/src/qtutil/filter/grayfilterwidget.hpp
  101. Some files were not shown because too many files have changed in this diff Show More

10
.gitignore vendored

@ -0,0 +1,10 @@
*.autosave
*.pyc
*.user
*~
.*.swp
.DS_Store
.sw[a-z]
Thumbs.db
tags
tegra/

@ -0,0 +1,37 @@
.. _Table-Of-Content-CVV:
*cvv* module. GUI for Interactive Visual Debugging
--------------------------------------------------
Here you will learn how to use the cvv module to ease programming computer vision software through visual debugging aids.
.. include:: ../../definitions/tocDefinitions.rst
+
.. tabularcolumns:: m{100pt} m{300pt}
.. cssclass:: toctableopencv
=============== ======================================================
|cvvIntro| *Title:* :ref:`Visual_Debugging_Introduction`
*Compatibility:* > OpenCV 2.4.8
*Author:* |Author_Bihlmaier|
We will learn how to debug our applications in a visual and interactive way.
=============== ======================================================
.. |cvvIntro| image:: images/Visual_Debugging_Introduction_Tutorial_Cover.jpg
:height: 90pt
:width: 90pt
.. raw:: latex
\pagebreak
.. toctree::
:hidden:
../visual_debugging_introduction/visual_debugging_introduction

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

@ -0,0 +1,372 @@
.. _Visual_Debugging_Introduction:
Interactive Visual Debugging of Computer Vision applications
************************************************************
What is the most common way to debug computer vision applications?
Usually the answer is temporary, hacked together, custom code that must be removed from the code for release compilation.
In this tutorial we will show how to use the visual debugging features of the **cvv** module (*opencv2/cvv/cvv.hpp*) instead.
Goals
======
In this tutorial you will learn how to:
* Add cvv debug calls to your application
* Use the visual debug GUI
* Enable and disable the visual debug features during compilation (with zero runtime overhead when disabled)
Code
=====
The example code
* captures images (*highgui*), e.g. from a webcam,
* applies some filters to each image (*imgproc*),
* detects image features and matches them to the previous image (*features2d*).
If the program is compiled without visual debugging (see CMakeLists.txt below) the only result is some information printed to the command line.
We want to demonstrate how much debugging or development functionality is added by just a few lines of *cvv* commands.
.. code-block:: cpp
// system includes
#include <getopt.h>
#include <iostream>
// library includes
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/features2d/features2d.hpp>
// Visual debugging
#include <opencv2/cvv/cvv.hpp>
// helper function to convert objects that support operator<<() to std::string
template<class T> std::string toString(const T& p_arg)
{
std::stringstream ss;
ss << p_arg;
return ss.str();
}
void
usage()
{
printf("usage: cvvt [-r WxH]\n");
printf("-h print this help\n");
printf("-r WxH change resolution to width W and height H\n");
}
int
main(int argc, char** argv)
{
#ifdef CVVISUAL_DEBUGMODE
std::cout << "Visual debugging is ENABLED" << std::endl;
#else
std::cout << "Visual debugging is DISABLED" << std::endl;
#endif
cv::Size* resolution = nullptr;
// parse options
const char* optstring = "hr:";
int opt;
while ((opt = getopt(argc, argv, optstring)) != -1) {
switch (opt) {
case 'h':
usage();
return 0;
break;
case 'r':
{
char dummych;
resolution = new cv::Size();
if (sscanf(optarg, "%d%c%d", &resolution->width, &dummych, &resolution->height) != 3) {
printf("%s not a valid resolution\n", optarg);
return 1;
}
}
break;
default:
usage();
return 2;
}
}
// setup video capture
cv::VideoCapture capture(0);
if (!capture.isOpened()) {
std::cout << "Could not open VideoCapture" << std::endl;
return 3;
}
if (resolution) {
printf("Setting resolution to %dx%d\n", resolution->width, resolution->height);
capture.set(CV_CAP_PROP_FRAME_WIDTH, resolution->width);
capture.set(CV_CAP_PROP_FRAME_HEIGHT, resolution->height);
}
cv::Mat prevImgGray;
std::vector<cv::KeyPoint> prevKeypoints;
cv::Mat prevDescriptors;
int maxFeatureCount = 500;
cv::ORB detector(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, CV_BGR2GRAY);
cvv::debugFilter(imgRead, imgGray, CVVISUAL_LOCATION, "to gray");
// filter edges using Canny on smoothed image
cv::Mat imgGraySmooth;
cv::GaussianBlur(imgGray, imgGraySmooth, cv::Size(9, 9), 2, 2);
cvv::debugFilter(imgGray, imgGraySmooth, CVVISUAL_LOCATION, "smoothed");
cv::Mat imgEdges;
cv::Canny(imgGraySmooth, imgEdges, 50, 150);
cvv::showImage(imgEdges, CVVISUAL_LOCATION, "edges");
// dilate edges
cv::Mat imgEdgesDilated;
cv::dilate(imgEdges, imgEdgesDilated, cv::getStructuringElement(cv::MORPH_RECT, cv::Size(7, 7), cv::Point(3, 3)));
cvv::debugFilter(imgEdges, imgEdgesDilated, CVVISUAL_LOCATION, "dilated edges");
// detect ORB features
std::vector<cv::KeyPoint> keypoints;
cv::Mat descriptors;
detector(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;
}
.. code-block:: cmake
cmake_minimum_required(VERSION 2.8)
project(cvvisual_test)
SET(CMAKE_PREFIX_PATH ~/software/opencv/install)
SET(CMAKE_CXX_COMPILER "g++-4.8")
SET(CMAKE_CXX_FLAGS "-std=c++11 -O2 -pthread -Wall -Werror")
# (un)set: cmake -DCVV_DEBUG_MODE=OFF ..
OPTION(CVV_DEBUG_MODE "cvvisual-debug-mode" ON)
if(CVV_DEBUG_MODE MATCHES ON)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DCVVISUAL_DEBUGMODE")
endif()
FIND_PACKAGE(OpenCV REQUIRED)
include_directories(${OpenCV_INCLUDE_DIRS})
add_executable(cvvt main.cpp)
target_link_libraries(cvvt
opencv_core opencv_highgui opencv_imgproc opencv_features2d
opencv_cvv
)
Explanation
============
#. We compile the program either using the above CmakeLists.txt with Option *CVV_DEBUG_MODE=ON* (*cmake -DCVV_DEBUG_MODE=ON*) or by adding the corresponding define *CVVISUAL_DEBUGMODE* to our compiler (e.g. *g++ -DCVVISUAL_DEBUGMODE*).
#. The first cvv call simply shows the image (similar to *imshow*) with the imgIdString as comment.
.. code-block:: cpp
cvv::showImage(imgRead, CVVISUAL_LOCATION, imgIdString.c_str());
The image is added to the overview tab in the visual debug GUI and the cvv call blocks.
.. image:: images/01_overview_single.jpg
:alt: Overview with image of first cvv call
:align: center
The image can then be selected and viewed
.. image:: images/02_single_image_view.jpg
:alt: Display image added through cvv::showImage
:align: center
Whenever you want to continue in the code, i.e. unblock the cvv call, you can
either continue until the next cvv call (*Step*), continue until the last cvv
call (*>>*) or run the application until it exists (*Close*).
We decide to press the green *Step* button.
#. The next cvv calls are used to debug all kinds of filter operations, i.e. operations that take a picture as input and return a picture as output.
.. code-block:: cpp
cvv::debugFilter(imgRead, imgGray, CVVISUAL_LOCATION, "to gray");
As with every cvv call, you first end up in the overview.
.. image:: images/03_overview_two.jpg
:alt: Overview with two cvv calls after pressing Step
:align: center
We decide not to care about the conversion to gray scale and press *Step*.
.. code-block:: cpp
cvv::debugFilter(imgGray, imgGraySmooth, CVVISUAL_LOCATION, "smoothed");
If you open the filter call, you will end up in the so called "DefaultFilterView".
Both images are shown next to each other and you can (synchronized) zoom into them.
.. image:: images/04_default_filter_view.jpg
:alt: Default filter view displaying a gray scale image and its corresponding GaussianBlur filtered one
:align: center
When you go to very high zoom levels, each pixel is annotated with its numeric values.
.. image:: images/05_default_filter_view_high_zoom.jpg
:alt: Default filter view at very high zoom levels
:align: center
We press *Step* twice and have a look at the dilated image.
.. code-block:: cpp
cvv::debugFilter(imgEdges, imgEdgesDilated, CVVISUAL_LOCATION, "dilated edges");
The DefaultFilterView showing both images
.. image:: images/06_default_filter_view_edges.jpg
:alt: Default filter view showing an edge image and the image after dilate()
:align: center
Now we use the *View* selector in the top right and select the "DualFilterView".
We select "Changed Pixels" as filter and apply it (middle image).
.. image:: images/07_dual_filter_view_edges.jpg
:alt: Dual filter view showing an edge image and the image after dilate()
:align: center
After we had a close look at these images, perhaps using different views, filters or other GUI features, we decide to let the program run through. Therefore we press the yellow *>>* button.
The program will block at
.. code-block:: cpp
cvv::finalShow();
and display the overview with everything that was passed to cvv in the meantime.
.. image:: images/08_overview_all.jpg
:alt: Overview displaying all cvv calls up to finalShow()
:align: center
#. The cvv debugDMatch call is used in a situation where there are two images each with a set of descriptors that are matched to each other.
We pass both images, both sets of keypoints and their matching to the visual debug module.
.. code-block:: cpp
cvv::debugDMatch(prevImgGray, prevKeypoints, imgGray, keypoints, matches, CVVISUAL_LOCATION, allMatchIdString.c_str());
Since we want to have a look at matches, we use the filter capabilities (*#type match*) in the overview to only show match calls.
.. image:: images/09_overview_filtered_type_match.jpg
:alt: Overview displaying only match calls
:align: center
We want to have a closer look at one of them, e.g. to tune our parameters that use the matching.
The view has various settings how to display keypoints and matches.
Furthermore, there is a mouseover tooltip.
.. image:: images/10_line_match_view.jpg
:alt: Line match view
:align: center
We see (visual debugging!) that there are many bad matches.
We decide that only 70% of the matches should be shown - those 70% with the lowest match distance.
.. image:: images/11_line_match_view_portion_selector.jpg
:alt: Line match view showing the best 70% matches, i.e. lowest match distance
:align: center
Having successfully reduced the visual distraction, we want to see more clearly what changed between the two images.
We select the "TranslationMatchView" that shows to where the keypoint was matched in a different way.
.. image:: images/12_translation_match_view_portion_selector.jpg
:alt: Translation match view
:align: center
It is easy to see that the cup was moved to the left during the two images.
Although, cvv is all about interactively *seeing* the computer vision bugs, this is complemented by a "RawView" that allows to have a look at the underlying numeric data.
.. image:: images/13_raw_view.jpg
:alt: Raw view of matches
:align: center
#. There are many more useful features contained in the cvv GUI. For instance, one can group the overview tab.
.. image:: images/14_overview_group_by_line.jpg
:alt: Overview grouped by call line
:align: center
Result
=======
* By adding a view expressive lines to our computer vision program we can interactively debug it through different visualizations.
* Once we are done developing/debugging we do not have to remove those lines. We simply disable cvv debugging (*cmake -DCVV_DEBUG_MODE=OFF* or g++ without *-DCVVISUAL_DEBUGMODE*) and our programs runs without any debug overhead.
Enjoy computer vision!

@ -107,13 +107,13 @@ enum {
* _take a look at imagelogpolprojection.hpp to discover retina spatial log sampling which originates from Barthelemy Durette phd with Jeanny Herault. A Retina / V1 cortex projection is also proposed and originates from Jeanny's discussions.
* ====> more informations in the above cited Jeanny Heraults's book.
*/
class CV_EXPORTS Retina : public Algorithm {
class CV_EXPORTS_W Retina : public Algorithm {
public:
// parameters structure for better clarity, check explenations on the comments of methods : setupOPLandIPLParvoChannel and setupIPLMagnoChannel
struct RetinaParameters{
struct OPLandIplParvoParameters{ // Outer Plexiform Layer (OPL) and Inner Plexiform Layer Parvocellular (IplParvo) parameters
struct CV_EXPORTS_W RetinaParameters{
struct CV_EXPORTS_W OPLandIplParvoParameters{ // Outer Plexiform Layer (OPL) and Inner Plexiform Layer Parvocellular (IplParvo) parameters
OPLandIplParvoParameters():colorMode(true),
normaliseOutput(true),
photoreceptorsLocalAdaptationSensitivity(0.75f),
@ -123,11 +123,11 @@ public:
hcellsTemporalConstant(0.5f),
hcellsSpatialConstant(7.f),
ganglionCellsSensitivity(0.75f) { } // default setup
bool colorMode, normaliseOutput;
float photoreceptorsLocalAdaptationSensitivity, photoreceptorsTemporalConstant, photoreceptorsSpatialConstant, horizontalCellsGain, hcellsTemporalConstant, hcellsSpatialConstant, ganglionCellsSensitivity;
};
struct IplMagnoParameters{ // Inner Plexiform Layer Magnocellular channel (IplMagno)
IplMagnoParameters():
CV_PROP_RW bool colorMode, normaliseOutput;
CV_PROP_RW float photoreceptorsLocalAdaptationSensitivity, photoreceptorsTemporalConstant, photoreceptorsSpatialConstant, horizontalCellsGain, hcellsTemporalConstant, hcellsSpatialConstant, ganglionCellsSensitivity;
};
struct CV_EXPORTS_W IplMagnoParameters{ // Inner Plexiform Layer Magnocellular channel (IplMagno)
IplMagnoParameters():
normaliseOutput(true),
parasolCells_beta(0.f),
parasolCells_tau(0.f),
@ -136,22 +136,22 @@ public:
V0CompressionParameter(0.95f),
localAdaptintegration_tau(0.f),
localAdaptintegration_k(7.f) { } // default setup
bool normaliseOutput;
float parasolCells_beta, parasolCells_tau, parasolCells_k, amacrinCellsTemporalCutFrequency, V0CompressionParameter, localAdaptintegration_tau, localAdaptintegration_k;
};
struct OPLandIplParvoParameters OPLandIplParvo;
struct IplMagnoParameters IplMagno;
CV_PROP_RW bool normaliseOutput;
CV_PROP_RW float parasolCells_beta, parasolCells_tau, parasolCells_k, amacrinCellsTemporalCutFrequency, V0CompressionParameter, localAdaptintegration_tau, localAdaptintegration_k;
};
CV_PROP_RW OPLandIplParvoParameters OPLandIplParvo;
CV_PROP_RW IplMagnoParameters IplMagno;
};
/**
* retreive retina input buffer size
*/
virtual Size getInputSize()=0;
CV_WRAP virtual Size getInputSize()=0;
/**
* retreive retina output buffer size
*/
virtual Size getOutputSize()=0;
CV_WRAP virtual Size getOutputSize()=0;
/**
* try to open an XML retina parameters file to adjust current retina instance setup
@ -160,7 +160,7 @@ public:
* @param retinaParameterFile : the parameters filename
* @param applyDefaultSetupOnFailure : set to true if an error must be thrown on error
*/
virtual void setup(String retinaParameterFile="", const bool applyDefaultSetupOnFailure=true)=0;
CV_WRAP virtual void setup(String retinaParameterFile="", const bool applyDefaultSetupOnFailure=true)=0;
/**
* try to open an XML retina parameters file to adjust current retina instance setup
@ -169,7 +169,7 @@ public:
* @param fs : the open Filestorage which contains retina parameters
* @param applyDefaultSetupOnFailure : set to true if an error must be thrown on error
*/
virtual void setup(cv::FileStorage &fs, const bool applyDefaultSetupOnFailure=true)=0;
CV_WRAP virtual void setup(cv::FileStorage &fs, const bool applyDefaultSetupOnFailure=true)=0;
/**
* try to open an XML retina parameters file to adjust current retina instance setup
@ -178,30 +178,30 @@ public:
* @param newParameters : a parameters structures updated with the new target configuration
* @param applyDefaultSetupOnFailure : set to true if an error must be thrown on error
*/
virtual void setup(RetinaParameters newParameters)=0;
CV_WRAP virtual void setup(RetinaParameters newParameters)=0;
/**
* @return the current parameters setup
*/
virtual struct Retina::RetinaParameters getParameters()=0;
CV_WRAP virtual RetinaParameters getParameters()=0;
/**
* parameters setup display method
* @return a string which contains formatted parameters information
*/
virtual const String printSetup()=0;
CV_WRAP virtual const String printSetup()=0;
/**
* write xml/yml formated parameters information
* @rparam fs : the filename of the xml file that will be open and writen with formatted parameters information
*/
virtual void write( String fs ) const=0;
CV_WRAP virtual void write( String fs ) const=0;
/**
* write xml/yml formated parameters information
* @param fs : a cv::Filestorage object ready to be filled
*/
virtual void write( FileStorage& fs ) const=0;
CV_WRAP virtual void write( FileStorage& fs ) const=0;
/**
* setup the OPL and IPL parvo channels (see biologocal model)
@ -218,7 +218,7 @@ public:
* @param HcellsSpatialConstant: the spatial constant of the first order low pass filter of the horizontal cells, use it to cut low spatial frequencies (local luminance), unit is pixels, typical value is 5 pixel, this value is also used for local contrast computing when computing the local contrast adaptation at the ganglion cells level (Inner Plexiform Layer parvocellular channel model)
* @param ganglionCellsSensitivity: the compression strengh of the ganglion cells local adaptation output, set a value between 160 and 250 for best results, a high value increases more the low value sensitivity... and the output saturates faster, recommended value: 230
*/
virtual void setupOPLandIPLParvoChannel(const bool colorMode=true, const bool normaliseOutput = true, const float photoreceptorsLocalAdaptationSensitivity=0.7, const float photoreceptorsTemporalConstant=0.5, const float photoreceptorsSpatialConstant=0.53, const float horizontalCellsGain=0, const float HcellsTemporalConstant=1, const float HcellsSpatialConstant=7, const float ganglionCellsSensitivity=0.7)=0;
CV_WRAP virtual void setupOPLandIPLParvoChannel(const bool colorMode=true, const bool normaliseOutput = true, const float photoreceptorsLocalAdaptationSensitivity=0.7, const float photoreceptorsTemporalConstant=0.5, const float photoreceptorsSpatialConstant=0.53, const float horizontalCellsGain=0, const float HcellsTemporalConstant=1, const float HcellsSpatialConstant=7, const float ganglionCellsSensitivity=0.7)=0;
/**
* set parameters values for the Inner Plexiform Layer (IPL) magnocellular channel
@ -232,13 +232,13 @@ public:
* @param localAdaptintegration_tau: specifies the temporal constant of the low pas filter involved in the computation of the local "motion mean" for the local adaptation computation
* @param localAdaptintegration_k: specifies the spatial constant of the low pas filter involved in the computation of the local "motion mean" for the local adaptation computation
*/
virtual void setupIPLMagnoChannel(const bool normaliseOutput = true, const float parasolCells_beta=0, const float parasolCells_tau=0, const float parasolCells_k=7, const float amacrinCellsTemporalCutFrequency=1.2, const float V0CompressionParameter=0.95, const float localAdaptintegration_tau=0, const float localAdaptintegration_k=7)=0;
CV_WRAP virtual void setupIPLMagnoChannel(const bool normaliseOutput = true, const float parasolCells_beta=0, const float parasolCells_tau=0, const float parasolCells_k=7, const float amacrinCellsTemporalCutFrequency=1.2, const float V0CompressionParameter=0.95, const float localAdaptintegration_tau=0, const float localAdaptintegration_k=7)=0;
/**
* method which allows retina to be applied on an input image, after run, encapsulated retina module is ready to deliver its outputs using dedicated acccessors, see getParvo and getMagno methods
* @param inputImage : the input cv::Mat image to be processed, can be gray level or BGR coded in any format (from 8bit to 16bits)
*/
virtual void run(InputArray inputImage)=0;
CV_WRAP virtual void run(InputArray inputImage)=0;
/**
* method that applies a luminance correction (initially High Dynamic Range (HDR) tone mapping) using only the 2 local adaptation stages of the retina parvo channel : photoreceptors level and ganlion cells level. Spatio temporal filtering is applied but limited to temporal smoothing and eventually high frequencies attenuation. This is a lighter method than the one available using the regular run method. It is then faster but it does not include complete temporal filtering nor retina spectral whitening. Then, it can have a more limited effect on images with a very high dynamic range. This is an adptation of the original still image HDR tone mapping algorithm of David Alleyson, Sabine Susstruck and Laurence Meylan's work, please cite:
@ -246,35 +246,35 @@ public:
@param inputImage the input image to process RGB or gray levels
@param outputToneMappedImage the output tone mapped image
*/
virtual void applyFastToneMapping(InputArray inputImage, OutputArray outputToneMappedImage)=0;
CV_WRAP virtual void applyFastToneMapping(InputArray inputImage, OutputArray outputToneMappedImage)=0;
/**
* accessor of the details channel of the retina (models foveal vision)
* @param retinaOutput_parvo : the output buffer (reallocated if necessary), this output is rescaled for standard 8bits image processing use in OpenCV
*/
virtual void getParvo(OutputArray retinaOutput_parvo)=0;
CV_WRAP virtual void getParvo(OutputArray retinaOutput_parvo)=0;
/**
* accessor of the details channel of the retina (models foveal vision)
* @param retinaOutput_parvo : a cv::Mat header filled with the internal parvo buffer of the retina module. This output is the original retina filter model output, without any quantification or rescaling
*/
virtual void getParvoRAW(OutputArray retinaOutput_parvo)=0;
CV_WRAP virtual void getParvoRAW(OutputArray retinaOutput_parvo)=0;
/**
* accessor of the motion channel of the retina (models peripheral vision)
* @param retinaOutput_magno : the output buffer (reallocated if necessary), this output is rescaled for standard 8bits image processing use in OpenCV
*/
virtual void getMagno(OutputArray retinaOutput_magno)=0;
CV_WRAP virtual void getMagno(OutputArray retinaOutput_magno)=0;
/**
* accessor of the motion channel of the retina (models peripheral vision)
* @param retinaOutput_magno : a cv::Mat header filled with the internal retina magno buffer of the retina module. This output is the original retina filter model output, without any quantification or rescaling
*/
virtual void getMagnoRAW(OutputArray retinaOutput_magno)=0;
CV_WRAP virtual void getMagnoRAW(OutputArray retinaOutput_magno)=0;
// original API level data accessors : get buffers addresses from a Mat header, similar to getParvoRAW and getMagnoRAW...
virtual const Mat getMagnoRAW() const=0;
virtual const Mat getParvoRAW() const=0;
CV_WRAP virtual const Mat getMagnoRAW() const=0;
CV_WRAP virtual const Mat getParvoRAW() const=0;
/**
* activate color saturation as the final step of the color demultiplexing process
@ -282,30 +282,32 @@ public:
* @param saturateColors: boolean that activates color saturation (if true) or desactivate (if false)
* @param colorSaturationValue: the saturation factor
*/
virtual void setColorSaturation(const bool saturateColors=true, const float colorSaturationValue=4.0)=0;
CV_WRAP virtual void setColorSaturation(const bool saturateColors=true, const float colorSaturationValue=4.0)=0;
/**
* clear all retina buffers (equivalent to opening the eyes after a long period of eye close ;o)
*/
virtual void clearBuffers()=0;
CV_WRAP virtual void clearBuffers()=0;
/**
* Activate/desactivate the Magnocellular pathway processing (motion information extraction), by default, it is activated
* @param activate: true if Magnocellular output should be activated, false if not
*/
virtual void activateMovingContoursProcessing(const bool activate)=0;
CV_WRAP virtual void activateMovingContoursProcessing(const bool activate)=0;
/**
* Activate/desactivate the Parvocellular pathway processing (contours information extraction), by default, it is activated
* @param activate: true if Parvocellular (contours information extraction) output should be activated, false if not
*/
virtual void activateContoursProcessing(const bool activate)=0;
CV_WRAP virtual void activateContoursProcessing(const bool activate)=0;
};
CV_EXPORTS Ptr<Retina> createRetina(Size inputSize);
CV_EXPORTS Ptr<Retina> createRetina(Size inputSize, const bool colorMode, int colorSamplingMethod=RETINA_COLOR_BAYER, const bool useRetinaLogSampling=false, const double reductionFactor=1.0, const double samplingStrenght=10.0);
#ifdef HAVE_OPENCV_OCL
CV_EXPORTS Ptr<Retina> createRetina_OCL(Size inputSize);
CV_EXPORTS Ptr<Retina> createRetina_OCL(Size inputSize, const bool colorMode, int colorSamplingMethod=RETINA_COLOR_BAYER, const bool useRetinaLogSampling=false, const double reductionFactor=1.0, const double samplingStrenght=10.0);
#endif
}
}
#endif /* __OPENCV_BIOINSPIRED_RETINA_HPP__ */

@ -0,0 +1,2 @@
set(the_description "Custom Calibration Pattern")
ocv_define_module(ccalib opencv_core opencv_imgproc opencv_calib3d opencv_features2d)

@ -0,0 +1,202 @@
Custom Calibration Pattern
==========================
.. highlight:: cpp
CustomPattern
-------------
A custom pattern class that can be used to calibrate a camera and to further track the translation and rotation of the pattern. Defaultly it uses an ``ORB`` feature detector and a ``BruteForce-Hamming(2)`` descriptor matcher to find the location of the pattern feature points that will subsequently be used for calibration.
.. ocv:class:: CustomPattern : public Algorithm
CustomPattern::CustomPattern
----------------------------
CustomPattern constructor.
.. ocv:function:: CustomPattern()
CustomPattern::create
---------------------
A method that initializes the class and generates the necessary detectors, extractors and matchers.
.. ocv:function:: bool create(InputArray pattern, const Size2f boardSize, OutputArray output = noArray())
:param pattern: The image, which will be used as a pattern. If the desired pattern is part of a bigger image, you can crop it out using image(roi).
:param boardSize: The size of the pattern in physical dimensions. These will be used to scale the points when the calibration occurs.
:param output: A matrix that is the same as the input pattern image, but has all the feature points drawn on it.
:return Returns whether the initialization was successful or not. Possible reason for failure may be that no feature points were detected.
.. seealso::
:ocv:func:`getFeatureDetector`,
:ocv:func:`getDescriptorExtractor`,
:ocv:func:`getDescriptorMatcher`
.. note::
* Determine the number of detected feature points can be done through :ocv:func:`getPatternPoints` method.
* The feature detector, extractor and matcher cannot be changed after initialization.
CustomPattern::findPattern
--------------------------
Finds the pattern in the input image
.. ocv:function:: bool findPattern(InputArray image, OutputArray matched_features, OutputArray pattern_points, const double ratio = 0.7, const double proj_error = 8.0, const bool refine_position = false, OutputArray out = noArray(), OutputArray H = noArray(), OutputArray pattern_corners = noArray());
:param image: The input image where the pattern is searched for.
:param matched_features: A ``vector<Point2f>`` of the projections of calibration pattern points, matched in the image. The points correspond to the ``pattern_points``.``matched_features`` and ``pattern_points`` have the same size.
:param pattern_points: A ``vector<Point3f>`` of calibration pattern points in the calibration pattern coordinate space.
:param ratio: A ratio used to threshold matches based on D. Lowe's point ratio test.
:param proj_error: The maximum projection error that is allowed when the found points are back projected. A lower projection error will be beneficial for eliminating mismatches. Higher values are recommended when the camera lens has greater distortions.
:param refine_position: Whether to refine the position of the feature points with :ocv:func:`cornerSubPix`.
:param out: An image showing the matched feature points and a contour around the estimated pattern.
:param H: The homography transformation matrix between the pattern and the current image.
:param pattern_corners: A ``vector<Point2f>`` containing the 4 corners of the found pattern.
:return The method return whether the pattern was found or not.
CustomPattern::isInitialized
----------------------------
.. ocv:function:: bool isInitialized()
:return If the class is initialized or not.
CustomPattern::getPatternPoints
-------------------------------
.. ocv:function:: void getPatternPoints(OutputArray original_points)
:param original_points: Fills the vector with the points found in the pattern.
CustomPattern::getPixelSize
---------------------------
.. ocv:function:: double getPixelSize()
:return Get the physical pixel size as initialized by the pattern.
CustomPattern::setFeatureDetector
---------------------------------
.. ocv:function:: bool setFeatureDetector(Ptr<FeatureDetector> featureDetector)
:param featureDetector: Set a new FeatureDetector.
:return Is it successfully set? Will fail if the object is already initialized by :ocv:func:`create`.
.. note::
* It is left to user discretion to select matching feature detector, extractor and matchers. Please consult the documentation for each to confirm coherence.
CustomPattern::setDescriptorExtractor
-------------------------------------
.. ocv:function:: bool setDescriptorExtractor(Ptr<DescriptorExtractor> extractor)
:param extractor: Set a new DescriptorExtractor.
:return Is it successfully set? Will fail if the object is already initialized by :ocv:func:`create`.
CustomPattern::setDescriptorMatcher
-----------------------------------
.. ocv:function:: bool setDescriptorMatcher(Ptr<DescriptorMatcher> matcher)
:param matcher: Set a new DescriptorMatcher.
:return Is it successfully set? Will fail if the object is already initialized by :ocv:func:`create`.
CustomPattern::getFeatureDetector
---------------------------------
.. ocv:function:: Ptr<FeatureDetector> getFeatureDetector()
:return The used FeatureDetector.
CustomPattern::getDescriptorExtractor
-------------------------------------
.. ocv:function:: Ptr<DescriptorExtractor> getDescriptorExtractor()
:return The used DescriptorExtractor.
CustomPattern::getDescriptorMatcher
-----------------------------------
.. ocv:function:: Ptr<DescriptorMatcher> getDescriptorMatcher()
:return The used DescriptorMatcher.
CustomPattern::calibrate
------------------------
Calibrates the camera.
.. ocv:function:: double calibrate(InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints, Size imageSize, InputOutputArray cameraMatrix, InputOutputArray distCoeffs, OutputArrayOfArrays rvecs, OutputArrayOfArrays tvecs, int flags = 0, TermCriteria criteria = TermCriteria(TermCriteria::COUNT + TermCriteria::EPS, 30, DBL_EPSILON))
See :ocv:func:`calibrateCamera` for parameter information.
CustomPattern::findRt
---------------------
Finds the rotation and translation vectors of the pattern.
.. ocv:function:: bool findRt(InputArray objectPoints, InputArray imagePoints, InputArray cameraMatrix, InputArray distCoeffs, OutputArray rvec, OutputArray tvec, bool useExtrinsicGuess = false, int flags = ITERATIVE)
.. ocv:function:: bool findRt(InputArray image, InputArray cameraMatrix, InputArray distCoeffs, OutputArray rvec, OutputArray tvec, bool useExtrinsicGuess = false, int flags = ITERATIVE)
:param image: The image, in which the rotation and translation of the pattern will be found.
See :ocv:func:`solvePnP` for parameter information.
CustomPattern::findRtRANSAC
---------------------------
Finds the rotation and translation vectors of the pattern using RANSAC.
.. ocv:function:: bool findRtRANSAC(InputArray objectPoints, InputArray imagePoints, InputArray cameraMatrix, InputArray distCoeffs, OutputArray rvec, OutputArray tvec, bool useExtrinsicGuess = false, int iterationsCount = 100, float reprojectionError = 8.0, int minInliersCount = 100, OutputArray inliers = noArray(), int flags = ITERATIVE)
.. ocv:function:: bool findRtRANSAC(InputArray image, InputArray cameraMatrix, InputArray distCoeffs, OutputArray rvec, OutputArray tvec, bool useExtrinsicGuess = false, int iterationsCount = 100, float reprojectionError = 8.0, int minInliersCount = 100, OutputArray inliers = noArray(), int flags = ITERATIVE)
:param image: The image, in which the rotation and translation of the pattern will be found.
See :ocv:func:`solvePnPRANSAC` for parameter information.
CustomPattern::drawOrientation
------------------------------
Draws the ``(x,y,z)`` axis on the image, in the center of the pattern, showing the orientation of the pattern.
.. ocv:function:: void drawOrientation(InputOutputArray image, InputArray tvec, InputArray rvec, InputArray cameraMatrix, InputArray distCoeffs, double axis_length = 3, int axis_width = 2)
:param image: The image, based on which the rotation and translation was calculated. The axis will be drawn in color - ``x`` - in red, ``y`` - in green, ``z`` - in blue.
:param tvec: Translation vector.
:param rvec: Rotation vector.
:param cameraMatrix: The camera matrix.
:param distCoeffs: The distortion coefficients.
:param axis_length: The length of the axis symbol.
:param axis_width: The width of the axis symbol.

@ -0,0 +1,149 @@
/*M///////////////////////////////////////////////////////////////////////////////////////
//
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
//
// By downloading, copying, installing or using the software you agree to this license.
// If you do not agree to this license, do not download, install,
// copy or use the software.
//
//
// License Agreement
// For Open Source Computer Vision Library
//
// Copyright (C) 2014, OpenCV Foundation, all rights reserved.
// Third party copyrights are property of their respective owners.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// * Redistribution's of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistribution's 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.
//
// * The name of the copyright holders may not 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 Intel Corporation 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.
//
//M*/
#ifndef __OPENCV_CCALIB_HPP__
#define __OPENCV_CCALIB_HPP__
#include <opencv2/core.hpp>
#include <opencv2/features2d.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/calib3d.hpp>
#include <vector>
namespace cv{ namespace ccalib{
class CV_EXPORTS CustomPattern : public Algorithm
{
public:
CustomPattern();
virtual ~CustomPattern();
bool create(InputArray pattern, const Size2f boardSize, OutputArray output = noArray());
bool findPattern(InputArray image, OutputArray matched_features, OutputArray pattern_points, const double ratio = 0.7,
const double proj_error = 8.0, const bool refine_position = false, OutputArray out = noArray(),
OutputArray H = noArray(), OutputArray pattern_corners = noArray());
bool isInitialized();
void getPatternPoints(OutputArray original_points);
/*
Returns a vector<Point> of the original points.
*/
double getPixelSize();
/*
Get the pixel size of the pattern
*/
bool setFeatureDetector(Ptr<FeatureDetector> featureDetector);
bool setDescriptorExtractor(Ptr<DescriptorExtractor> extractor);
bool setDescriptorMatcher(Ptr<DescriptorMatcher> matcher);
Ptr<FeatureDetector> getFeatureDetector();
Ptr<DescriptorExtractor> getDescriptorExtractor();
Ptr<DescriptorMatcher> getDescriptorMatcher();
double calibrate(InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints,
Size imageSize, InputOutputArray cameraMatrix, InputOutputArray distCoeffs,
OutputArrayOfArrays rvecs, OutputArrayOfArrays tvecs, int flags = 0,
TermCriteria criteria = TermCriteria(TermCriteria::COUNT + TermCriteria::EPS, 30, DBL_EPSILON));
/*
Calls the calirateCamera function with the same inputs.
*/
bool findRt(InputArray objectPoints, InputArray imagePoints, InputArray cameraMatrix, InputArray distCoeffs,
OutputArray rvec, OutputArray tvec, bool useExtrinsicGuess = false, int flags = SOLVEPNP_ITERATIVE);
bool findRt(InputArray image, InputArray cameraMatrix, InputArray distCoeffs,
OutputArray rvec, OutputArray tvec, bool useExtrinsicGuess = false, int flags = SOLVEPNP_ITERATIVE);
/*
Uses solvePnP to find the rotation and translation of the pattern
with respect to the camera frame.
*/
bool findRtRANSAC(InputArray objectPoints, InputArray imagePoints, InputArray cameraMatrix, InputArray distCoeffs,
OutputArray rvec, OutputArray tvec, bool useExtrinsicGuess = false, int iterationsCount = 100,
float reprojectionError = 8.0, int minInliersCount = 100, OutputArray inliers = noArray(), int flags = SOLVEPNP_ITERATIVE);
bool findRtRANSAC(InputArray image, InputArray cameraMatrix, InputArray distCoeffs,
OutputArray rvec, OutputArray tvec, bool useExtrinsicGuess = false, int iterationsCount = 100,
float reprojectionError = 8.0, int minInliersCount = 100, OutputArray inliers = noArray(), int flags = SOLVEPNP_ITERATIVE);
/*
Uses solvePnPRansac()
*/
void drawOrientation(InputOutputArray image, InputArray tvec, InputArray rvec, InputArray cameraMatrix,
InputArray distCoeffs, double axis_length = 3, int axis_width = 2);
/*
pattern_corners -> projected over the image position of the edges of the pattern.
*/
private:
Mat img_roi;
std::vector<Point2f> obj_corners;
double pxSize;
bool initialized;
Ptr<FeatureDetector> detector;
Ptr<DescriptorExtractor> descriptorExtractor;
Ptr<DescriptorMatcher> descriptorMatcher;
std::vector<KeyPoint> keypoints;
std::vector<Point3f> points3d;
Mat descriptor;
bool init(Mat& image, const float pixel_size, OutputArray output = noArray());
bool findPatternPass(const Mat& image, std::vector<Point2f>& matched_features, std::vector<Point3f>& pattern_points,
Mat& H, std::vector<Point2f>& scene_corners, const double pratio, const double proj_error,
const bool refine_position = false, const Mat& mask = Mat(), OutputArray output = noArray());
void scaleFoundPoints(const double squareSize, const std::vector<KeyPoint>& corners, std::vector<Point3f>& pts3d);
void check_matches(std::vector<Point2f>& matched, const std::vector<Point2f>& pattern, std::vector<DMatch>& good, std::vector<Point3f>& pattern_3d, const Mat& H);
void keypoints2points(const std::vector<KeyPoint>& in, std::vector<Point2f>& out);
void updateKeypointsPos(std::vector<KeyPoint>& in, const std::vector<Point2f>& new_pos);
void refinePointsPos(const Mat& img, std::vector<Point2f>& p);
void refineKeypointsPos(const Mat& img, std::vector<KeyPoint>& kp);
};
}} // namespace ccalib, cv
#endif

@ -0,0 +1,494 @@
/*M///////////////////////////////////////////////////////////////////////////////////////
//
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
//
// By downloading, copying, installing or using the software you agree to this license.
// If you do not agree to this license, do not download, install,
// copy or use the software.
//
//
// License Agreement
// For Open Source Computer Vision Library
//
// Copyright (C) 2014, OpenCV Foundation, all rights reserved.
// Third party copyrights are property of their respective owners.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// * Redistribution's of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistribution's 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.
//
// * The name of the copyright holders may not 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 Intel Corporation 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.
//
//M*/
#ifndef __OPENCV_CCALIB_CPP__
#define __OPENCV_CCALIB_CPP__
#ifdef __cplusplus
#include "precomp.hpp"
#include "opencv2/ccalib.hpp"
#include <opencv2/core.hpp>
#include <opencv2/core/types_c.h> // CV_TERM
#include <opencv2/imgproc.hpp>
#include <opencv2/calib3d.hpp>
#include <opencv2/features2d.hpp>
#include <vector>
#include <cstring>
namespace cv{ namespace ccalib{
using namespace std;
const int MIN_CONTOUR_AREA_PX = 100;
const float MIN_CONTOUR_AREA_RATIO = 0.2f;
const float MAX_CONTOUR_AREA_RATIO = 5.0f;
const int MIN_POINTS_FOR_H = 10;
const float MAX_PROJ_ERROR_PX = 5.0f;
CustomPattern::CustomPattern()
{
initialized = false;
}
bool CustomPattern::create(InputArray pattern, const Size2f boardSize, OutputArray output)
{
CV_Assert(!pattern.empty() && (boardSize.area() > 0));
Mat img = pattern.getMat();
float pixel_size = (boardSize.width > boardSize.height)? // Choose the longer side for more accurate calculation
float(img.cols) / boardSize.width: // width is longer
float(img.rows) / boardSize.height; // height is longer
return init(img, pixel_size, output);
}
bool CustomPattern::init(Mat& image, const float pixel_size, OutputArray output)
{
image.copyTo(img_roi);
//Setup object corners
obj_corners = std::vector<Point2f>(4);
obj_corners[0] = Point2f(0, 0); obj_corners[1] = Point2f(float(img_roi.cols), 0);
obj_corners[2] = Point2f(float(img_roi.cols), float(img_roi.rows)); obj_corners[3] = Point2f(0, float(img_roi.rows));
if (!detector) // if no detector chosen, use default
{
detector = FeatureDetector::create("ORB");
detector->set("nFeatures", 2000);
detector->set("scaleFactor", 1.15);
detector->set("nLevels", 30);
}
detector->detect(img_roi, keypoints);
if (keypoints.empty())
{
initialized = false;
return initialized;
}
refineKeypointsPos(img_roi, keypoints);
if (!descriptorExtractor) // if no extractor chosen, use default
descriptorExtractor = DescriptorExtractor::create("ORB");
descriptorExtractor->compute(img_roi, keypoints, descriptor);
if (!descriptorMatcher)
descriptorMatcher = DescriptorMatcher::create("BruteForce-Hamming(2)");
// Scale found points by pixelSize
pxSize = pixel_size;
scaleFoundPoints(pxSize, keypoints, points3d);
if (output.needed())
{
Mat out;
drawKeypoints(img_roi, keypoints, out, Scalar(0, 0, 255));
out.copyTo(output);
}
initialized = !keypoints.empty();
return initialized; // initialized if any keypoints are found
}
CustomPattern::~CustomPattern() {}
bool CustomPattern::isInitialized()
{
return initialized;
}
bool CustomPattern::setFeatureDetector(Ptr<FeatureDetector> featureDetector)
{
if (!initialized)
{
this->detector = featureDetector;
return true;
}
else
return false;
}
bool CustomPattern::setDescriptorExtractor(Ptr<DescriptorExtractor> extractor)
{
if (!initialized)
{
this->descriptorExtractor = extractor;
return true;
}
else
return false;
}
bool CustomPattern::setDescriptorMatcher(Ptr<DescriptorMatcher> matcher)
{
if (!initialized)
{
this->descriptorMatcher = matcher;
return true;
}
else
return false;
}
Ptr<FeatureDetector> CustomPattern::getFeatureDetector()
{
return detector;
}
Ptr<DescriptorExtractor> CustomPattern::getDescriptorExtractor()
{
return descriptorExtractor;
}
Ptr<DescriptorMatcher> CustomPattern::getDescriptorMatcher()
{
return descriptorMatcher;
}
void CustomPattern::scaleFoundPoints(const double pixelSize,
const vector<KeyPoint>& corners, vector<Point3f>& pts3d)
{
for (unsigned int i = 0; i < corners.size(); ++i)
{
pts3d.push_back(Point3f(
float(corners[i].pt.x * pixelSize),
float(corners[i].pt.y * pixelSize),
0));
}
}
//Takes a descriptor and turns it into an (x,y) point
void CustomPattern::keypoints2points(const vector<KeyPoint>& in, vector<Point2f>& out)
{
out.clear();
out.reserve(in.size());
for (size_t i = 0; i < in.size(); ++i)
{
out.push_back(in[i].pt);
}
}
void CustomPattern::updateKeypointsPos(vector<KeyPoint>& in, const vector<Point2f>& new_pos)
{
for (size_t i = 0; i < in.size(); ++i)
{
in[i].pt= new_pos[i];
}
}
void CustomPattern::refinePointsPos(const Mat& img, vector<Point2f>& p)
{
Mat gray;
cvtColor(img, gray, COLOR_RGB2GRAY);
cornerSubPix(gray, p, Size(10, 10), Size(-1, -1),
TermCriteria(CV_TERMCRIT_ITER + CV_TERMCRIT_EPS, 30, 0.1));
}
void CustomPattern::refineKeypointsPos(const Mat& img, vector<KeyPoint>& kp)
{
vector<Point2f> points;
keypoints2points(kp, points);
refinePointsPos(img, points);
updateKeypointsPos(kp, points);
}
template<typename Tstve>
void deleteStdVecElem(vector<Tstve>& v, int idx)
{
v[idx] = v.back();
v.pop_back();
}
void CustomPattern::check_matches(vector<Point2f>& matched, const vector<Point2f>& pattern, vector<DMatch>& good,
vector<Point3f>& pattern_3d, const Mat& H)
{
vector<Point2f> proj;
perspectiveTransform(pattern, proj, H);
int deleted = 0;
double error_sum = 0;
double error_sum_filtered = 0;
for (uint i = 0; i < proj.size(); ++i)
{
double error = norm(matched[i] - proj[i]);
error_sum += error;
if (error >= MAX_PROJ_ERROR_PX)
{
deleteStdVecElem(good, i);
deleteStdVecElem(matched, i);
deleteStdVecElem(pattern_3d, i);
++deleted;
}
else
{
error_sum_filtered += error;
}
}
}
bool CustomPattern::findPatternPass(const Mat& image, vector<Point2f>& matched_features, vector<Point3f>& pattern_points,
Mat& H, vector<Point2f>& scene_corners, const double pratio, const double proj_error,
const bool refine_position, const Mat& mask, OutputArray output)
{
if (!initialized) {return false; }
matched_features.clear();
pattern_points.clear();
vector<vector<DMatch> > matches;
vector<KeyPoint> f_keypoints;
Mat f_descriptor;
detector->detect(image, f_keypoints, mask);
if (refine_position) refineKeypointsPos(image, f_keypoints);
descriptorExtractor->compute(image, f_keypoints, f_descriptor);
descriptorMatcher->knnMatch(f_descriptor, descriptor, matches, 2); // k = 2;
vector<DMatch> good_matches;
vector<Point2f> obj_points;
for(int i = 0; i < f_descriptor.rows; ++i)
{
if(matches[i][0].distance < pratio * matches[i][1].distance)
{
const DMatch& dm = matches[i][0];
good_matches.push_back(dm);
// "keypoints1[matches[i].queryIdx] has a corresponding point in keypoints2[matches[i].trainIdx]"
matched_features.push_back(f_keypoints[dm.queryIdx].pt);
pattern_points.push_back(points3d[dm.trainIdx]);
obj_points.push_back(keypoints[dm.trainIdx].pt);
}
}
if (good_matches.size() < MIN_POINTS_FOR_H) return false;
Mat h_mask;
H = findHomography(obj_points, matched_features, RANSAC, proj_error, h_mask);
if (H.empty())
{
// cout << "findHomography() returned empty Mat." << endl;
return false;
}
for(unsigned int i = 0; i < good_matches.size(); ++i)
{
if(!h_mask.data[i])
{
deleteStdVecElem(good_matches, i);
deleteStdVecElem(matched_features, i);
deleteStdVecElem(pattern_points, i);
}
}
if (good_matches.empty()) return false;
size_t numb_elem = good_matches.size();
check_matches(matched_features, obj_points, good_matches, pattern_points, H);
if (good_matches.empty() || numb_elem < good_matches.size()) return false;
// Get the corners from the image
scene_corners = vector<Point2f>(4);
perspectiveTransform(obj_corners, scene_corners, H);
// Check correctnes of H
// Is it a convex hull?
bool cConvex = isContourConvex(scene_corners);
if (!cConvex) return false;
// Is the hull too large or small?
double scene_area = contourArea(scene_corners);
if (scene_area < MIN_CONTOUR_AREA_PX) return false;
double ratio = scene_area/img_roi.size().area();
if ((ratio < MIN_CONTOUR_AREA_RATIO) ||
(ratio > MAX_CONTOUR_AREA_RATIO)) return false;
// Is any of the projected points outside the hull?
for(unsigned int i = 0; i < good_matches.size(); ++i)
{
if(pointPolygonTest(scene_corners, f_keypoints[good_matches[i].queryIdx].pt, false) < 0)
{
deleteStdVecElem(good_matches, i);
deleteStdVecElem(matched_features, i);
deleteStdVecElem(pattern_points, i);
}
}
if (output.needed())
{
Mat out;
drawMatches(image, f_keypoints, img_roi, keypoints, good_matches, out);
// Draw lines between the corners (the mapped object in the scene - image_2 )
line(out, scene_corners[0], scene_corners[1], Scalar(0, 255, 0), 2);
line(out, scene_corners[1], scene_corners[2], Scalar(0, 255, 0), 2);
line(out, scene_corners[2], scene_corners[3], Scalar(0, 255, 0), 2);
line(out, scene_corners[3], scene_corners[0], Scalar(0, 255, 0), 2);
out.copyTo(output);
}
return (!good_matches.empty()); // return true if there are enough good matches
}
bool CustomPattern::findPattern(InputArray image, OutputArray matched_features, OutputArray pattern_points,
const double ratio, const double proj_error, const bool refine_position, OutputArray out,
OutputArray H, OutputArray pattern_corners)
{
CV_Assert(!image.empty() && proj_error > 0);
Mat img = image.getMat();
vector<Point2f> m_ftrs;
vector<Point3f> pattern_pts;
Mat _H;
vector<Point2f> scene_corners;
if (!findPatternPass(img, m_ftrs, pattern_pts, _H, scene_corners, 0.6, proj_error, refine_position))
return false; // pattern not found
Mat mask = Mat::zeros(img.size(), CV_8UC1);
vector<vector<Point> > obj(1);
vector<Point> scorners_int(scene_corners.size());
for (uint i = 0; i < scene_corners.size(); ++i)
scorners_int[i] = (Point)scene_corners[i]; // for drawContours
obj[0] = scorners_int;
drawContours(mask, obj, 0, Scalar(255), FILLED);
// Second pass
Mat output;
if (!findPatternPass(img, m_ftrs, pattern_pts, _H, scene_corners,
ratio, proj_error, refine_position, mask, output))
return false; // pattern not found
Mat(m_ftrs).copyTo(matched_features);
Mat(pattern_pts).copyTo(pattern_points);
if (out.needed()) output.copyTo(out);
if (H.needed()) _H.copyTo(H);
if (pattern_corners.needed()) Mat(scene_corners).copyTo(pattern_corners);
return (!m_ftrs.empty());
}
void CustomPattern::getPatternPoints(OutputArray original_points)
{
return Mat(keypoints).copyTo(original_points);
}
double CustomPattern::getPixelSize()
{
return pxSize;
}
double CustomPattern::calibrate(InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints,
Size imageSize, InputOutputArray cameraMatrix, InputOutputArray distCoeffs,
OutputArrayOfArrays rvecs, OutputArrayOfArrays tvecs, int flags,
TermCriteria criteria)
{
return calibrateCamera(objectPoints, imagePoints, imageSize, cameraMatrix, distCoeffs,
rvecs, tvecs, flags, criteria);
}
bool CustomPattern::findRt(InputArray objectPoints, InputArray imagePoints, InputArray cameraMatrix,
InputArray distCoeffs, OutputArray rvec, OutputArray tvec, bool useExtrinsicGuess, int flags)
{
return solvePnP(objectPoints, imagePoints, cameraMatrix, distCoeffs, rvec, tvec, useExtrinsicGuess, flags);
}
bool CustomPattern::findRt(InputArray image, InputArray cameraMatrix, InputArray distCoeffs,
OutputArray rvec, OutputArray tvec, bool useExtrinsicGuess, int flags)
{
vector<Point2f> imagePoints;
vector<Point3f> objectPoints;
if (!findPattern(image, imagePoints, objectPoints))
return false;
return solvePnP(objectPoints, imagePoints, cameraMatrix, distCoeffs, rvec, tvec, useExtrinsicGuess, flags);
}
bool CustomPattern::findRtRANSAC(InputArray objectPoints, InputArray imagePoints, InputArray cameraMatrix, InputArray distCoeffs,
OutputArray rvec, OutputArray tvec, bool useExtrinsicGuess, int iterationsCount,
float reprojectionError, int minInliersCount, OutputArray inliers, int flags)
{
solvePnPRansac(objectPoints, imagePoints, cameraMatrix, distCoeffs, rvec, tvec, useExtrinsicGuess,
iterationsCount, reprojectionError, minInliersCount, inliers, flags);
return true; // for consistency with the other methods
}
bool CustomPattern::findRtRANSAC(InputArray image, InputArray cameraMatrix, InputArray distCoeffs,
OutputArray rvec, OutputArray tvec, bool useExtrinsicGuess, int iterationsCount,
float reprojectionError, int minInliersCount, OutputArray inliers, int flags)
{
vector<Point2f> imagePoints;
vector<Point3f> objectPoints;
if (!findPattern(image, imagePoints, objectPoints))
return false;
solvePnPRansac(objectPoints, imagePoints, cameraMatrix, distCoeffs, rvec, tvec, useExtrinsicGuess,
iterationsCount, reprojectionError, minInliersCount, inliers, flags);
return true;
}
void CustomPattern::drawOrientation(InputOutputArray image, InputArray tvec, InputArray rvec,
InputArray cameraMatrix, InputArray distCoeffs,
double axis_length, int axis_width)
{
Point3f ptrCtr3d = Point3f(float((img_roi.cols * pxSize)/2.0), float((img_roi.rows * pxSize)/2.0), 0);
vector<Point3f> axis(4);
float alen = float(axis_length * pxSize);
axis[0] = ptrCtr3d;
axis[1] = Point3f(alen, 0, 0) + ptrCtr3d;
axis[2] = Point3f(0, alen, 0) + ptrCtr3d;
axis[3] = Point3f(0, 0, -alen) + ptrCtr3d;
vector<Point2f> proj_axis;
projectPoints(axis, rvec, tvec, cameraMatrix, distCoeffs, proj_axis);
Mat img = image.getMat();
line(img, proj_axis[0], proj_axis[1], Scalar(0, 0, 255), axis_width); // red
line(img, proj_axis[0], proj_axis[2], Scalar(0, 255, 0), axis_width); // green
line(img, proj_axis[0], proj_axis[3], Scalar(255, 0, 0), axis_width); // blue
img.copyTo(image);
}
}} // namespace ccalib, cv
#endif // __OPENCV_CCALIB_CPP__
#endif // cplusplus

@ -0,0 +1,51 @@
/*M///////////////////////////////////////////////////////////////////////////////////////
//
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
//
// By downloading, copying, installing or using the software you agree to this license.
// If you do not agree to this license, do not download, install,
// copy or use the software.
//
//
// License Agreement
// For Open Source Computer Vision Library
//
// Copyright (C) 2014, OpenCV Foundation, all rights reserved.
// Third party copyrights are property of their respective owners.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// * Redistribution's of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistribution's 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.
//
// * The name of the copyright holders may not 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 Intel Corporation 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.
//
//M*/
#ifndef __OPENCV_CCALIB_PRECOMP__
#define __OPENCV_CCALIB_PRECOMP__
#include <opencv2/core.hpp>
#include <opencv2/calib3d.hpp>
#include <opencv2/features2d.hpp>
#include <vector>
#endif

@ -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
...

@ -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
*~

@ -0,0 +1,20 @@
if(NOT HAVE_QT5)
ocv_module_disable(cvv)
return()
endif()
# we need C++11 and want warnings:
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wextra -pedantic")
ocv_warnings_disable(CMAKE_CXX_FLAGS -Wshadow)
# Qt5
set(CMAKE_AUTOMOC ON)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
foreach(dt5_dep Core Gui Widgets)
add_definitions(${Qt5${dt5_dep}_DEFINITIONS})
include_directories(${Qt5${dt5_dep}_INCLUDE_DIRS})
list(APPEND CVV_LIBRARIES ${Qt5${dt5_dep}_LIBRARIES})
endforeach()
set(the_description "Debug visualization framework")
ocv_define_module(cvv opencv_core opencv_imgproc opencv_features2d ${CVV_LIBRARIES})

@ -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.

@ -0,0 +1,2 @@
CVVisual
========

@ -0,0 +1,11 @@
*********************************************************************
cvv. GUI for Interactive Visual Debugging of Computer Vision Programs
*********************************************************************
The module provides an interactive GUI to debug and incrementally design computer vision algorithms. The debug statements can remain in the code after development and aid in further changes because they have neglectable overhead if the program is compiled in release mode.
.. toctree::
:maxdepth: 2
CVV API Documentation <cvv_api/index>
CVV GUI Documentation <cvv_gui/index>

@ -0,0 +1,85 @@
CVV : the API
*************
.. highlight:: cpp
Introduction
++++++++++++
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.
API Functions
+++++++++++++
showImage
---------
Add a single image to debug GUI (similar to :imshow:`imshow <>`).
.. ocv:function:: void showImage(InputArray img, const CallMetaData& metaData, const string& description, const string& view)
: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.
debugFilter
-----------
Add two images to debug GUI for comparison. Usually the input and output of some filter operation, whose result should be inspected.
.. ocv:function:: void debugFilter(InputArray original, InputArray result, const CallMetaData& metaData, const string& description, const string& view)
:param original: First image for comparison, e.g. filter input.
:param result: Second image for comparison, e.g. filter output.
:param metaData: See :ocv:func:`showImage`
:param description: See :ocv:func:`showImage`
:param view: See :ocv:func:`showImage`
debugDMatch
-----------
Add a filled in :basicstructures:`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<>`).
.. ocv:function:: void debugDMatch(InputArray img1, std::vector<cv::KeyPoint> keypoints1, InputArray img2, std::vector<cv::KeyPoint> keypoints2, std::vector<cv::DMatch> matches, const CallMetaData& metaData, const string& description, const string& view, bool useTrainDescriptor)
:param img1: First image used in :basicstructures:`DMatch <dmatch>`.
:param keypoints1: Keypoints of first image.
:param img2: Second image used in DMatch.
:param keypoints2: Keypoints of second image.
:param metaData: See :ocv:func:`showImage`
:param description: See :ocv:func:`showImage`
:param view: See :ocv:func:`showImage`
:param useTrainDescriptor: Use :basicstructures:`DMatch <dmatch>`'s train descriptor index instead of query descriptor index.
finalShow
---------
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).
.. ocv:function:: void finalShow()
setDebugFlag
------------
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).
.. ocv:function:: void setDebugFlag(bool active)
:param active: See above

@ -0,0 +1,24 @@
CVV : the GUI
*************
.. highlight:: cpp
Introduction
++++++++++++
For now: See cvv tutorial.
Overview
++++++++
Filter
------
Views
++++++++

@ -0,0 +1,68 @@
#ifndef CVVISUAL_CALL_DATA_HPP
#define CVVISUAL_CALL_DATA_HPP
#include <string>
#include <cstddef>
#include <utility>
namespace 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
#ifdef __GNUC__
#define CVVISUAL_FUNCTION_NAME_MACRO __PRETTY_FUNCTION__
#else
#define CVVISUAL_FUNCTION_NAME_MACRO __func__
#endif
/**
* @brief Creates an instance of CallMetaData with the location of the macro as
* value.
*/
#define CVVISUAL_LOCATION \
::cvv::impl::CallMetaData(__FILE__, __LINE__, \
CVVISUAL_FUNCTION_NAME_MACRO)
#endif

@ -0,0 +1,6 @@
#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>

@ -0,0 +1,45 @@
#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
{
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 Set the debug-mode for this TU and thread.
*/
static inline void setDebugFlag(bool active)
{
impl::getDebugFlag() = active;
}
} // namespace cvv
#endif

@ -0,0 +1,80 @@
#ifndef CVVISUAL_DEBUG_DMATCH_HPP
#define CVVISUAL_DEBUG_DMATCH_HPP
#include <string>
#include "opencv2/core/core.hpp"
#include "opencv2/features2d/features2d.hpp"
#include "call_meta_data.hpp"
#include "debug_mode.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);
} // namespace impl
#ifdef CVVISUAL_DEBUGMODE
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);
}
}
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
/**
* @brief Debug a set of matches between two images.
*/
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)
{
}
/**
* Dito.
*/
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,69 @@
#ifndef CVVISUAL_DEBUG_FILTER_HPP
#define CVVISUAL_DEBUG_FILTER_HPP
#include <string>
#include "opencv2/core/core.hpp"
#include "call_meta_data.hpp"
#include "debug_mode.hpp"
namespace cvv
{
namespace impl
{
// implementation outside API
void debugFilter(cv::InputArray original, cv::InputArray result,
const CallMetaData &data, const char *description,
const char *view);
} // namespace impl
#ifdef CVVISUAL_DEBUGMODE
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);
}
}
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
/**
* @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, cv::InputArray,
impl::CallMetaData = impl::CallMetaData(),
const char * = nullptr, const char * = nullptr)
{
}
/**
* Dito.
*/
static inline void debugFilter(cv::InputArray, cv::InputArray,
impl::CallMetaData, const ::std::string &,
const ::std::string &)
{
}
#endif
} // namespace cvv
#endif

@ -0,0 +1,53 @@
#ifndef CVVISUAL_FINAL_SHOW_HPP
#define CVVISUAL_FINAL_SHOW_HPP
#include "debug_mode.hpp"
namespace cvv
{
namespace impl
{
void finalShow();
}
/**
* @brief Passes the control to the debug-window for a last time.
*
* This function must be called once if there was any prior debug-call. After that all debug-data
* are freed.
*
* If there was no prior call it may be called once in which case it returns
* without opening a window.
*
* In either case no further debug-calls must be made (undefined behaviour!!).
*
*/
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,62 @@
#ifndef CVVISUAL_DEBUG_SHOW_IMAGE_HPP
#define CVVISUAL_DEBUG_SHOW_IMAGE_HPP
#include <string>
#include "opencv2/core/core.hpp"
#include "call_meta_data.hpp"
#include "debug_mode.hpp"
namespace cvv
{
namespace impl
{
// implementation outside API
void showImage(cv::InputArray img, const CallMetaData &data,
const char *description, const char *view);
} // namespace impl
#ifdef CVVISUAL_DEBUGMODE
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);
}
}
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
/**
* Use the debug-framework to show a single image.
*/
static inline void showImage(cv::InputArray,
impl::CallMetaData = impl::CallMetaData(),
const char * = nullptr, const char * = nullptr)
{
}
/**
* Dito.
*/
static inline void showImage(cv::InputArray, impl::CallMetaData,
const ::std::string &, const ::std::string &)
{
}
#endif
} // namespace cvv
#endif

@ -0,0 +1,138 @@
// system includes
#include <getopt.h>
#include <iostream>
// library includes
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/features2d/features2d.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>
template<class T> std::string toString(const T& p_arg)
{
std::stringstream ss;
ss << p_arg;
return ss.str();
}
void
usage()
{
printf("usage: cvv_demo [-r WxH]\n");
printf("-h print this help\n");
printf("-r WxH change resolution to width W and height H\n");
}
int
main(int argc, char** argv)
{
cv::Size* resolution = nullptr;
// parse options
const char* optstring = "hr:";
int opt;
while ((opt = getopt(argc, argv, optstring)) != -1) {
switch (opt) {
case 'h':
usage();
return 0;
break;
case 'r':
{
char dummych;
resolution = new cv::Size();
if (sscanf(optarg, "%d%c%d", &resolution->width, &dummych, &resolution->height) != 3) {
printf("%s not a valid resolution\n", optarg);
return 1;
}
}
break;
default: /* '?' */
usage();
return 2;
}
}
// setup video capture
cv::VideoCapture capture(0);
if (!capture.isOpened()) {
std::cout << "Could not open VideoCapture" << std::endl;
return 3;
}
if (resolution) {
printf("Setting resolution to %dx%d\n", resolution->width, resolution->height);
capture.set(CV_CAP_PROP_FRAME_WIDTH, resolution->width);
capture.set(CV_CAP_PROP_FRAME_HEIGHT, resolution->height);
}
cv::Mat prevImgGray;
std::vector<cv::KeyPoint> prevKeypoints;
cv::Mat prevDescriptors;
int maxFeatureCount = 500;
cv::ORB detector(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, CV_BGR2GRAY);
cvv::debugFilter(imgRead, imgGray, CVVISUAL_LOCATION, "to gray");
// detect ORB features
std::vector<cv::KeyPoint> keypoints;
cv::Mat descriptors;
detector(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;
}
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>());
}
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>());
}
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 neccessary.
* @param tabId id of the tab
*/
void showAndOpenCallTab(size_t tabId);
/**
* @brief Opens the tab it if neccessary.
* @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

@ -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

@ -0,0 +1,63 @@
#ifndef CVVISUAL_EXTENSION_API_HPP
#define CVVISUAL_EXTENSION_API_HPP
#include <opencv2/core/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 recieves 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

@ -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

@ -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();
}
}
}

@ -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);
size_t getCallTabIdByTabIndex(int index);
bool hasTabAtIndex(int index);
};
}
}
#endif

@ -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

@ -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

@ -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

@ -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);
}
}
}

@ -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);
private:
OverviewPanel *ovPanel;
};
}
}
#endif

@ -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

@ -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 neccessary.
* @param newGroup new group of rows that will be displayed
*/
void setRowGroup(stfl::ElementGroup<OverviewTableRow> &newGroup);
protected:
void resizeEvent(QResizeEvent *event);
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

@ -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);
}
}
}

@ -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

@ -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);
}
}
}
}

@ -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

@ -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());
}
}
}
}
}

@ -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/core.hpp>
#include <opencv2/features2d/features2d.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

@ -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);
}
}
}
}

@ -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

@ -0,0 +1,323 @@
#include "rawview_table_row.hpp"
#include <functional>
#include <iostream>
#include <utility>
#include <QTableWidgetItem>
#include <QImage>
#include "../qtutil/util.hpp"
#include "../stfl/stringutils.hpp"
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;
}
}
}

@ -0,0 +1,245 @@
#ifndef CVVISUAL_RAWVIEWTABLEROW_HPP
#define CVVISUAL_RAWVIEWTABLEROW_HPP
#include <vector>
#include <utility>
#include <opencv2/core/core.hpp>
#include <opencv2/features2d/features2d.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

@ -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

@ -0,0 +1,27 @@
#include "call.hpp"
namespace cvv
{
namespace impl
{
size_t newCallId()
{
thread_local 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

@ -0,0 +1,116 @@
#ifndef CVVISUAL_CALL_HPP
#define CVVISUAL_CALL_HPP
#include <utility>
#include <QString>
#include "opencv2/core/core.hpp"
#include "opencv2/features2d/features2d.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;
Call &operator=(const Call &) = default;
Call &operator=(Call &&) = default;
impl::CallMetaData metaData_;
size_t id;
QString calltype;
QString description_;
QString requestedView_;
};
}
} // namespaces
#endif

@ -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

@ -0,0 +1,94 @@
#ifndef CVVISUAL_DATA_CONTROLLER_HPP
#define CVVISUAL_DATA_CONTROLLER_HPP
#include <memory>
#include <vector>
#include "opencv2/core/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

@ -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

@ -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

@ -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

@ -0,0 +1,66 @@
#ifndef CVVISUAL_FILTER_CALL_HPP
#define CVVISUAL_FILTER_CALL_HPP
#include <QString>
#include "call.hpp"
#include "opencv2/core/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

@ -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

@ -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");
}
}
}

@ -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

@ -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

@ -0,0 +1,104 @@
#ifndef CVVISUAL_MATCH_CALL_HPP
#define CVVISUAL_MATCH_CALL_HPP
#include <vector>
#include <utility>
#include <type_traits>
#include "opencv2/core/core.hpp"
#include "opencv2/features2d/features2d.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

@ -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/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

@ -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

@ -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

@ -0,0 +1,475 @@
#ifndef CVVISUAL_AUTOFILTERWIDGET_HPP
#define CVVISUAL_AUTOFILTERWIDGET_HPP
#include <array>
#include <vector>
#include <chrono>
#include "opencv2/core/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

@ -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

@ -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);
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=const 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;
PixelInType* in0;
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/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <unordered_map>
#include <QComboBox>
#include <QLabel>
#include <QString>
#include <QVBoxLayout>
#include "../../util/util.hpp"
#include "diffFilterWidget.hpp"
namespace cvv
{
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, CV_BGR2HSV);
cv::cvtColor(in.at(1).get(), filteredHSV, CV_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;
/**
* @brief Checks whether matrices in 'in' can be processed by this
* DiffFilter
*/
std::pair<bool, QString> checkInput(InputArray in) const;
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/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

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

Loading…
Cancel
Save