From bfb5db886b5243e630254dcd252417c7d4046087 Mon Sep 17 00:00:00 2001
From: asantamaria <asantamaria@iri.upc.edu>
Date: Mon, 25 Sep 2017 15:16:22 +0200
Subject: [PATCH] added google tests structure

---
 .cproject                                     |  16 +-
 CMakeLists.txt                                |  38 ++++-
 src/CMakeLists.txt                            |  31 +++-
 .../activesearch/alg_activesearch.cpp         |   8 +-
 src/test/CMakeLists.txt                       |  27 ++++
 src/test/Test_ORB.png                         | Bin 0 -> 10936 bytes
 src/test/data/Test_ORB.png                    | Bin 0 -> 10936 bytes
 src/test/data/roi_orb.yaml                    |  10 ++
 src/test/gtest/CMakeLists.txt                 |  64 ++++++++
 src/test/gtest_example.cpp                    |  20 +++
 src/test/gtest_roi_ORB.cpp                    | 141 ++++++++++++++++++
 src/test/gtest_vision_utils.cpp               |  34 +++++
 src/test/utils_gtest.h                        | 132 ++++++++++++++++
 src/vision_utils.cpp                          |  26 ++++
 src/vision_utils.h                            |  24 +--
 15 files changed, 543 insertions(+), 28 deletions(-)
 create mode 100644 src/test/CMakeLists.txt
 create mode 100644 src/test/Test_ORB.png
 create mode 100644 src/test/data/Test_ORB.png
 create mode 100644 src/test/data/roi_orb.yaml
 create mode 100644 src/test/gtest/CMakeLists.txt
 create mode 100644 src/test/gtest_example.cpp
 create mode 100644 src/test/gtest_roi_ORB.cpp
 create mode 100644 src/test/gtest_vision_utils.cpp
 create mode 100644 src/test/utils_gtest.h

diff --git a/.cproject b/.cproject
index de76406..222a359 100644
--- a/.cproject
+++ b/.cproject
@@ -22,8 +22,8 @@
 							<tool id="cdt.managedbuild.tool.gnu.archiver.base.599567389" name="GCC Archiver" superClass="cdt.managedbuild.tool.gnu.archiver.base"/>
 							<tool id="cdt.managedbuild.tool.gnu.cpp.compiler.base.1572359948" name="GCC C++ Compiler" superClass="cdt.managedbuild.tool.gnu.cpp.compiler.base">
 								<option id="gnu.cpp.compiler.option.include.paths.1770487778" name="Include paths (-I)" superClass="gnu.cpp.compiler.option.include.paths" useByScannerDiscovery="false" valueType="includePath">
-									<listOptionValue builtIn="false" value="&quot;${workspace_loc:/vision_utils/src}&quot;"/>
 									<listOptionValue builtIn="false" value="/usr/local/include"/>
+									<listOptionValue builtIn="false" value="&quot;${workspace_loc:/Vision Utils/src}&quot;"/>
 								</option>
 								<inputType id="cdt.managedbuild.tool.gnu.cpp.compiler.input.466329004" superClass="cdt.managedbuild.tool.gnu.cpp.compiler.input"/>
 							</tool>
@@ -36,7 +36,7 @@
 							</tool>
 							<tool id="cdt.managedbuild.tool.gnu.c.linker.base.1546364764" name="GCC C Linker" superClass="cdt.managedbuild.tool.gnu.c.linker.base"/>
 							<tool id="cdt.managedbuild.tool.gnu.cpp.linker.base.674742229" name="GCC C++ Linker" superClass="cdt.managedbuild.tool.gnu.cpp.linker.base">
-								<option id="gnu.cpp.link.option.paths.1264112011" superClass="gnu.cpp.link.option.paths" valueType="libPaths">
+								<option id="gnu.cpp.link.option.paths.1264112011" name="Library search path (-L)" superClass="gnu.cpp.link.option.paths" valueType="libPaths">
 									<listOptionValue builtIn="false" value="/usr/local/lib"/>
 								</option>
 								<inputType id="cdt.managedbuild.tool.gnu.cpp.linker.input.224259993" superClass="cdt.managedbuild.tool.gnu.cpp.linker.input">
@@ -70,18 +70,18 @@
 	<storageModule moduleId="org.eclipse.cdt.make.core.buildtargets"/>
 	<storageModule moduleId="scannerConfiguration">
 		<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
-		<scannerConfigBuildInfo instanceId="cdt.managedbuild.toolchain.gnu.base.1736073220;cdt.managedbuild.toolchain.gnu.base.1736073220.1390770886;cdt.managedbuild.tool.gnu.c.compiler.base.1459107576;cdt.managedbuild.tool.gnu.c.compiler.input.1695301683">
-			<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
-		</scannerConfigBuildInfo>
 		<scannerConfigBuildInfo instanceId="cdt.managedbuild.toolchain.gnu.base.1819918851;cdt.managedbuild.toolchain.gnu.base.1819918851.1318867162;cdt.managedbuild.tool.gnu.cpp.compiler.base.1649772065;cdt.managedbuild.tool.gnu.cpp.compiler.input.146329834">
 			<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
 		</scannerConfigBuildInfo>
-		<scannerConfigBuildInfo instanceId="cdt.managedbuild.toolchain.gnu.base.207681558;cdt.managedbuild.toolchain.gnu.base.207681558.1208625085;cdt.managedbuild.tool.gnu.c.compiler.base.1439246049;cdt.managedbuild.tool.gnu.c.compiler.input.637694474">
+		<scannerConfigBuildInfo instanceId="cdt.managedbuild.toolchain.gnu.base.1736073220;cdt.managedbuild.toolchain.gnu.base.1736073220.1390770886;cdt.managedbuild.tool.gnu.c.compiler.base.1459107576;cdt.managedbuild.tool.gnu.c.compiler.input.1695301683">
 			<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
 		</scannerConfigBuildInfo>
 		<scannerConfigBuildInfo instanceId="cdt.managedbuild.toolchain.gnu.base.727026548;cdt.managedbuild.toolchain.gnu.base.727026548.1071101131;cdt.managedbuild.tool.gnu.c.compiler.base.61708613;cdt.managedbuild.tool.gnu.c.compiler.input.166078074">
 			<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
 		</scannerConfigBuildInfo>
+		<scannerConfigBuildInfo instanceId="cdt.managedbuild.toolchain.gnu.base.207681558;cdt.managedbuild.toolchain.gnu.base.207681558.1208625085;cdt.managedbuild.tool.gnu.c.compiler.base.1439246049;cdt.managedbuild.tool.gnu.c.compiler.input.637694474">
+			<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
+		</scannerConfigBuildInfo>
 		<scannerConfigBuildInfo instanceId="cdt.managedbuild.toolchain.gnu.base.1244102678;cdt.managedbuild.toolchain.gnu.base.1244102678.1450977940;cdt.managedbuild.tool.gnu.cpp.compiler.base.1572359948;cdt.managedbuild.tool.gnu.cpp.compiler.input.466329004">
 			<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
 		</scannerConfigBuildInfo>
@@ -91,13 +91,13 @@
 		<scannerConfigBuildInfo instanceId="cdt.managedbuild.toolchain.gnu.base.727026548;cdt.managedbuild.toolchain.gnu.base.727026548.1071101131;cdt.managedbuild.tool.gnu.cpp.compiler.base.433122623;cdt.managedbuild.tool.gnu.cpp.compiler.input.341288586">
 			<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
 		</scannerConfigBuildInfo>
-		<scannerConfigBuildInfo instanceId="cdt.managedbuild.toolchain.gnu.base.1244102678;cdt.managedbuild.toolchain.gnu.base.1244102678.1450977940;cdt.managedbuild.tool.gnu.c.compiler.base.1838202852;cdt.managedbuild.tool.gnu.c.compiler.input.964584931">
+		<scannerConfigBuildInfo instanceId="cdt.managedbuild.toolchain.gnu.base.1736073220;cdt.managedbuild.toolchain.gnu.base.1736073220.1390770886;cdt.managedbuild.tool.gnu.cpp.compiler.base.1526891046;cdt.managedbuild.tool.gnu.cpp.compiler.input.1116382079">
 			<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
 		</scannerConfigBuildInfo>
 		<scannerConfigBuildInfo instanceId="cdt.managedbuild.toolchain.gnu.base.1819918851;cdt.managedbuild.toolchain.gnu.base.1819918851.1318867162;cdt.managedbuild.tool.gnu.c.compiler.base.593702953;cdt.managedbuild.tool.gnu.c.compiler.input.392834553">
 			<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
 		</scannerConfigBuildInfo>
-		<scannerConfigBuildInfo instanceId="cdt.managedbuild.toolchain.gnu.base.1736073220;cdt.managedbuild.toolchain.gnu.base.1736073220.1390770886;cdt.managedbuild.tool.gnu.cpp.compiler.base.1526891046;cdt.managedbuild.tool.gnu.cpp.compiler.input.1116382079">
+		<scannerConfigBuildInfo instanceId="cdt.managedbuild.toolchain.gnu.base.1244102678;cdt.managedbuild.toolchain.gnu.base.1244102678.1450977940;cdt.managedbuild.tool.gnu.c.compiler.base.1838202852;cdt.managedbuild.tool.gnu.c.compiler.input.964584931">
 			<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
 		</scannerConfigBuildInfo>
 	</storageModule>
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 93fe002..59f82f0 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,5 +1,10 @@
 # Pre-requisites about cmake itself
-CMAKE_MINIMUM_REQUIRED(VERSION 2.4)
+CMAKE_MINIMUM_REQUIRED(VERSION 2.6)
+
+## OPTIONS ##
+OPTION(BUILD_TESTS "Build Unit tests" ON)
+OPTION(BUILD_EXAMPLES "Build examples" ON)
+OPTION(PRINT_INFO_VU "Print vision utils information" OFF)
 
 if(COMMAND cmake_policy)
   cmake_policy(SET CMP0005 NEW) 
@@ -15,10 +20,14 @@ SET(LIBRARY_OUTPUT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/lib)
 SET(CMAKE_INSTALL_PREFIX /usr/local)
 
 IF (NOT CMAKE_BUILD_TYPE)
- SET(CMAKE_BUILD_TYPE "RELEASE") 
+ SET(CMAKE_BUILD_TYPE "DEBUG") 
 ENDIF (NOT CMAKE_BUILD_TYPE)
 MESSAGE(STATUS "Compilation type: ${CMAKE_BUILD_TYPE}")
  
+#Set Flags
+SET(CMAKE_CXX_FLAGS_DEBUG "-g -Wall -D_REENTRANT")
+SET(CMAKE_CXX_FLAGS_RELEASE "-O3 -D_REENTRANT") 
+ 
 if(UNIX)
   # GCC is not strict enough by default, so enable most of the warnings.
   set(CMAKE_CXX_FLAGS
@@ -39,6 +48,16 @@ else()
         message(STATUS "The compiler ${CMAKE_CXX_COMPILER} has no C++11 support. Please use a different C++ compiler.")
 endif()
 
+## Tests ##
+if(BUILD_TESTS)
+    MESSAGE("Building tests.")
+    # Enables testing for this directory and below.
+    # Note that ctest expects to find a test file in the build directory root.
+    # Therefore, this command should be in the source directory root.
+    #include(CTest) # according to http://public.kitware.com/pipermail/cmake/2012-June/050853.html
+    enable_testing()
+endif()
+
 ADD_SUBDIRECTORY(src)
 
 FIND_PACKAGE(Doxygen)
@@ -76,4 +95,19 @@ ELSE(UNIX)
   )
 ENDIF(UNIX)
 
+IF (UNIX)
+  SET(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-dev-${CPACK_PACKAGE_VERSION}${CPACK_DEBIAN_PACKAGE_ARCHITECTURE}")
+  SET(CPACK_PACKAGE_NAME "${PROJECT_NAME}-dev")
+  SET(CPACK_PACKAGE_DESCRIPTION_SUMMARY "...Enter something here...")
+  SET(CPACK_PACKAGING_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX})
+  SET(CPACK_GENERATOR "DEB")
+  SET(CPACK_DEBIAN_PACKAGE_MAINTAINER "asantamaria@iri.upc.edu")
+  SET(CPACK_SET_DESTDIR "ON")  # Necessary because of the absolute install paths
+  INCLUDE(CPack)
+ELSE(UNIX)
+  ADD_CUSTOM_COMMAND(
+    COMMENT "packaging only implemented in unix"
+    TARGET  uninstall)
+ENDIF(UNIX)
+
 
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index ceae6cc..0167371 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -1,5 +1,19 @@
-# Specific definitions
-#ADD_DEFINITIONS(-DPRINT_INFO_VU)
+#Start Vision Utils build
+MESSAGE("Starting Vision Utils CMakeLists ...")
+CMAKE_MINIMUM_REQUIRED(VERSION 2.8)
+
+IF(BUILD_EXAMPLES OR BUILD_TESTS)
+  SET(_VU_ROOT_DIR ${CMAKE_SOURCE_DIR})
+ENDIF(BUILD_EXAMPLES OR BUILD_TESTS)
+
+IF(PRINT_INFO_VU)
+  ADD_DEFINITIONS(-DPRINT_INFO_VU)
+ENDIF(PRINT_INFO_VU)  
+
+IF((CMAKE_BUILD_TYPE MATCHES DEBUG) OR (CMAKE_BUILD_TYPE MATCHES debug) OR (CMAKE_BUILD_TYPE MATCHES Debug))
+  SET(_VU_DEBUG true)
+  ADD_DEFINITIONS(-D_VU_DEBUG)
+ENDIF()
 
 # library source files
 SET(sources
@@ -277,7 +291,14 @@ INSTALL(FILES ${headers_alg_activesearch} DESTINATION include/${PROJECT_NAME}/al
 INSTALL(FILES ../cmake_modules/Find${PROJECT_NAME}.cmake DESTINATION ${CMAKE_ROOT}/Modules/)
 INSTALL(FILES "${VU_CONFIG_DIR}/config.h" DESTINATION include/${PROJECT_NAME}/_internal)
 
-# examples of usage
-ADD_SUBDIRECTORY(examples)
-
+## Tests ##
+if(BUILD_TESTS)
+    MESSAGE("Building tests.")
+    ADD_SUBDIRECTORY(test)
+endif()
 
+## Examples ##
+IF(BUILD_EXAMPLES)
+  MESSAGE("Building examples.")
+  ADD_SUBDIRECTORY(examples)
+ENDIF(BUILD_EXAMPLES)
diff --git a/src/algorithms/activesearch/alg_activesearch.cpp b/src/algorithms/activesearch/alg_activesearch.cpp
index 1305f09..b55b0b1 100644
--- a/src/algorithms/activesearch/alg_activesearch.cpp
+++ b/src/algorithms/activesearch/alg_activesearch.cpp
@@ -163,13 +163,13 @@ void AlgorithmACTIVESEARCH::detectNewFeatures(FramePtr& _frame, const DetectorBa
 	       	{
 	       		// Keep best in cell
 	       		KeyPointVector list_keypoints = kps;
-	       		unsigned int index = 0;
+
 	       		// cv::KeyPointsFilter keypoint_filter;
 	       		// keypoint_filter.retainBest(kps,1);
 	       		retainBest(kps,1);
-	       		for(unsigned int ii = 0; ii < list_keypoints.size(); ii++)
-	       			if(list_keypoints[ii].pt == kps[0].pt)
-	       				index = ii;
+
+	       		// Check if point exist in list
+	       		int index = existsIn(kps[0], list_keypoints, 1.0);
 
 	       		if(kps[0].response > params_ptr_->min_response_new_feature)
 	            {
diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt
new file mode 100644
index 0000000..5f9db75
--- /dev/null
+++ b/src/test/CMakeLists.txt
@@ -0,0 +1,27 @@
+# Retrieve googletest from github & compile
+add_subdirectory(gtest)
+
+# Include gtest directory.
+include_directories(${GTEST_INCLUDE_DIRS})
+
+############# USE THIS TEST AS AN EXAMPLE #################
+#                                                         #
+# Create a specific test executable for gtest_example     #
+vu_add_gtest(gtest_example gtest_example.cpp)           #
+target_link_libraries(gtest_example ${PROJECT_NAME})      #
+#                                                         #
+###########################################################
+
+
+################# ADD YOUR TESTS BELOW ####################
+#                                                         #
+#           ==== IN ALPHABETICAL ORDER! ====              #
+#                                                         #
+
+# ------- First Core classes ----------
+
+# ------- Now Derived classes ----------
+
+# ROI test
+  vu_add_gtest(gtest_roi_ORB gtest_roi_ORB.cpp)
+  target_link_libraries(gtest_roi_ORB ${PROJECT_NAME})
\ No newline at end of file
diff --git a/src/test/Test_ORB.png b/src/test/Test_ORB.png
new file mode 100644
index 0000000000000000000000000000000000000000..016141f5309c1ed34a61d71cfa63b130ea90aa8f
GIT binary patch
literal 10936
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
LSU2yi!_9vK%33fW

literal 0
HcmV?d00001

diff --git a/src/test/data/Test_ORB.png b/src/test/data/Test_ORB.png
new file mode 100644
index 0000000000000000000000000000000000000000..016141f5309c1ed34a61d71cfa63b130ea90aa8f
GIT binary patch
literal 10936
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
LSU2yi!_9vK%33fW

literal 0
HcmV?d00001

diff --git a/src/test/data/roi_orb.yaml b/src/test/data/roi_orb.yaml
new file mode 100644
index 0000000..96331a6
--- /dev/null
+++ b/src/test/data/roi_orb.yaml
@@ -0,0 +1,10 @@
+detector:
+  type: "ORB"         
+  nfeatures: 20
+  scale factor: 1.2
+  nlevels: 8
+  edge threshold: 16   # 16
+  first level: 0 
+  WTA_K: 2            # See: http://docs.opencv.org/trunk/db/d95/classcv_1_1ORB.html#a180ae17d3300cf2c619aa240d9b607e5
+  score type: 0       #enum { kBytes = 32, HARRIS_SCORE=0, FAST_SCORE=1 };
+  patch size: 16     # 31
diff --git a/src/test/gtest/CMakeLists.txt b/src/test/gtest/CMakeLists.txt
new file mode 100644
index 0000000..1e7b400
--- /dev/null
+++ b/src/test/gtest/CMakeLists.txt
@@ -0,0 +1,64 @@
+cmake_minimum_required(VERSION 2.8.8)
+project(gtest_builder C CXX)
+
+# We need thread support
+#find_package(Threads REQUIRED)
+
+# Enable ExternalProject CMake module
+include(ExternalProject)
+
+set(GTEST_FORCE_SHARED_CRT ON)
+set(GTEST_DISABLE_PTHREADS OFF)
+
+# For some reason I need to disable PTHREADS
+# with g++ (Ubuntu 4.9.3-8ubuntu2~14.04) 4.9.3
+# This is a known issue for MinGW :
+# https://github.com/google/shaderc/pull/174
+#if(MINGW)
+    set(GTEST_DISABLE_PTHREADS ON)
+#endif()
+
+# Download GoogleTest
+ExternalProject_Add(googletest
+    GIT_REPOSITORY https://github.com/google/googletest.git
+    # TIMEOUT 1 # We'll try this
+    CMAKE_ARGS -DCMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG:PATH=DebugLibs
+    -DCMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE:PATH=ReleaseLibs
+    -DCMAKE_CXX_FLAGS=${MSVC_COMPILER_DEFS}
+    -Dgtest_force_shared_crt=${GTEST_FORCE_SHARED_CRT}
+    -Dgtest_disable_pthreads=${GTEST_DISABLE_PTHREADS}
+    -DBUILD_GTEST=ON
+    PREFIX "${CMAKE_CURRENT_BINARY_DIR}"
+    # Disable install step
+    INSTALL_COMMAND ""
+    # UPDATE_DISCONNECTED 1
+)
+
+# Get GTest source and binary directories from CMake project
+
+# Specify include dir
+ExternalProject_Get_Property(googletest source_dir)
+set(GTEST_INCLUDE_DIRS ${source_dir}/googletest/include PARENT_SCOPE)
+
+# Specify MainTest's link libraries
+ExternalProject_Get_Property(googletest binary_dir)
+set(GTEST_LIBS_DIR ${binary_dir}/googlemock/gtest PARENT_SCOPE)
+
+# Create a libgtest target to be used as a dependency by test programs
+add_library(libgtest IMPORTED STATIC GLOBAL)
+add_dependencies(libgtest googletest)
+
+# Set libgtest properties
+set_target_properties(libgtest PROPERTIES
+    "IMPORTED_LOCATION" "${binary_dir}/googlemock/gtest/libgtest.a"
+    "IMPORTED_LINK_INTERFACE_LIBRARIES" "${CMAKE_THREAD_LIBS_INIT}"
+)
+
+function(vu_add_gtest target)
+  add_executable(${target} ${ARGN})
+  add_dependencies(${target} libgtest)
+  target_link_libraries(${target} libgtest)
+
+  #WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/bin
+  add_test(NAME ${target} COMMAND ${target})
+endfunction()
diff --git a/src/test/gtest_example.cpp b/src/test/gtest_example.cpp
new file mode 100644
index 0000000..9c4ae7a
--- /dev/null
+++ b/src/test/gtest_example.cpp
@@ -0,0 +1,20 @@
+#include "utils_gtest.h"
+
+TEST(TestTest, DummyTestExample)
+{
+  EXPECT_FALSE(false);
+
+  ASSERT_TRUE(true);
+
+  int my_int = 5;
+
+  ASSERT_EQ(my_int, 5);
+
+  PRINTF("All good at TestTest::DummyTestExample !\n");
+}
+
+int main(int argc, char **argv)
+{
+  testing::InitGoogleTest(&argc, argv);
+  return RUN_ALL_TESTS();
+}
diff --git a/src/test/gtest_roi_ORB.cpp b/src/test/gtest_roi_ORB.cpp
new file mode 100644
index 0000000..4c9bcd0
--- /dev/null
+++ b/src/test/gtest_roi_ORB.cpp
@@ -0,0 +1,141 @@
+/**
+ * \file gtest_roi_ORB.cpp
+ *
+ *  Created on: Nov 28, 2016
+ *      \author: jsola
+ */
+
+#include "utils_gtest.h"
+
+// vision utils includes
+#include "../vision_utils.h"
+#include "../detectors.h"
+
+// std include
+#include <vector>
+#include <map>
+
+TEST(RoiORB, LoadImageFromFile)
+{
+    cv::Mat image;
+    std::string filename, vu_root;
+    vu_root = _VU_ROOT_DIR;
+    filename = vu_root + "/src/test/data/Test_ORB.png";
+    image = cv::imread(filename, CV_LOAD_IMAGE_GRAYSCALE);
+
+    ASSERT_TRUE(image.data)<< "Failed to load image " << filename << std::endl;
+}
+
+// Test_ORB.png image file has the following interest points that should be detected:
+// 0  [ 65.6 , 100.6 ]
+// 1  [ 164 , 100.6 ]
+// 2  [ 266 , 100.6 ]
+// 3  [ 365.2 , 100.6 ]
+// 4  [ 467.04 , 101.32 ]
+// 5  [ 565.6 , 100.6 ]
+// 6  [ 71.6 , 237.8 ]
+// 7  [ 164.8 , 237.8 ]
+// 8  [ 251.36 , 239.24 ]
+// 9  [ 330.2 , 237.8 ]
+// 10 [ 349 , 270 ]     // point #10 is out of the ROI scanned area and should not be detected
+// 11 [ 584 , 237.8 ]
+// 12 [ 467.2 , 455.4 ]
+// 13 [ 566 , 455.4 ]
+PointVector points_to_check({
+    cv::Point2f(  65.6 , 100.6 ),
+    cv::Point2f( 164   , 100.6 ),
+    cv::Point2f( 266   , 100.6 ),
+    cv::Point2f( 365.2 , 100.6 ),
+    cv::Point2f( 467   , 101.3 ),
+    cv::Point2f( 565.6 , 100.6 ),
+    cv::Point2f(  71.6 , 237.8 ),
+    cv::Point2f( 164.8 , 237.8 ),
+    cv::Point2f( 250   , 239.2 ),
+    cv::Point2f( 330.2 , 237.8 ),
+    cv::Point2f( 349   , 270   ),
+    cv::Point2f( 584   , 237.8 ),
+    cv::Point2f( 467.2 , 455.4 ),
+    cv::Point2f( 566   , 455.4 )
+});
+
+TEST(RoiORB, RoiBounds)
+{
+    cv::Mat image;
+    std::string filename, vu_root;
+    vu_root = _VU_ROOT_DIR;
+    filename = vu_root + "/src/test/data/Test_ORB.png";
+    image = cv::imread(filename, CV_LOAD_IMAGE_GRAYSCALE);
+
+    ASSERT_TRUE(image.data)<< "failed to load image " << filename << std::endl;
+
+    unsigned int img_width = image.cols;
+
+    // Define detector
+    std::string yaml_file_params = vu_root + "/src/test/data/roi_orb.yaml";
+    std::string det_name = vision_utils::readYamlType(yaml_file_params, "detector");
+    vision_utils::DetectorBasePtr det_ptr = vision_utils::setupDetector(det_name, det_name + " detector", yaml_file_params);
+    det_ptr = std::static_pointer_cast<vision_utils::DetectorORB>(det_ptr);
+
+    std::vector<cv::KeyPoint> target_keypoints;
+    cv::KeyPointsFilter keypoint_filter;
+
+    int roi_x;
+    int roi_y;
+    int roi_width = 50;
+    int roi_heigth = 50;
+
+    Eigen::VectorXi roi_center_y(3); roi_center_y << 102 , 250 , 476;
+
+    std::map<int, cv::Point2f> points_found;
+
+    for (int ii = 0; ii<roi_center_y.size() ; ii++)
+    {
+        roi_y = roi_center_y(ii) - roi_width/2;
+
+        for(roi_x = 0; roi_x < img_width; roi_x += 5)
+        {
+            cv::Rect roi(roi_x, roi_y, roi_width, roi_heigth);
+            cv::Rect roi_inflated = roi;
+
+            // Detect features in ROI
+            target_keypoints = det_ptr->detect(image, roi_inflated);
+
+            // Keep only one KP in ROI
+            if (!target_keypoints.empty())
+            {
+                keypoint_filter.retainBest(target_keypoints,1);
+                cv::Point2f pnt = target_keypoints[0].pt;
+
+                int j = vision_utils::existsIn(pnt, points_to_check, 2.0);
+
+                ASSERT_GE(j, 0);
+
+                // append the keypoint to the list of keypoints found
+                if (j >= 0)
+                    points_found[j] = pnt;
+            }
+
+#ifdef _VU_DEBUG
+            cv::Mat image_graphics = image.clone();
+            cv::drawKeypoints(image_graphics,target_keypoints,image_graphics);
+            cv::rectangle(image_graphics, roi, cv::Scalar(255.0, 0.0, 255.0), 1, 8, 0);
+            cv::rectangle(image_graphics, roi_inflated, cv::Scalar(255.0, 255.0, 0.0), 1, 8, 0);
+            cv::imshow("test",image_graphics);
+            cv::waitKey(1);
+#endif
+            }
+    }
+
+    // check that at least all keypoints in the list except #10 have been detected
+    // (note: #10 is out of the ROI)
+    std::vector<int> nn({0,1,2,3,4,5,6,7,8,9,11,12,13});
+    for (int n : nn)
+        ASSERT_TRUE(points_found.count(n));
+    ASSERT_FALSE(points_found.count(10));
+}
+
+int main(int argc, char **argv)
+{
+  testing::InitGoogleTest(&argc, argv);
+  return RUN_ALL_TESTS();
+}
diff --git a/src/test/gtest_vision_utils.cpp b/src/test/gtest_vision_utils.cpp
new file mode 100644
index 0000000..3b8cf3e
--- /dev/null
+++ b/src/test/gtest_vision_utils.cpp
@@ -0,0 +1,34 @@
+#include "utils_gtest.h"
+#include "../vision_utils.h"
+
+TEST(VisionUtils, DummyTestExample)
+{
+  EXPECT_FALSE(false);
+
+  ASSERT_TRUE(true);
+
+  int my_int = 5;
+
+  ASSERT_EQ(my_int, 5);
+
+  PRINTF("All good at TestTest::DummyTestExample !\n");
+}
+
+TEST(VisionUtils, DummyTestExample)
+{
+  EXPECT_FALSE(false);
+
+  ASSERT_TRUE(true);
+
+  int my_int = 5;
+
+  ASSERT_EQ(my_int, 5);
+
+  PRINTF("All good at TestTest::DummyTestExample !\n");
+}
+
+int main(int argc, char **argv)
+{
+  testing::InitGoogleTest(&argc, argv);
+  return RUN_ALL_TESTS();
+}
diff --git a/src/test/utils_gtest.h b/src/test/utils_gtest.h
new file mode 100644
index 0000000..7716bf0
--- /dev/null
+++ b/src/test/utils_gtest.h
@@ -0,0 +1,132 @@
+#ifndef VU_UTILS_GTEST_H
+#define VU_UTILS_GTEST_H
+
+#include <gtest/gtest.h>
+
+// Macros for testing equalities and inequalities.
+//
+//    * {ASSERT|EXPECT}_EQ(expected, actual): Tests that expected == actual
+//    * {ASSERT|EXPECT}_NE(v1, v2):           Tests that v1 != v2
+//    * {ASSERT|EXPECT}_LT(v1, v2):           Tests that v1 < v2
+//    * {ASSERT|EXPECT}_LE(v1, v2):           Tests that v1 <= v2
+//    * {ASSERT|EXPECT}_GT(v1, v2):           Tests that v1 > v2
+//    * {ASSERT|EXPECT}_GE(v1, v2):           Tests that v1 >= v2
+//
+// C String Comparisons.  All tests treat NULL and any non-NULL string
+// as different.  Two NULLs are equal.
+//
+//    * {ASSERT|EXPECT}_STREQ(s1, s2):     Tests that s1 == s2
+//    * {ASSERT|EXPECT}_STRNE(s1, s2):     Tests that s1 != s2
+//    * {ASSERT|EXPECT}_STRCASEEQ(s1, s2): Tests that s1 == s2, ignoring case
+//    * {ASSERT|EXPECT}_STRCASENE(s1, s2): Tests that s1 != s2, ignoring case
+//
+// Macros for comparing floating-point numbers.
+//
+//    * {ASSERT|EXPECT}_FLOAT_EQ(expected, actual):
+//         Tests that two float values are almost equal.
+//    * {ASSERT|EXPECT}_DOUBLE_EQ(expected, actual):
+//         Tests that two double values are almost equal.
+//    * {ASSERT|EXPECT}_NEAR(v1, v2, abs_error):
+//         Tests that v1 and v2 are within the given distance to each other.
+//
+// These predicate format functions work on floating-point values, and
+// can be used in {ASSERT|EXPECT}_PRED_FORMAT2*(), e.g.
+//
+//   EXPECT_PRED_FORMAT2(testing::DoubleLE, Foo(), 5.0);
+//
+// Macros that execute statement and check that it doesn't generate new fatal
+// failures in the current thread.
+//
+//    * {ASSERT|EXPECT}_NO_FATAL_FAILURE(statement);
+
+
+// http://stackoverflow.com/a/29155677
+
+namespace testing
+{
+namespace internal
+{
+enum GTestColor
+{
+  COLOR_DEFAULT,
+  COLOR_RED,
+  COLOR_GREEN,
+  COLOR_YELLOW
+};
+
+extern void ColoredPrintf(GTestColor color, const char* fmt, ...);
+
+#define PRINTF(...) \
+  do { testing::internal::ColoredPrintf(testing::internal::COLOR_GREEN,\
+  "[          ] "); \
+  testing::internal::ColoredPrintf(testing::internal::COLOR_YELLOW, __VA_ARGS__); } \
+  while(0)
+
+// C++ stream interface
+class TestCout : public std::stringstream
+{
+public:
+  ~TestCout()
+  {
+    PRINTF("%s\n", str().c_str());
+  }
+};
+
+/* Usage :
+
+TEST(Test, Foo)
+{
+  // the following works but prints default stream
+  EXPECT_TRUE(false) << "Testing Stream.";
+
+  // or you can play with AINSI color code
+  EXPECT_TRUE(false) << "\033[1;31m" << "Testing Stream.";
+
+  // or use the above defined macros
+
+  PRINTF("Hello world");
+
+  // or
+
+  TEST_COUT << "Hello world";
+}
+
+*/
+#define TEST_COUT testing::internal::TestCout()
+
+} // namespace internal
+
+/* Macros related to testing Eigen classes:
+ */
+#define EXPECT_MATRIX_APPROX(C_expect, C_actual, precision) EXPECT_PRED2([](const Eigen::MatrixXs lhs, const Eigen::MatrixXs rhs) { \
+                  return (lhs - rhs).isMuchSmallerThan(1, precision); \
+               }, \
+               C_expect, C_actual);
+
+#define ASSERT_MATRIX_APPROX(C_expect, C_actual, precision) ASSERT_PRED2([](const Eigen::MatrixXs lhs, const Eigen::MatrixXs rhs) { \
+                  return (lhs - rhs).isMuchSmallerThan(1, precision); \
+               }, \
+               C_expect, C_actual);
+
+#define EXPECT_QUATERNION_APPROX(C_expect, C_actual, precision) EXPECT_MATRIX_APPROX((C_expect).coeffs(), (C_actual).coeffs(), precision)
+
+#define ASSERT_QUATERNION_APPROX(C_expect, C_actual, precision) ASSERT_MATRIX_APPROX((C_expect).coeffs(), (C_actual).coeffs(), precision)
+
+#define EXPECT_POSE2D_APPROX(C_expect, C_actual, precision) EXPECT_PRED2([](const Eigen::MatrixXs lhs, const Eigen::MatrixXs rhs) { \
+                   MatrixXs er = lhs - rhs; \
+                   er(2) = pi2pi((Scalar)er(2)); \
+                   return er.isMuchSmallerThan(1, precision); \
+               }, \
+               C_expect, C_actual);
+
+#define ASSERT_POSE2D_APPROX(C_expect, C_actual, precision) EXPECT_PRED2([](const Eigen::MatrixXs lhs, const Eigen::MatrixXs rhs) { \
+                   MatrixXs er = lhs - rhs; \
+                   er(2) = pi2pi((Scalar)er(2)); \
+                   return er.isMuchSmallerThan(1, precision); \
+               }, \
+               C_expect, C_actual);
+
+} // namespace testing
+
+
+#endif /* VU_UTILS_GTEST_H */
diff --git a/src/vision_utils.cpp b/src/vision_utils.cpp
index 2b68a93..6cb1fd3 100644
--- a/src/vision_utils.cpp
+++ b/src/vision_utils.cpp
@@ -151,6 +151,32 @@ void whoHasMoved(const PointVector& _pt_vec_1, const PointVector& _pt_vec_2, Poi
     }
 }
 
+int existsIn(const cv::Point2f& p, const PointVector p_vec, const double& pixel_tol)
+{
+	double pixel_tol_squared = pixel_tol*pixel_tol;
+    for (int ii = 0; ii < p_vec.size(); ii++)
+    {
+    	double dx = p.x - p_vec[ii].x;
+    	double dy = p.y - p_vec[ii].y;
+        if ( dx*dx + dy*dy < pixel_tol_squared) // match based on Euclidean distance
+            return ii;
+    }
+    return -1; // -1 marks 'not found'
+}
+
+int existsIn(const cv::KeyPoint& p, const KeyPointVector p_vec, const double& pixel_tol)
+{
+	double pixel_tol_squared = pixel_tol*pixel_tol;
+    for (int ii = 0; ii < p_vec.size(); ii++)
+    {
+    	double dx = p.pt.x - p_vec[ii].pt.x;
+    	double dy = p.pt.y - p_vec[ii].pt.y;
+        if ( dx*dx + dy*dy < pixel_tol_squared) // match based on Euclidean distance
+            return ii;
+    }
+    return -1; // -1 marks 'not found'
+}
+
 void drawKeyPoints(cv::Mat& _image, const KeyPointVector& _kp_vec, const int& _radius, const cv::Scalar& _color, const int& _thickness)
 {
     for (auto kp : _kp_vec)
diff --git a/src/vision_utils.h b/src/vision_utils.h
index 9f10fc4..d9e883d 100644
--- a/src/vision_utils.h
+++ b/src/vision_utils.h
@@ -24,17 +24,20 @@
 #include <eigen3/Eigen/Sparse>
 
 // OpenCV
-#include <opencv2/core/core.hpp>
-#include <opencv2/core/types.hpp>
+#include <opencv2/opencv.hpp>
 #include <opencv2/core/eigen.hpp>
-#include <opencv2/imgproc/imgproc.hpp>
-#include <opencv2/highgui/highgui.hpp>
-#include <opencv2/features2d/features2d.hpp>
-#include <opencv2/features2d.hpp>
-#include <opencv2/xfeatures2d/nonfree.hpp>
+#include <opencv2/line_descriptor.hpp>
 #include <opencv2/xfeatures2d.hpp>
-#include <opencv2/line_descriptor/descriptor.hpp>
-#include <opencv2/calib3d.hpp>
+
+// REMOVE
+//#include <opencv2/core.hpp>
+//#include <opencv2/core/types.hpp>
+//#include <opencv2/imgproc/imgproc.hpp>
+//#include <opencv2/highgui/highgui.hpp>
+//#include <opencv2/features2d/features2d.hpp>
+//#include <opencv2/features2d.hpp>
+//#include <opencv2/xfeatures2d/nonfree.hpp>
+//#include <opencv2/calib3d.hpp>
 
 typedef std::vector<cv::Point2f> PointVector;
 typedef std::vector<cv::KeyPoint> KeyPointVector;
@@ -153,6 +156,9 @@ PointVector KPToP(const KeyPointVector& _kp_vec);
 void whoHasMoved(const KeyPointVector& _kpts1, const KeyPointVector& _kpts2, KeyPointVector& _common_kpts, KeyPointVector& _new_in_kpts2);
 void whoHasMoved(const PointVector& _pts1, const PointVector& _pts2, PointVector& _common_pts, PointVector& _new_in_pts2);
 
+int existsIn(const cv::Point2f& p, const PointVector p_vec, const double& pixel_tol);
+int existsIn(const cv::KeyPoint& p, const KeyPointVector p_vec, const double& pixel_tol);
+
 void drawKeyPoints(cv::Mat& _image, const KeyPointVector& _kp_vec, const int& _radius=5, const cv::Scalar& _color = cv::Scalar(0, 0, 255), const int& _thickness=-1);
 
 void drawKeyLines(cv::Mat& _image, const KeyLineVector& _kl_vec, const cv::Scalar& _color = cv::Scalar(128, 128, 255) );
-- 
GitLab