Merge branch 'master' of https://github.com/Itseez/opencv_contrib
@ -0,0 +1,10 @@ |
||||
*.autosave |
||||
*.pyc |
||||
*.user |
||||
*~ |
||||
.*.swp |
||||
.DS_Store |
||||
.sw[a-z] |
||||
Thumbs.db |
||||
tags |
||||
tegra/ |
After Width: | Height: | Size: 2.5 KiB |
@ -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 |
After Width: | Height: | Size: 45 KiB |
After Width: | Height: | Size: 85 KiB |
After Width: | Height: | Size: 53 KiB |
After Width: | Height: | Size: 99 KiB |
After Width: | Height: | Size: 73 KiB |
After Width: | Height: | Size: 81 KiB |
After Width: | Height: | Size: 116 KiB |
After Width: | Height: | Size: 101 KiB |
After Width: | Height: | Size: 108 KiB |
After Width: | Height: | Size: 52 KiB |
After Width: | Height: | Size: 114 KiB |
After Width: | Height: | Size: 156 KiB |
After Width: | Height: | Size: 143 KiB |
After Width: | Height: | Size: 136 KiB |
After Width: | Height: | Size: 146 KiB |
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! |
@ -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,91 @@ |
||||
#Über CVVisual |
||||
CVVisual ist eine Debug-Bibliothek für OpenCV, die verschiedene Möglichkeiten der Darstellung von Bildern und Ergebnissen von beispielsweise Filter- und Match-Operationen von OpenCV anbietet. |
||||
|
||||
##Benutzung: Beispiel |
||||
Ist die Bibliothek eingebunden, das CVVISUAL\_DEBUG-Makro definiert und die benötigten Header in den Code eingebunden, kann durch den Aufruf einer CVVisual-Funktion mit den von OpenCV gelieferten Daten als Argumenten das CVV-Hauptfenster geöffnet werden. |
||||
|
||||
Beispielsweise könnte ein Codestück folgendermaßen aussehen: |
||||
|
||||
//... |
||||
cvv::debugDMatch(src, keypoints1, src, keypoints2, match, CVVISUAL\_LOCATION); |
||||
|
||||
 |
||||
|
||||
Die Bilder werden zusammen mit Informationen und Metadaten in der Overview-Tabelle angezeigt. |
||||
Ein Doppelklick darauf öffnet ein Tab, in dem die Bilder und Matches groß angezeigt werden. |
||||
|
||||
 |
||||
|
||||
In dieser Ansicht, genannt *Line Match View* werden die KeyPoints der Matches, d.h. die von OpenCV gelieferten ähnlichen Bildpunkte, durch Linien verbunden. Im Akkordeonmenü kann man beispielsweise deren Farbe änder. `Strg + Mausrad` erlaubt, zu zoomen. |
||||
|
||||
 |
||||
|
||||
Die Art der Darstellung kann im `View`-Dropdown-Menü geändert werden; so können die Matches etwa auch als Translationslinien angezeigt werden. |
||||
|
||||
 |
||||
|
||||
Zudem gibt es bei Matches auch die Möglichkeit, die Daten in einer Tabelle anzuzeigen, im sogenannten |
||||
*Raw View*. Die Daten können hier über einen Linksklick als JSON oder CSV ins Clipboard kopiert |
||||
werden. |
||||
|
||||
 |
||||
|
||||
Wird `Step` geklickt wird die Ausführung des zu debuggenden Programmes, das beim Aufruf des Hauptfensters angehalten wurde fortgesetzt, bis es auf eine weitere CVVisual-Funktion |
||||
stößt: |
||||
|
||||
//... |
||||
cvv::debugFilter(src, dest, CVVISUAL\_LOCATION, filename); |
||||
|
||||
Das Hauptfenster erscheint erneut, wobei der neuen Datensatz der Tabelle hinzugefügt wird. |
||||
|
||||
 |
||||
|
||||
Da es sich hier um eine Filter-Operation handelt, ist die Anzeige im Tab eine andere: |
||||
|
||||
 |
||||
|
||||
Auch die möglichen Anzeigen unterscheiden sich von denen für Match-Operationen. |
||||
Der *Dual Filter View* erlaubt zum Beispiel zusätzlich, ein Differenzbild der beiden übergebenen anzuzeigen. |
||||
|
||||
 |
||||
|
||||
Nach einem *fast-forward* (`>>`) über die weiteren Schritte des Programms |
||||
|
||||
//... |
||||
cvv::debugDMatch(src, keypoints1, src, keypoints2, match, CVVISUAL\_LOCATION) |
||||
//... |
||||
cvv::debugFilter(src, dest, CVVISUAL\_LOCATION, filename); |
||||
//... |
||||
cvv::debugFilter(src, dest, CVVISUAL\_LOCATION, filename); |
||||
//... |
||||
cvv::debugDMatch(src, keypoints1, src, keypoints2, match, CVVISUAL\_LOCATION); |
||||
//... |
||||
cvv::showImage(img, CVVISUAL\_LOCATION); |
||||
//... |
||||
cvv::finalShow(); |
||||
ergibt sich im Overview folgendes Bild: |
||||
|
||||
 |
||||
|
||||
Dabei wird durch den letzten Aufruf nur ein einziges Bild zur Anzeige übergeben: |
||||
|
||||
 |
||||
|
||||
Mithilfe der Textzeile lassen sich durch Kommandos der *Filter Query Language* von CVVisual die Datensätze ordnen, filtern und gruppieren. Hier wurde nach ID gruppiert: |
||||
|
||||
 |
||||
|
||||
Dies funktioniert auch im *Raw View*. |
||||
|
||||
Hinter dem letzten Aufruf einer regulären CVVisual-Funktion muss, wie oben gesehen, `finalShow` aufgerufen werden: |
||||
|
||||
//... |
||||
cvv::finalShow(); |
||||
//... |
||||
|
||||
Es wird ein weiteres Mal das Hauptfenster angezeigt; wird jedoch der nun der einzige verbleibende, der `Close`-Knopf betätigt, wird das Hauptfenster endgültig geschlossen. |
||||
|
||||
Dies beschließt die Debug-Sitzung. |
||||
|
||||
[Quelle des zur Demonstration benutzten Bildes.](http://commons.wikimedia.org/wiki/File:PNG-Gradient.png) |
||||
|
@ -0,0 +1,34 @@ |
||||
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") |
||||
|
||||
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}) |
||||
|
||||
FIND_PACKAGE(Qt5Core REQUIRED) |
||||
include_directories(${Qt5Core_INCLUDE_DIRS}) |
||||
FIND_PACKAGE(Qt5Widgets REQUIRED) |
||||
include_directories(${Qt5Widgets_INCLUDE_DIRS}) |
||||
add_definitions(${QT_DEFINITIONS}) |
||||
|
||||
SET(OpenCVVisual_DIR "$ENV{HOME}/<<<SET ME>>>") |
||||
include_directories("${OpenCVVisual_DIR}/include") |
||||
link_directories("${OpenCVVisual_DIR}/build/release") |
||||
|
||||
add_executable(cvvt main.cpp) |
||||
target_link_libraries(cvvt |
||||
opencv_core opencv_highgui opencv_imgproc opencv_features2d |
||||
opencv_cvv |
||||
Qt5Core Qt5Widgets Qt5Gui |
||||
) |
@ -0,0 +1,5 @@ |
||||
This is a tiny example of how to use CVVisual. It requires a webcam. |
||||
|
||||
Note that the paths in CMakeLists.txt have to be set manually. |
||||
|
||||
cvvisual_test was created by Andreas Bihlmaier. |
@ -0,0 +1,137 @@ |
||||
// system includes
|
||||
#include <getopt.h> |
||||
#include <iostream> |
||||
|
||||
// library includes
|
||||
#include <opencv2/highgui/highgui.hpp> |
||||
#include <opencv2/imgproc/imgproc.hpp> |
||||
#include <opencv2/features2d/features2d.hpp> |
||||
|
||||
#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: 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) |
||||
{ |
||||
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,35 @@ |
||||
#Introduction to filter function widgets |
||||
##The class, functions and types |
||||
If you want to enable the user to provide input to a filter you can inherit the virtual class FilterFunctionWidget<In,Out>. |
||||
It provides an interface for a filter accepting In images as an input and Out images as output. |
||||
|
||||
The input images are passed using: |
||||
|
||||
```cpp |
||||
InputArray = std::array<util::Reference<const cv::Mat>,In> |
||||
``` |
||||
|
||||
and the output is provided with an output parameter of the type: |
||||
|
||||
```cpp |
||||
OutputArray = std::array<util::Reference<cv::Mat>,Out> |
||||
``` |
||||
|
||||
You should override following functions: |
||||
|
||||
```cpp |
||||
virtual void applyFilter(InputArray in,OutputArray out) const; |
||||
|
||||
virtual std::pair<bool, QString> checkInput(InputArray in) const; |
||||
``` |
||||
|
||||
`applyFilter` has to apply your filter and `checkInput` should check weather the filter can be applied (the first member of the returned pair). |
||||
In case the filter can not be applied the second member of the returned pair should contain a message for the user. |
||||
|
||||
If user input changes the setting of the filter the function _emitSignal()_ of the member _signFilterSettingsChanged__ should be called. |
||||
|
||||
For a detailed example look at _CVVisual/src/qtutil/filter/grayfilterwidget.{hpp, cpp}_ |
||||
|
||||
https://github.com/CVVisualPSETeam/CVVisual/blob/master/src/qtutil/filter/grayfilterwidget.hpp |
||||
|
||||
https://github.com/CVVisualPSETeam/CVVisual/blob/master/src/qtutil/filter/grayfilterwidget.cpp |
@ -0,0 +1,106 @@ |
||||
#Filter query language |
||||
|
||||
The filter query language is the query language used in the overview and the raw match view to simply the task of filtering, sorting and grouping data sets in a table UI. |
||||
The following is a description of the simple syntax and the supported commands. |
||||
|
||||
Just type `#` into the search field to see some supported commands, using the suggestions feature (it's inspired by the awesome z shell). |
||||
|
||||
##Syntax |
||||
A query consist basically of many subqueries starting with a `#`: |
||||
|
||||
`[raw filter subquery] #[subquery 1] [...] #[subquery n]` |
||||
|
||||
The optional first part of the query doesn't start with a `#`, it's short for `#raw [...]`. |
||||
|
||||
|
||||
There three different types of subqueries: |
||||
|
||||
###Sort query |
||||
A sort query has the following structure: |
||||
|
||||
`sort by [sort subquery 1], [...], [sort subquery n]` |
||||
|
||||
A sort subquery consist of a sort command (aka "the feature by which you want to sort the table") and a sort order: |
||||
- `[command]`: equivalent to `[command] asc` |
||||
- `[command] asc`: sorts in ascending order |
||||
- `[command] desc`: sorts in descending order |
||||
|
||||
(The sort command is typically a single word.) |
||||
|
||||
For your interest: The `[subquery n]` has higher priority than the `[subquery n+1]`. |
||||
|
||||
###Group query |
||||
A group query has the following structure: |
||||
|
||||
`group by [command 1], [...], [command n]` |
||||
|
||||
A group command is a single word declaring the feature you want to group the data sets in the table by. |
||||
The group header consist of the `n` items. |
||||
|
||||
For your interest: The raw view currently doesn't support group queries. |
||||
|
||||
###Filter query |
||||
A filter query is the basic type of query, allowing you to filter the data sets by several criterias. |
||||
|
||||
It has the following structure: |
||||
|
||||
`#[filter command] [argument]` |
||||
|
||||
It also supports several arguments for one filter command (via the comma seperated filters feature): |
||||
|
||||
`#[cs filter command] [argument 1], [...], [argument n]` |
||||
|
||||
|
||||
####Range filter query |
||||
A range filter query uses basically a comma seperated filter command with two arguments, allowing you to |
||||
filter for a range of elements (`[lower bound]` <= `element` <= `[upper bound]`). |
||||
|
||||
It has the following structure: |
||||
|
||||
`#[filter command] [lower bound], [upper bound]` |
||||
|
||||
|
||||
|
||||
##Overview |
||||
The following commands are supported in the overview: |
||||
|
||||
feauture/command | sorting supported | grouping supported | filtering supported | description |
||||
-----------------|:-----------------:|:------------------:|:--------------------|:--------------------- |
||||
id | yes | yes | yes, also range | |
||||
raw | yes | yes | only basic filter | alias for description |
||||
description | yes | yes | only basic filter | |
||||
image_count | yes | yes | yes, also range | number of images |
||||
function | yes | yes | yes | calling function |
||||
file | yes | yes | yes | inheriting file |
||||
line | yes | yes | yes, also range | |
||||
type | yes | yes | yes | call type |
||||
|
||||
|
||||
##Rawview |
||||
The following command are supported in the raw (match) view: |
||||
|
||||
feauture/command | numeric type | description/property |
||||
-----------------|:-------------|:--------------------------------------------- |
||||
match_distance | float | match distance |
||||
img_idx | integer | match img idx |
||||
query_idx | integer | match query idx |
||||
train_idx | integer | match train idx |
||||
x_1 | float | x coordinate of the "left" key point |
||||
y_1 | float | y coordinate of the "left" key point |
||||
size_1 | float | size of the "left" key point |
||||
angle_1 | float | angle of the "left" key point |
||||
response_1 | float | response (or strength) of the "left" key point |
||||
octave_1 | integer | octave of the "left" key point |
||||
x_2 | float | x coordinate of the "right" key point |
||||
y_2 | float | y coordinate of the "right" key point |
||||
size_2 | float | size of the "right" key point |
||||
angle_2 | float | angle of the "right" key point |
||||
response_2 | float | response (or strength) of the "right" key point |
||||
octave_2 | integer | octave of the "right" key point |
||||
|
||||
|
||||
|
||||
All commands support range filtering, sorting and grouping and therefore only the used numeric type |
||||
(integer or float) is given. |
||||
|
||||
See the opencv documentation for more information about the features. |
After Width: | Height: | Size: 298 KiB |
After Width: | Height: | Size: 172 KiB |
After Width: | Height: | Size: 149 KiB |
After Width: | Height: | Size: 165 KiB |
After Width: | Height: | Size: 147 KiB |
After Width: | Height: | Size: 142 KiB |
After Width: | Height: | Size: 149 KiB |
After Width: | Height: | Size: 39 KiB |
After Width: | Height: | Size: 61 KiB |
After Width: | Height: | Size: 78 KiB |
After Width: | Height: | Size: 63 KiB |
After Width: | Height: | Size: 245 KiB |
After Width: | Height: | Size: 290 KiB |
After Width: | Height: | Size: 290 KiB |
After Width: | Height: | Size: 47 KiB |
After Width: | Height: | Size: 31 KiB |
After Width: | Height: | Size: 65 KiB |
After Width: | Height: | Size: 76 KiB |
After Width: | Height: | Size: 88 KiB |
After Width: | Height: | Size: 111 KiB |
After Width: | Height: | Size: 56 KiB |
After Width: | Height: | Size: 106 KiB |
After Width: | Height: | Size: 66 KiB |
After Width: | Height: | Size: 87 KiB |
After Width: | Height: | Size: 92 KiB |
After Width: | Height: | Size: 51 KiB |
After Width: | Height: | Size: 99 KiB |
@ -0,0 +1,122 @@ |
||||
#CVVisual Example |
||||
CVVisual is a debug visualization for OpenCV; thus, its main purpose is to offer different ways to visualize |
||||
the results of OpenCV functions to make it possible to see whether they are what the programmer had in mind; |
||||
and also to offer some functionality to try other operations on the images right in the debug window. |
||||
This text wants to illustrate the use of CVVisual on a code example. |
||||
|
||||
Image we want to debug this program: |
||||
|
||||
[code_example/main.cpp](https://github.com/CVVisualPSETeam/CVVisual/tree/master/doc/code_example/main.cpp) |
||||
|
||||
Note the includes for CVVisual: |
||||
|
||||
10 #include <opencv2/debug_mode.hpp> |
||||
11 #include <opencv2/show_image.hpp> |
||||
12 #include <opencv2/filter.hpp> |
||||
13#include <opencv2/dmatch.hpp> |
||||
14 #include <opencv2/final_show.hpp> |
||||
|
||||
It takes 10 snapshots with the webcam. |
||||
With each, it first shows the image alone in the debug window, |
||||
|
||||
97 cvv::showImage(imgRead, CVVISUAL_LOCATION, imgIdString.c_str()); |
||||
|
||||
then converts it to grayscale and calls CVVisual with the original and resulting image, |
||||
|
||||
101 cv::cvtColor(imgRead, imgGray, CV_BGR2GRAY); |
||||
102 cvv::debugFilter(imgRead, imgGray, CVVISUAL_LOCATION, "to gray"); |
||||
|
||||
detects the grayscale image's ORB features |
||||
|
||||
107 detector(imgGray, cv::noArray(), keypoints, descriptors); |
||||
|
||||
and matches them to those of the previous image, if available. It calls cvv::debugDMatch() with the results. |
||||
|
||||
113 matcher.match(prevDescriptors, descriptors, matches); |
||||
... |
||||
117 cvv::debugDMatch(prevImgGray, prevKeypoints, imgGray, keypoints, matches, CVVISUAL_LOCATION, allMatchIdString.c_str()); |
||||
|
||||
Finally, it removes the worst (as defined by match distance) 0.8 quantile of matches and calls cvv::debugDMatch() again. |
||||
|
||||
121 std::sort(matches.begin(), matches.end()); |
||||
122 matches.resize(int(bestRatio * matches.size())); |
||||
... |
||||
126 cvv::debugDMatch(prevImgGray, prevKeypoints, imgGray, keypoints, matches, CVVISUAL_LOCATION, bestMatchIdString.c_str()); |
||||
|
||||
After we started the program, the CVVisual Main Window opens with one _Call_, that is, the first image that a `cvv::showImage()` was called with (the program execution was halted at this call). |
||||
|
||||
 |
||||
|
||||
The image is shown as a small thumbnail in the _Overview table_, together with additional information on it, like the line of the call and the description passed as a parameter. |
||||
We double-click it, and a tab opens, where the image is shown bigger. It looks like the webcam worked, so we press `Step` and go to the _Overview_. |
||||
|
||||
 |
||||
|
||||
The window shows up again, this time with the first _Call_ to `cvv::debugFilter()` added. |
||||
|
||||
 |
||||
|
||||
We open its tab, too, because, say, the grayscale image does not exactly look like what we wanted. |
||||
|
||||
 |
||||
|
||||
After switching to _SingleFilterView_, which will be more useful to us here, we select to not show the right two images - the grayscale image and the one below, where results of filter operations in this tab are depicted. |
||||
|
||||
 |
||||
|
||||
In `Select a filter`, a gray filter can be applied with different parameters. |
||||
|
||||
 |
||||
|
||||
This looks more like what we wanted. |
||||
Rechecking `Show image` for the unselected result image of the actual filter operation and zooming (`Ctrl` + `Mouse wheel`) into all images synchronously deeper than 60% shows the different values of the pixels. |
||||
|
||||
 |
||||
|
||||
Sadly, we can't do anything about this situation in this session, though, so we just continue. |
||||
As stepping through each single _Call_ seems quite tedious, we use the _fast-forward_ button, `>>`. |
||||
The program runs until it reaches `finalShow()`, taking images with the webcam along the way. |
||||
This saved us some clicking; on the downside, we now have quite an amount of _Calls_ in the table. |
||||
|
||||
 |
||||
|
||||
Using the [filter query language](http://cvv.mostlynerdless.de/ref/filters-ref.html), the _Calls_ to `debugDMatch()` can be filtered out as they have the specific type "match". |
||||
|
||||
 |
||||
|
||||
We open the tab of the last such _Call_, and find ourselves greeted with a dense bundle of lines across both images, which represent the matches between the two. |
||||
|
||||
 |
||||
|
||||
It is a bit unclear where there actually are matches in this case, so we switch to _TranslationMatchView_, which is a little bit better (especially after scrolling a bit to the right in the left image). |
||||
|
||||
 |
||||
|
||||
_TranslationMatchView_ shows how the matching _KeyPoints_ are moved in the respective other image. |
||||
It seems more fitting for this debug session than the _LineMatchView_, thus, we `Set`it `as default`. |
||||
Still, there are too many matches for our taste. |
||||
Back in the _Overview_, we open the _Call_ before the last, which is the one where the upper 80% of matches were not yet filtered out. |
||||
|
||||
 |
||||
|
||||
Here, the best 70% of matches can be chosen. The result looks more acceptable, and we take a mental note to change the threshold to 0.7. |
||||
|
||||
 |
||||
|
||||
The matches can also be shown in a table, the so called _RawView_: |
||||
|
||||
 |
||||
|
||||
Here, you could copy a selection of them as CSV, JSON, Ruby or Python to the clipboard. |
||||
We don't need that in the moment, though; we just close the window, and the program finishes. |
||||
We now know what we might want to change in the program. |
||||
|
||||
|
||||
Finally, a little note on the `cvv::finalShow()` function: |
||||
|
||||
It needs to be there in every program using CVVisual, after the last call to another CVVisual function, er else, the program will crash in the end. |
||||
|
||||
Hopefully, this example shed some light on how CVVisual can be used. |
||||
If you want to learn more, refer to the [API](http://cvv.mostlynerdless.de/api) or other documentation on the [web page](http://cvv.mostlynerdless.de/). |
||||
|
||||
Credit, and special thanks, goes to Andreas Bihlmaier, supervisor of the project, who provided the example code. |
@ -0,0 +1,41 @@ |
||||
#Introduction to using CVVisual |
||||
##Enabling debug mode |
||||
Define the CVV\_DEBUG_MODE macro somewhere in the translation unit. |
||||
##Opening the debug window |
||||
Open the debug window by putting one of the functions from the [CVVisual API](http://cvv.mostlynerdless.de/api) into your code. |
||||
In this example, we want to debug a call to 'dilate' (line.1) which is a filter, so we use debugFilter. |
||||
###Example: Code |
||||
src, dest and the structuring element elem are of type cv::Mat (see the OpenCV doc on [dilate()](http://docs.opencv.org/modules/imgproc/doc/filtering.html#dilate)); |
||||
CVVISUAL\_LOCATION is a special macro that inserts the location of the code and description and view can be either string-literals or std::strings. The later three are all optional. |
||||
|
||||
```cpp |
||||
#include <filter.hpp> |
||||
//... |
||||
cv::dilate(src, dest, elem); |
||||
cvv::debugFilter(src, dest, CVVISUAL_LOCATION, description, view); |
||||
``` |
||||
|
||||
When executing the code, the debugFilter function will open the window and halt the execution. |
||||
##The Overview Tab |
||||
 |
||||
|
||||
You are now in the overview tab. Each time you call one of the CVVisual functions, a *Call* is added to the table. |
||||
You can see the images you passed to the funtion as well as metadata and additional information. |
||||
The text field allows you to sort or group the Calls by different criteria; see the [filter query language documentation](http://cvv.mostlynerdless.de/ref/filterquery-ref.html) on how to use it. |
||||
Now double-click on the Call or select `Open in 'CVVisual|main window'` from the context menu. |
||||
(You can also choose to remove the Call or open it in a new window there) |
||||
##Debugging a filter operation |
||||
 |
||||
|
||||
A *CallTab* opens. In the center, there are the images from the call. |
||||
In the `View` drop-down menu you find different *Views* of the Call, that is, different visualizations of it. The accordion menu on the left offers information on the images and additional options depending on the View and the type of the Call. |
||||
Important here might be that `ImageInformation` offers the possibility to zoom (you can also use `Ctrl` plus the mouse wheel); if you zoom in more than 60%, the image pixels will be overlaid with the channel values, in a 3-channel image usually in order (top-down) BGR. |
||||
As our dilate seems to have produced acceptable results, we want to continue through the code. |
||||
So, we push the `Step` button in the upper left. |
||||
The window will comes up again the next time one of the CVVisual functions is called. |
||||
Then, we see two Calls in the Overview table, the one from before and the new one. |
||||
|
||||
You need to put `finalShow()` after the last regular CVVisual function. If the program reaches it, `Step` and the fast-forward button `>>` will vanish, so we press `Close`, which does exactly what it says. |
||||
|
||||
([Source](http://commons.wikimedia.org/wiki/File:PNG-Gradient.png) of the image used for demonstration. |
||||
Note that the screenshots were taken during development and may not depict all features of the current version.) |
@ -0,0 +1,9 @@ |
||||
filterquery: filterquery-ref.md |
||||
SingleImageView: views-ref.md#toc_2 |
||||
DefaultFilterView: views-ref.md#toc_4 |
||||
DualFilterView: views-ref.md#toc_5 |
||||
SingleFilterView: views-ref.md#toc_6 |
||||
DepthMatchView: views-ref.md#toc_8 |
||||
LineMatchView: views-ref.md#toc_9 |
||||
RawView: views-ref.md#toc_10 |
||||
TranslationMatchView: views-ref.md#toc_11 |
@ -0,0 +1,40 @@ |
||||
#Views |
||||
##General information: |
||||
Most views offer an `ImageInformation` collapsable in their accordion menus. |
||||
The zoom can be found here. |
||||
`Ctrl`+`Mouse wheel` is also zoom; `Ctrl`+`Shift`+`Mouse wheel` is a slower zoom. |
||||
If the zoom is deeper than 60%, the image's pixels will be overlaid with their channel values; usually, the order is BGR[+alpha] from the top. |
||||
|
||||
##Single Image View: |
||||
Associated with the `debugSingleImage()` function. |
||||
Shows one single image with no features other than `Image Information`. |
||||
|
||||
##Filter Views: |
||||
Associated with the `debugFilter()` function. |
||||
|
||||
###DefaultFilterView: |
||||
Shows two images with only the basic features of `ImageInformation`, synchronized zoom and `Histogram`. |
||||
|
||||
###DualFilterView: |
||||
Shows the two images given to the CVVisual function and _Result Image_ inbetween |
||||
which represents the result of a filter that was applied to the others via the `Filter selection` collapsable, |
||||
like a difference image between the two. |
||||
|
||||
###SingleFilterView: |
||||
Allows to apply filters to the images it shows via the `Select a filter` collapsable. |
||||
|
||||
##Match Views: |
||||
Associated with the `debugDMatch()` function. |
||||
|
||||
###PointMatchView: |
||||
Interprets the translation of matches as depth value. |
||||
|
||||
###LineMatchView: |
||||
Connects matching key points in the images with lines. |
||||
|
||||
###Rawview: |
||||
Shows in a table data of the matches. |
||||
The table entries can be filtered, sorted and grouped by using commands from CVVisual's [filter query language](filterquery-ref.html) in the text box. |
||||
|
||||
###TranslationMatchView |
||||
Shows the distance between a keypoint in one image to its match in the other as an arrow or line in one image. |
@ -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)), ¤tIndexChanged, |
||||
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); |
||||
} |
||||
} |
||||
|
||||
} |
||||
} |