From f997578184eccb4077c3191e8ca5cad8aba014d3 Mon Sep 17 00:00:00 2001
From: jean-michel-gonet <jmgonet@yahoo.com>
Date: Sun, 20 Jan 2019 17:28:07 +0100
Subject: [PATCH 01/18] Building a service locator - WIP

---
 src/app/CMakeLists.txt          |  1 +
 src/app/cpp/main.cpp            | 14 ++-------
 src/app/cpp/service-locator.cpp | 51 +++++++++++++++++++++++++++++++++
 src/app/hpp/service-locator.hpp | 25 ++++++++++++++++
 4 files changed, 79 insertions(+), 12 deletions(-)
 create mode 100644 src/app/cpp/service-locator.cpp
 create mode 100644 src/app/hpp/service-locator.hpp

diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt
index 72c7adc..7c38cc7 100644
--- a/src/app/CMakeLists.txt
+++ b/src/app/CMakeLists.txt
@@ -16,6 +16,7 @@ add_executable(rascapp
     cpp/main.cpp
     cpp/main-window.cpp
     cpp/camera-drawing-area.cpp
+    cpp/service-locator.cpp
 )
 
 # Add folder with all headers:
diff --git a/src/app/cpp/main.cpp b/src/app/cpp/main.cpp
index 99b1a4d..190b298 100644
--- a/src/app/cpp/main.cpp
+++ b/src/app/cpp/main.cpp
@@ -1,19 +1,9 @@
 #include "main-window.hpp"
 
-#include "capture-image-from-camera.hpp"
-#include "event-bus.hpp"
-#include "movie-maker.hpp"
-#include "orange-ball-detector.hpp"
-#include "system-helper.hpp"
+#include "service-locator.hpp"
 
-int main (int argc, char *argv[]) {
-    CaptureImageFromCamera captureImageFromCamera;
-    OrangeBallDetector orangeBallDetector;
-    MovieMaker movieMaker(obtainPathToDesktopFolder().append("/live.avi"), 20.0);
 
-    EventBus<EventImageCaptured> eventImageBus;
-    eventImageBus.subscribe(&orangeBallDetector);
-    eventImageBus.subscribe(&movieMaker);
+int main (int argc, char *argv[]) {
 
     auto app = Gtk::Application::create(
             argc, 
diff --git a/src/app/cpp/service-locator.cpp b/src/app/cpp/service-locator.cpp
new file mode 100644
index 0000000..1e9ccbf
--- /dev/null
+++ b/src/app/cpp/service-locator.cpp
@@ -0,0 +1,51 @@
+#include "service-locator.hpp"
+
+#include "system-helper.hpp"
+
+ServiceLocator * ServiceLocator::serviceLocator = nullptr;
+
+MovieMaker * ServiceLocator::movieMaker = nullptr;
+
+CaptureImageFromCamera * ServiceLocator::captureImageFromCamera = nullptr;
+
+OrangeBallDetector * ServiceLocator::orangeBallDetector = nullptr;
+
+void ServiceLocator::startUp() {
+    serviceLocator = new ServiceLocator();
+}
+
+ServiceLocator::ServiceLocator() {
+    CaptureImageFromCamera captureImageFromCamera;
+    OrangeBallDetector orangeBallDetector;
+    MovieMaker movieMaker(obtainPathToDesktopFolder().append("/live.avi"), 20.0);
+
+    EventBus<EventImageCaptured> eventImageBus;
+    eventImageBus.subscribe(&orangeBallDetector);
+    eventImageBus.subscribe(&movieMaker);
+}
+
+ServiceLocator::~ServiceLocator() {
+
+}
+
+MovieMaker * ServiceLocator::locateMovieMaker() {
+    if (!movieMaker) {
+        movieMaker = buildMovieMaker();
+    }
+    return movieMaker;
+}
+MovieMaker * ServiceLocator::buildMovieMaker() {
+}
+CaptureImageFromCamera * ServiceLocator::locateCaptureImageFromCamera() {
+    if (!captureImageFromCamera) {
+        captureImageFromCamera = buildCaptureImageFromCamera();
+    }
+    return captureImageFromCamera;
+}
+
+OrangeBallDetector * ServiceLocator::locateOrangeBallDetector() {
+    if (!orangeBallDetector) {
+        orangeBallDetector = buildOrangeBallDetector();
+    }
+    return orangeBallDetector;
+}
diff --git a/src/app/hpp/service-locator.hpp b/src/app/hpp/service-locator.hpp
new file mode 100644
index 0000000..53d5819
--- /dev/null
+++ b/src/app/hpp/service-locator.hpp
@@ -0,0 +1,25 @@
+#ifndef SERVICE_LOCATOR_HPP
+#define SERVICE_LOCATOR_HPP
+
+#include "movie-maker.hpp"
+#include "capture-image-from-camera.hpp"
+#include "orange-ball-detector.hpp"
+
+class ServiceLocator {
+public:
+    ServiceLocator();
+    virtual ~ServiceLocator();
+    void startUp();
+    static ServiceLocator * obtainInstance();
+    MovieMaker * locateMovieMaker();
+    CaptureImageFromCamera * locateCaptureImageFromCamera();
+    OrangeBallDetector * locateOrangeBallDetector();
+    
+private:
+    static ServiceLocator * serviceLocator;
+    static MovieMaker * movieMaker;
+    static CaptureImageFromCamera * captureImageFromCamera;
+    static OrangeBallDetector * orangeBallDetector;
+}
+
+#endif

From 9965e2af81171c66b2f8da243d5fa565c9bd2c50 Mon Sep 17 00:00:00 2001
From: jean-michel-gonet <jmgonet@yahoo.com>
Date: Mon, 21 Jan 2019 07:37:18 +0100
Subject: [PATCH 02/18] Building a Service Locator

---
 src/app/cpp/main.cpp            |  2 ++
 src/app/cpp/service-locator.cpp | 43 +++++++++++++--------------------
 src/app/hpp/service-locator.hpp | 12 +++++----
 3 files changed, 26 insertions(+), 31 deletions(-)

diff --git a/src/app/cpp/main.cpp b/src/app/cpp/main.cpp
index 190b298..b800cfb 100644
--- a/src/app/cpp/main.cpp
+++ b/src/app/cpp/main.cpp
@@ -5,6 +5,8 @@
 
 int main (int argc, char *argv[]) {
 
+    ServiceLocator::startUp();
+    
     auto app = Gtk::Application::create(
             argc, 
             argv, 
diff --git a/src/app/cpp/service-locator.cpp b/src/app/cpp/service-locator.cpp
index 1e9ccbf..671853c 100644
--- a/src/app/cpp/service-locator.cpp
+++ b/src/app/cpp/service-locator.cpp
@@ -4,48 +4,39 @@
 
 ServiceLocator * ServiceLocator::serviceLocator = nullptr;
 
-MovieMaker * ServiceLocator::movieMaker = nullptr;
-
-CaptureImageFromCamera * ServiceLocator::captureImageFromCamera = nullptr;
-
-OrangeBallDetector * ServiceLocator::orangeBallDetector = nullptr;
-
 void ServiceLocator::startUp() {
     serviceLocator = new ServiceLocator();
 }
 
-ServiceLocator::ServiceLocator() {
-    CaptureImageFromCamera captureImageFromCamera;
-    OrangeBallDetector orangeBallDetector;
-    MovieMaker movieMaker(obtainPathToDesktopFolder().append("/live.avi"), 20.0);
+ServiceLocator * ServiceLocator::obtainInstance() {
+    if (!serviceLocator) {
+        startUp();
+    }
+    return serviceLocator;
+}
 
-    EventBus<EventImageCaptured> eventImageBus;
+ServiceLocator::ServiceLocator():
+movieMaker(obtainPathToDesktopFolder().append("/live.avi"), 20.0),
+captureImageFromCamera(),
+orangeBallDetector(),
+eventImageBus() {
     eventImageBus.subscribe(&orangeBallDetector);
     eventImageBus.subscribe(&movieMaker);
 }
 
 ServiceLocator::~ServiceLocator() {
-
+    eventImageBus.unsubscribe(&orangeBallDetector);
+    eventImageBus.unsubscribe(&movieMaker);
 }
 
 MovieMaker * ServiceLocator::locateMovieMaker() {
-    if (!movieMaker) {
-        movieMaker = buildMovieMaker();
-    }
-    return movieMaker;
-}
-MovieMaker * ServiceLocator::buildMovieMaker() {
+    return &movieMaker;
 }
+
 CaptureImageFromCamera * ServiceLocator::locateCaptureImageFromCamera() {
-    if (!captureImageFromCamera) {
-        captureImageFromCamera = buildCaptureImageFromCamera();
-    }
-    return captureImageFromCamera;
+    return &captureImageFromCamera;
 }
 
 OrangeBallDetector * ServiceLocator::locateOrangeBallDetector() {
-    if (!orangeBallDetector) {
-        orangeBallDetector = buildOrangeBallDetector();
-    }
-    return orangeBallDetector;
+    return &orangeBallDetector;
 }
diff --git a/src/app/hpp/service-locator.hpp b/src/app/hpp/service-locator.hpp
index 53d5819..50c8122 100644
--- a/src/app/hpp/service-locator.hpp
+++ b/src/app/hpp/service-locator.hpp
@@ -9,7 +9,7 @@ class ServiceLocator {
 public:
     ServiceLocator();
     virtual ~ServiceLocator();
-    void startUp();
+    static void startUp();
     static ServiceLocator * obtainInstance();
     MovieMaker * locateMovieMaker();
     CaptureImageFromCamera * locateCaptureImageFromCamera();
@@ -17,9 +17,11 @@ class ServiceLocator {
     
 private:
     static ServiceLocator * serviceLocator;
-    static MovieMaker * movieMaker;
-    static CaptureImageFromCamera * captureImageFromCamera;
-    static OrangeBallDetector * orangeBallDetector;
-}
+    
+    MovieMaker movieMaker;
+    CaptureImageFromCamera captureImageFromCamera;
+    OrangeBallDetector orangeBallDetector;
+    EventBus<EventImageCaptured> eventImageBus;
+};
 
 #endif

From ec4788ba3f0576f398a1293ce3ea322806ab6710 Mon Sep 17 00:00:00 2001
From: jean-michel-gonet <jmgonet@yahoo.com>
Date: Mon, 21 Jan 2019 17:53:58 +0100
Subject: [PATCH 03/18] Use a service locator to start up the application
 context

---
 src/app/cpp/service-locator.cpp | 24 ++++++++++--------------
 src/app/hpp/service-locator.hpp | 22 ++++++++++++++--------
 2 files changed, 24 insertions(+), 22 deletions(-)

diff --git a/src/app/cpp/service-locator.cpp b/src/app/cpp/service-locator.cpp
index 671853c..c46382b 100644
--- a/src/app/cpp/service-locator.cpp
+++ b/src/app/cpp/service-locator.cpp
@@ -2,17 +2,13 @@
 
 #include "system-helper.hpp"
 
-ServiceLocator * ServiceLocator::serviceLocator = nullptr;
-
 void ServiceLocator::startUp() {
-    serviceLocator = new ServiceLocator();
+    getInstance();
 }
 
-ServiceLocator * ServiceLocator::obtainInstance() {
-    if (!serviceLocator) {
-        startUp();
-    }
-    return serviceLocator;
+ServiceLocator * ServiceLocator::getInstance() {
+    static ServiceLocator serviceLocator;
+    return &serviceLocator;
 }
 
 ServiceLocator::ServiceLocator():
@@ -29,14 +25,14 @@ ServiceLocator::~ServiceLocator() {
     eventImageBus.unsubscribe(&movieMaker);
 }
 
-MovieMaker * ServiceLocator::locateMovieMaker() {
-    return &movieMaker;
+MovieMaker * ServiceLocator::getMovieMaker() {
+    return & getInstance()->movieMaker;
 }
 
-CaptureImageFromCamera * ServiceLocator::locateCaptureImageFromCamera() {
-    return &captureImageFromCamera;
+CaptureImageFromCamera * ServiceLocator::getCaptureImageFromCamera() {
+    return & getInstance()->captureImageFromCamera;
 }
 
-OrangeBallDetector * ServiceLocator::locateOrangeBallDetector() {
-    return &orangeBallDetector;
+OrangeBallDetector * ServiceLocator::getOrangeBallDetector() {
+    return & getInstance()->orangeBallDetector;
 }
diff --git a/src/app/hpp/service-locator.hpp b/src/app/hpp/service-locator.hpp
index 50c8122..0b7db0b 100644
--- a/src/app/hpp/service-locator.hpp
+++ b/src/app/hpp/service-locator.hpp
@@ -7,21 +7,27 @@
 
 class ServiceLocator {
 public:
-    ServiceLocator();
     virtual ~ServiceLocator();
+
     static void startUp();
-    static ServiceLocator * obtainInstance();
-    MovieMaker * locateMovieMaker();
-    CaptureImageFromCamera * locateCaptureImageFromCamera();
-    OrangeBallDetector * locateOrangeBallDetector();
-    
+    static ServiceLocator * getInstance();
+    static MovieMaker * getMovieMaker();
+    static CaptureImageFromCamera * getCaptureImageFromCamera();
+    static OrangeBallDetector * getOrangeBallDetector();
+
+    // Don't copy a singleton.
+    ServiceLocator(const ServiceLocator&) = delete;
+    // Don't copy a singleton.
+    ServiceLocator& operator= (const ServiceLocator&) = delete;
+
 private:
-    static ServiceLocator * serviceLocator;
-    
     MovieMaker movieMaker;
     CaptureImageFromCamera captureImageFromCamera;
     OrangeBallDetector orangeBallDetector;
     EventBus<EventImageCaptured> eventImageBus;
+    
+    // Don't instantiate a singleton.
+    ServiceLocator();
 };
 
 #endif

From c917e103742cedd52505ac66cfe995d94c5bceec Mon Sep 17 00:00:00 2001
From: jean-michel-gonet <jmgonet@yahoo.com>
Date: Mon, 21 Jan 2019 17:55:22 +0100
Subject: [PATCH 04/18] Use a service locator to start up the application
 context

---
 src/app/hpp/service-locator.hpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/app/hpp/service-locator.hpp b/src/app/hpp/service-locator.hpp
index 0b7db0b..3eba748 100644
--- a/src/app/hpp/service-locator.hpp
+++ b/src/app/hpp/service-locator.hpp
@@ -16,9 +16,9 @@ class ServiceLocator {
     static OrangeBallDetector * getOrangeBallDetector();
 
     // Don't copy a singleton.
-    ServiceLocator(const ServiceLocator&) = delete;
+    ServiceLocator(const ServiceLocator &) = delete;
     // Don't copy a singleton.
-    ServiceLocator& operator= (const ServiceLocator&) = delete;
+    ServiceLocator& operator= (const ServiceLocator &) = delete;
 
 private:
     MovieMaker movieMaker;

From cc5db69fcac62c8d4c193ce4793aecd209a289b3 Mon Sep 17 00:00:00 2001
From: jean-michel-gonet <jmgonet@yahoo.com>
Date: Wed, 23 Jan 2019 07:29:12 +0100
Subject: [PATCH 05/18] The movie maker can be put to pause

---
 src/lib/cpp/movie-maker.cpp      | 21 +++++++---
 src/lib/hpp/movie-maker.hpp      |  3 ++
 src/lib/tst/movie-maker-test.cpp | 67 +++++++++++++++++++++-----------
 3 files changed, 63 insertions(+), 28 deletions(-)

diff --git a/src/lib/cpp/movie-maker.cpp b/src/lib/cpp/movie-maker.cpp
index 7e8e731..59ea48d 100644
--- a/src/lib/cpp/movie-maker.cpp
+++ b/src/lib/cpp/movie-maker.cpp
@@ -6,6 +6,7 @@ using namespace cv;
 MovieMaker::MovieMaker(string filename, double fps):
 videoWriter(),
 isMovieStarted(false),
+recording(true),
 filename(filename),
 fps(fps) {
     // Nothing to do.
@@ -15,17 +16,27 @@ MovieMaker::~MovieMaker() {
     endMovie();
 }
 
+bool MovieMaker::isRecording() {
+    return recording;
+}
+
+void MovieMaker::toggleRecording() {
+    recording = !recording;
+}
+
 void MovieMaker::receive(EventImageCaptured e) {
     addPhotogram(e.getCapturedImage());
 }
 
 void MovieMaker::addPhotogram(Mat photogram) {
-    if (photogram.size().width > 0) {
-        if (!isMovieStarted) {
-            startMovie(photogram);
-            isMovieStarted = true;
+    if (recording) {
+        if (photogram.size().width > 0) {
+            if (!isMovieStarted) {
+                startMovie(photogram);
+                isMovieStarted = true;
+            }
+            videoWriter.write(photogram);
         }
-        videoWriter.write(photogram);
     }
 }
 
diff --git a/src/lib/hpp/movie-maker.hpp b/src/lib/hpp/movie-maker.hpp
index c30225c..9087715 100644
--- a/src/lib/hpp/movie-maker.hpp
+++ b/src/lib/hpp/movie-maker.hpp
@@ -13,9 +13,12 @@ class MovieMaker: public Subscriptor<EventImageCaptured> {
     void endMovie();
     void addPhotogram(cv::Mat photogram);
     void receive(EventImageCaptured e);
+    void toggleRecording();
+    bool isRecording();
 private:
     cv::VideoWriter videoWriter;
     bool isMovieStarted;
+    bool recording;
     std::string filename;
     double fps;
     
diff --git a/src/lib/tst/movie-maker-test.cpp b/src/lib/tst/movie-maker-test.cpp
index e542b7d..136646c 100644
--- a/src/lib/tst/movie-maker-test.cpp
+++ b/src/lib/tst/movie-maker-test.cpp
@@ -8,41 +8,62 @@
 using namespace std;
 using namespace cv;
 
+int nonEmptyFrames(string movieFileName) {
+    cv::Mat mat;
+    cv::VideoCapture movieReader(movieFileName);
+    int n = 0;
+    while(1) {
+        movieReader.read(mat);
+        if (mat.empty()) {
+            break;
+        }
+        n++;
+        REQUIRE(mat.size().width == 640);
+        REQUIRE(mat.size().height == 400);
+    }
+    return n;
+}
+
 SCENARIO("Can make a movie") {
-    GIVEN( "An initialized movie maker") {
-        string filename("./live.avi");
+    
+    string filename("./live.avi");
+    EventBus<EventImageCaptured> capturedImageEventBus;
+
+    Mat mat1, mat2, mat3;
+    mat1 = imread(string(PATH_TO_TEST_DATA).append("/abc-a.png"));
+    mat2 = imread(string(PATH_TO_TEST_DATA).append("/abc-b.png"));
+    mat3 = imread(string(PATH_TO_TEST_DATA).append("/abc-c.png"));
 
-        EventBus<EventImageCaptured> capturedImageEventBus;
+    GIVEN( "An initialized movie maker") {
         MovieMaker movieMaker(filename, 10.0);
         capturedImageEventBus.subscribe(&movieMaker);
         
         WHEN( "Fed with images") {
-            Mat mat;
-            mat = imread(string(PATH_TO_TEST_DATA).append("/abc-a.png"));
-            capturedImageEventBus.propagate(EventImageCaptured(mat));
-            mat = imread(string(PATH_TO_TEST_DATA).append("/abc-b.png"));
-            capturedImageEventBus.propagate(EventImageCaptured(mat));
-            mat = imread(string(PATH_TO_TEST_DATA).append("/abc-c.png"));
-            capturedImageEventBus.propagate(EventImageCaptured(mat));
+            capturedImageEventBus.propagate(EventImageCaptured(mat1));
+            capturedImageEventBus.propagate(EventImageCaptured(mat2));
+            capturedImageEventBus.propagate(EventImageCaptured(mat3));
 
             movieMaker.endMovie();
             
             THEN ( "Can make a movie") {
-                cv::VideoCapture movieReader(filename);
-                int n = 0;
-                while(1) {
-                    movieReader.read(mat);
-                    if (mat.empty()) {
-                        break;
-                    }
-                    n++;
-                    REQUIRE(mat.size().width == 640);
-                    REQUIRE(mat.size().height == 400);
-                }
-                REQUIRE( n == 3);
+                REQUIRE( nonEmptyFrames(filename) == 3);
+            }
+        }
+        WHEN( "Toggling the recording") {
+            capturedImageEventBus.propagate(EventImageCaptured(mat1));
+            
+            movieMaker.toggleRecording();
+            capturedImageEventBus.propagate(EventImageCaptured(mat2));
+            movieMaker.toggleRecording();
+            
+            capturedImageEventBus.propagate(EventImageCaptured(mat3));
+            
+            movieMaker.endMovie();
+            
+            THEN ( "Skips the images propagated during pause") {
+                REQUIRE( nonEmptyFrames(filename) == 2);
             }
         }
         capturedImageEventBus.unsubscribe(&movieMaker);
     }
 }
-

From 21872d3678f28458bee5af600892ae658ff9c20d Mon Sep 17 00:00:00 2001
From: jean-michel-gonet <jmgonet@yahoo.com>
Date: Wed, 23 Jan 2019 07:41:29 +0100
Subject: [PATCH 06/18] Show a red circle when recording

---
 src/app/cpp/camera-drawing-area.cpp | 22 ++++++++++++++++++++--
 src/app/cpp/main-window.cpp         | 13 +++++++++++--
 src/app/hpp/camera-drawing-area.hpp |  2 ++
 src/app/hpp/main-window.hpp         |  1 +
 4 files changed, 34 insertions(+), 4 deletions(-)

diff --git a/src/app/cpp/camera-drawing-area.cpp b/src/app/cpp/camera-drawing-area.cpp
index 9c5c507..eafb683 100644
--- a/src/app/cpp/camera-drawing-area.cpp
+++ b/src/app/cpp/camera-drawing-area.cpp
@@ -1,10 +1,13 @@
 #include "camera-drawing-area.hpp"
+
+#include "service-locator.hpp"
 #include "system-helper.hpp"
 
 #include <opencv2/imgproc.hpp>
 
 CameraDrawingArea::CameraDrawingArea():
-dispatchInvalidate() {
+dispatchInvalidate(),
+movieMaker(ServiceLocator::getMovieMaker()){
     dispatchInvalidate.
         connect(sigc::mem_fun(*this, 
                     &CameraDrawingArea::doInvalidate));
@@ -44,14 +47,28 @@ bool CameraDrawingArea::on_draw(const Cairo::RefPtr<Cairo::Context>& cr) {
                     output.rows,
                     (int) output.step);
 
-        // Display
+        // Show it all
         Gdk::Cairo::set_source_pixbuf(cr, pixbuf);
         cr->paint();
+
+        // When recording, displays a red circle
+        if (movieMaker->isRecording()) {
+            displayRec(cr);
+        }
     }
 
     return true;
 }
 
+void CameraDrawingArea::displayRec(const Cairo::RefPtr<Cairo::Context>& cr) {
+    double radius = 7;
+    double x = width / 2;
+    double y = height - radius;
+    cr->arc(x, y, radius, 0, 2 * M_PI);
+    cr->fill();
+    cr->stroke();
+}
+
 void CameraDrawingArea::on_size_allocate (Gtk::Allocation& allocation) {
     DrawingArea::on_size_allocate(allocation);
 
@@ -59,3 +76,4 @@ void CameraDrawingArea::on_size_allocate (Gtk::Allocation& allocation) {
     height = allocation.get_height();
 }
 
+
diff --git a/src/app/cpp/main-window.cpp b/src/app/cpp/main-window.cpp
index c82d59f..84644aa 100644
--- a/src/app/cpp/main-window.cpp
+++ b/src/app/cpp/main-window.cpp
@@ -1,12 +1,15 @@
 #include "main-window.hpp"
 
+#include "service-locator.hpp"
+
 #include <iostream>
 
 MainWindow::MainWindow(int width, int height):
 probablyInFullScreen(false),
 m_button("Hello World"),
 m_box(Gtk::ORIENTATION_VERTICAL),
-m_label1("First Label") {
+m_label1("First Label"),
+movieMaker(ServiceLocator::getMovieMaker()){
     // Configure this window:
     this->set_default_size(width, height);
 
@@ -49,7 +52,13 @@ bool MainWindow::on_key_press_event(GdkEventKey* event) {
                 get_application()->quit();
             }
             return true;
-
+            
+        // [P] Toggles the record pause:
+        case GDK_KEY_P:
+        case GDK_KEY_p:
+            movieMaker->toggleRecording();
+            return true;
+            
         // [F] toggles full screen mode:
         case GDK_KEY_F:
         case GDK_KEY_f:
diff --git a/src/app/hpp/camera-drawing-area.hpp b/src/app/hpp/camera-drawing-area.hpp
index 7e5c64b..987d57f 100644
--- a/src/app/hpp/camera-drawing-area.hpp
+++ b/src/app/hpp/camera-drawing-area.hpp
@@ -26,5 +26,7 @@ public Subscriptor<EventOrangeDetected> {
 	cv::Mat output;
 	int width, height;
 	EventBus<EventOrangeDetected> eventBus;
+    MovieMaker * movieMaker;
+    void displayRec(const Cairo::RefPtr<Cairo::Context>& cr);
 };
 #endif
diff --git a/src/app/hpp/main-window.hpp b/src/app/hpp/main-window.hpp
index 5331f9a..e27b0a7 100644
--- a/src/app/hpp/main-window.hpp
+++ b/src/app/hpp/main-window.hpp
@@ -20,6 +20,7 @@ class MainWindow : public Gtk::Window {
     Gtk::Box m_box;
     Gtk::Label m_label1;
     CameraDrawingArea cameraDrawingArea;
+    MovieMaker * movieMaker;
 };
 
 #endif

From 20f0a2e67997f2281dbb477ee1469cff7075fe99 Mon Sep 17 00:00:00 2001
From: jgonet <jgonet@pictet.com>
Date: Wed, 23 Jan 2019 16:14:14 +0100
Subject: [PATCH 07/18] Show a red circle when recording

---
 src/app/cpp/camera-drawing-area.cpp | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/src/app/cpp/camera-drawing-area.cpp b/src/app/cpp/camera-drawing-area.cpp
index eafb683..5c7d6f5 100644
--- a/src/app/cpp/camera-drawing-area.cpp
+++ b/src/app/cpp/camera-drawing-area.cpp
@@ -36,7 +36,6 @@ void CameraDrawingArea::doInvalidate() {
 
 bool CameraDrawingArea::on_draw(const Cairo::RefPtr<Cairo::Context>& cr) {
     if (output.size().width > 0) {
-
         // Initializes a pixbuf sharing the same data as the mat:
         Glib::RefPtr<Gdk::Pixbuf> pixbuf =
             Gdk::Pixbuf::create_from_data((guint8*)output.data,
@@ -46,7 +45,6 @@ bool CameraDrawingArea::on_draw(const Cairo::RefPtr<Cairo::Context>& cr) {
                     output.cols,
                     output.rows,
                     (int) output.step);
-
         // Show it all
         Gdk::Cairo::set_source_pixbuf(cr, pixbuf);
         cr->paint();
@@ -64,6 +62,7 @@ void CameraDrawingArea::displayRec(const Cairo::RefPtr<Cairo::Context>& cr) {
     double radius = 7;
     double x = width / 2;
     double y = height - radius;
+    cr->set_source_rgb(1.0, 0.1, 0.1);
     cr->arc(x, y, radius, 0, 2 * M_PI);
     cr->fill();
     cr->stroke();

From 46c34742d176ea9ecd701212cc13d66b26247fb9 Mon Sep 17 00:00:00 2001
From: jean-michel-gonet <jmgonet@yahoo.com>
Date: Thu, 24 Jan 2019 07:42:23 +0100
Subject: [PATCH 08/18] Using a class to measure rates

---
 src/lib/CMakeLists.txt                    |  3 +-
 src/lib/cpp/capture-image-from-camera.cpp | 15 ++++++-
 src/lib/cpp/rate-meter.cpp                | 24 ++++++++++
 src/lib/hpp/capture-image-from-camera.hpp |  9 +++-
 src/lib/hpp/rate-meter.hpp                | 19 ++++++++
 src/lib/tst/CMakeLists.txt                |  3 +-
 src/lib/tst/rate-meter-test.cpp           | 53 +++++++++++++++++++++++
 7 files changed, 121 insertions(+), 5 deletions(-)
 create mode 100644 src/lib/cpp/rate-meter.cpp
 create mode 100644 src/lib/hpp/rate-meter.hpp
 create mode 100644 src/lib/tst/rate-meter-test.cpp

diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt
index 4236268..606ec3e 100644
--- a/src/lib/CMakeLists.txt
+++ b/src/lib/CMakeLists.txt
@@ -8,6 +8,7 @@ set(RASCLIB_SOURCES
 	cpp/orange-ball-detector
 	cpp/movie-maker
 	cpp/capture-image-from-camera
+	cpp/rate-meter
 )
 
 if (WIN32)
@@ -39,4 +40,4 @@ target_link_libraries(rasclib ${RASCLIB_LIBRARIES})
 export(TARGETS rasclib FILE rasclib.cmake)
 
 # Let's have unit tests:
-add_subdirectory(tst)
\ No newline at end of file
+add_subdirectory(tst)
diff --git a/src/lib/cpp/capture-image-from-camera.cpp b/src/lib/cpp/capture-image-from-camera.cpp
index 9ff2d44..cbf8be8 100644
--- a/src/lib/cpp/capture-image-from-camera.cpp
+++ b/src/lib/cpp/capture-image-from-camera.cpp
@@ -1,8 +1,9 @@
 #include "capture-image-from-camera.hpp"
 
 CaptureImageFromCamera::CaptureImageFromCamera():
-captureThread(nullptr),
+captureThread(nullptr),
 propagateThread(nullptr),
+lastFrameSystemClock(std::chrono::system_clock::now()),
 videoCapture(0) {
     startCapturing();
 }
@@ -42,10 +43,22 @@ void CaptureImageFromCamera::doCapture() {
         videoCapture.read(webcam);
         if (webcam.size().width > 0) {
             doPropagate();
+            updateFrameRate();
         }
     } while (keepCapturing);
 }
 
+void CaptureImageFromCamera::updateFrameRate() {
+    std::chrono::time_point<std::chrono::system_clock> now = std::chrono::system_clock::now();
+    std::chrono::duration<float> difference = now - lastFrameSystemClock;
+    
+    frameRate = 1 / difference.count();
+}
+
+float CaptureImageFromCamera::getFrameRate() {
+    return frameRate;
+}
+
 void CaptureImageFromCamera::doPropagate() {
     if (propagateThread) {
         propagateThread->join();
diff --git a/src/lib/cpp/rate-meter.cpp b/src/lib/cpp/rate-meter.cpp
new file mode 100644
index 0000000..54e50bb
--- /dev/null
+++ b/src/lib/cpp/rate-meter.cpp
@@ -0,0 +1,24 @@
+#include "rate-meter.hpp"
+
+using namespace std::chrono;
+
+RateMeter::RateMeter():
+rate(0),
+lastSampleSystemClock(system_clock::now()) {
+}
+
+void RateMeter::updateRate() {
+    time_point<system_clock> now = system_clock::now();
+    std::chrono::duration<double> difference = now - lastSampleSystemClock;
+    
+    double count = difference.count();
+    if (count == 0) {
+        rate = 0;
+    } else {
+        rate = 1 / difference.count();
+    }
+}
+
+double RateMeter::getRate() {
+    return rate;
+}
diff --git a/src/lib/hpp/capture-image-from-camera.hpp b/src/lib/hpp/capture-image-from-camera.hpp
index c903b75..593623e 100644
--- a/src/lib/hpp/capture-image-from-camera.hpp
+++ b/src/lib/hpp/capture-image-from-camera.hpp
@@ -6,6 +6,7 @@
 #include <opencv2/highgui.hpp>
 
 #include <thread>
+#include <chrono>
 
 class EventImageCaptured {
 public:
@@ -22,12 +23,16 @@ class CaptureImageFromCamera {
 	virtual ~CaptureImageFromCamera();
 	void startCapturing();
 	void stopCapturing();
+    float getFrameRate();
 private:
 	bool keepCapturing;
-	void doCapture();
+    float frameRate;
+    void updateFrameRate();
+	void doCapture();
 	void doPropagate();
-	std::thread* captureThread;
+	std::thread* captureThread;
 	std::thread* propagateThread;
+    std::chrono::time_point<std::chrono::system_clock> lastFrameSystemClock;
 	cv::VideoCapture videoCapture;
 	cv::Mat webcam;
 	EventBus<EventImageCaptured> eventBus;
diff --git a/src/lib/hpp/rate-meter.hpp b/src/lib/hpp/rate-meter.hpp
new file mode 100644
index 0000000..99314db
--- /dev/null
+++ b/src/lib/hpp/rate-meter.hpp
@@ -0,0 +1,19 @@
+#ifndef RATE_METER_HPP
+#define RATE_METER_HPP
+
+#include <chrono>
+
+using namespace std::chrono;
+
+class RateMeter {
+public:
+    RateMeter();
+    void updateRate();
+    double getRate();
+    
+private:
+    double rate;
+    time_point<system_clock> lastSampleSystemClock;
+};
+
+#endif
diff --git a/src/lib/tst/CMakeLists.txt b/src/lib/tst/CMakeLists.txt
index d8054f7..0cd0c67 100644
--- a/src/lib/tst/CMakeLists.txt
+++ b/src/lib/tst/CMakeLists.txt
@@ -19,6 +19,7 @@ add_executable(rasctest
 	movie-maker-test
 	event-bus-test
 	system-helper-test
+	rate-meter-test
 )
 
 target_include_directories(rasctest PRIVATE ${PROJECT_BINARY_DIR})
@@ -30,4 +31,4 @@ target_link_libraries(rasctest
 )
 
 # Adds the executable to the test goal:
-add_test(test rasctest)
\ No newline at end of file
+add_test(test rasctest)
diff --git a/src/lib/tst/rate-meter-test.cpp b/src/lib/tst/rate-meter-test.cpp
new file mode 100644
index 0000000..f9b25de
--- /dev/null
+++ b/src/lib/tst/rate-meter-test.cpp
@@ -0,0 +1,53 @@
+#include "rate-meter.hpp"
+
+#include "catch.hpp"
+
+void wait(int c) {
+    for (int n = 0; n < c; n++) {
+        for (int m = 0; m < 1000; m++);
+    }
+}
+
+SCENARIO("Measure rates") {
+    GIVEN( "An initialized rate meter") {
+        WHEN("Calling it once") {
+            RateMeter rateMeter;
+            wait(1000);
+            rateMeter.updateRate();
+            THEN("It measures rate between creation and update") {
+                REQUIRE(rateMeter.getRate() > 0);
+            }
+        }
+        WHEN("Calling it repeatedly") {
+            RateMeter rateMeter;
+            wait(1000);
+            rateMeter.updateRate();
+            wait(1000);
+            rateMeter.updateRate();
+            THEN("Measures rate between updates") {
+                REQUIRE(rateMeter.getRate() > 0);
+            }
+        }
+
+        WHEN("Calling at different frequencies") {
+            RateMeter rateMeter1, rateMeter2, rateMeter3;
+            wait(1000);
+            rateMeter1.updateRate();
+            wait(1000);
+            rateMeter1.updateRate();
+            rateMeter2.updateRate();
+            wait(1000);
+            rateMeter1.updateRate();
+            rateMeter3.updateRate();
+            wait(1000);
+            rateMeter1.updateRate();
+            rateMeter2.updateRate();
+            wait(1000);
+            rateMeter1.updateRate();
+
+            THEN("Rates reflect frequency") {
+                REQUIRE(rateMeter.getRate() > 0);
+            }
+        }
+    }
+}

From a0b5c7bc9e6a3d571aa8c5300ab86883c3f3197a Mon Sep 17 00:00:00 2001
From: jgonet <jgonet@pictet.com>
Date: Thu, 24 Jan 2019 16:29:32 +0100
Subject: [PATCH 09/18] Using a class to measure rates

---
 src/app/cpp/camera-drawing-area.cpp       | 45 ++++++++++++++++++++++-
 src/app/hpp/camera-drawing-area.hpp       |  7 +++-
 src/lib/cpp/capture-image-from-camera.cpp | 22 ++++++-----
 src/lib/cpp/rate-meter.cpp                |  1 +
 src/lib/hpp/capture-image-from-camera.hpp | 13 +++----
 src/lib/tst/rate-meter-test.cpp           | 27 +++++++-------
 6 files changed, 83 insertions(+), 32 deletions(-)

diff --git a/src/app/cpp/camera-drawing-area.cpp b/src/app/cpp/camera-drawing-area.cpp
index 6c55395..61aceaf 100644
--- a/src/app/cpp/camera-drawing-area.cpp
+++ b/src/app/cpp/camera-drawing-area.cpp
@@ -7,7 +7,13 @@
 
 CameraDrawingArea::CameraDrawingArea():
 dispatchInvalidate(),
-movieMaker(ServiceLocator::getMovieMaker()){
+movieMaker(ServiceLocator::getMovieMaker()),
+captureImageFromCamera(ServiceLocator::getCaptureImageFromCamera()){
+
+    fontDescription.set_family("Monospace");
+    fontDescription.set_weight(Pango::WEIGHT_BOLD);
+    fontDescription.set_size(10 * Pango::SCALE);
+
     dispatchInvalidate.
         connect(sigc::mem_fun(*this, 
                     &CameraDrawingArea::doInvalidate));
@@ -54,6 +60,9 @@ bool CameraDrawingArea::on_draw(const Cairo::RefPtr<Cairo::Context>& cr) {
         displayRec(cr);
     }
 
+    displayCaptureRate(cr, captureImageFromCamera->getCaptureRate());
+    displayFrameRate(cr, captureImageFromCamera->getFrameRate());
+
     return true;
 }
 
@@ -67,6 +76,40 @@ void CameraDrawingArea::displayRec(const Cairo::RefPtr<Cairo::Context>& cr) {
     cr->stroke();
 }
 
+void CameraDrawingArea::displayCaptureRate(const Cairo::RefPtr<Cairo::Context> &cr, double captureRate) {
+    
+    char text[30];
+    sprintf(text, "CR %.1f V", captureRate);
+    
+    auto layout = create_pango_layout(text);
+    layout->set_font_description(fontDescription);
+    
+    int textWidth, textHeight;
+    layout->get_pixel_size(textWidth, textHeight);
+    
+    cr->move_to(0, height - textHeight);
+
+    cr->set_source_rgb(1.0, 1.0, 1.0);
+    layout->show_in_cairo_context(cr);
+}
+
+void CameraDrawingArea::displayFrameRate(const Cairo::RefPtr<Cairo::Context> &cr, double frameRate) {
+
+    char text[30];
+    sprintf(text, "FR %.1f V", frameRate);
+    
+    auto layout = create_pango_layout(text);
+    layout->set_font_description(fontDescription);
+    
+    int textWidth, textHeight;
+    layout->get_pixel_size(textWidth, textHeight);
+    
+    cr->move_to(width - textWidth, height - textHeight);
+    
+    cr->set_source_rgb(1.0, 1.0, 1.0);
+    layout->show_in_cairo_context(cr);
+}
+
 void CameraDrawingArea::on_size_allocate (Gtk::Allocation& allocation) {
     DrawingArea::on_size_allocate(allocation);
 
diff --git a/src/app/hpp/camera-drawing-area.hpp b/src/app/hpp/camera-drawing-area.hpp
index 687fbb4..61f0a17 100644
--- a/src/app/hpp/camera-drawing-area.hpp
+++ b/src/app/hpp/camera-drawing-area.hpp
@@ -1,6 +1,7 @@
 #ifndef CAMERA_DRAWING_AREA_HPP
 #define CAMERA_DRAWING_AREA_HPP
 
+#include "capture-image-from-camera.hpp"
 #include "movie-maker.hpp"
 #include "orange-ball-detector.hpp"
 
@@ -26,7 +27,11 @@ public Subscriptor<EventOrangeDetected> {
 	int width, height;
 	EventBus<EventOrangeDetected> eventBus;
     MovieMaker * movieMaker;
-    void displayRec(const Cairo::RefPtr<Cairo::Context>& cr);
+    CaptureImageFromCamera * captureImageFromCamera;
     Glib::RefPtr<Gdk::Pixbuf> pixbuf;
+    Pango::FontDescription fontDescription;
+    void displayRec(const Cairo::RefPtr<Cairo::Context>& cr);
+    void displayCaptureRate(const Cairo::RefPtr<Cairo::Context>& cr, double captureRate);
+    void displayFrameRate(const Cairo::RefPtr<Cairo::Context>& cr, double captureRate);
 };
 #endif
diff --git a/src/lib/cpp/capture-image-from-camera.cpp b/src/lib/cpp/capture-image-from-camera.cpp
index cbf8be8..24c9072 100644
--- a/src/lib/cpp/capture-image-from-camera.cpp
+++ b/src/lib/cpp/capture-image-from-camera.cpp
@@ -3,7 +3,6 @@
 CaptureImageFromCamera::CaptureImageFromCamera():
 captureThread(nullptr),
 propagateThread(nullptr),
-lastFrameSystemClock(std::chrono::system_clock::now()),
 videoCapture(0) {
     startCapturing();
 }
@@ -38,25 +37,30 @@ void CaptureImageFromCamera::stopCapturing() {
 void CaptureImageFromCamera::doCapture() {
     do {
         videoCapture.grab();
+        frameRate.updateRate();
+
         videoCapture.grab();
+        frameRate.updateRate();
+
         videoCapture.grab();
+        frameRate.updateRate();
+
         videoCapture.read(webcam);
+        frameRate.updateRate();
+        captureRate.updateRate();
+
         if (webcam.size().width > 0) {
             doPropagate();
-            updateFrameRate();
         }
     } while (keepCapturing);
 }
 
-void CaptureImageFromCamera::updateFrameRate() {
-    std::chrono::time_point<std::chrono::system_clock> now = std::chrono::system_clock::now();
-    std::chrono::duration<float> difference = now - lastFrameSystemClock;
-    
-    frameRate = 1 / difference.count();
+double CaptureImageFromCamera::getFrameRate() {
+    return frameRate.getRate();
 }
 
-float CaptureImageFromCamera::getFrameRate() {
-    return frameRate;
+double CaptureImageFromCamera::getCaptureRate() {
+    return captureRate.getRate();
 }
 
 void CaptureImageFromCamera::doPropagate() {
diff --git a/src/lib/cpp/rate-meter.cpp b/src/lib/cpp/rate-meter.cpp
index 54e50bb..e20cd80 100644
--- a/src/lib/cpp/rate-meter.cpp
+++ b/src/lib/cpp/rate-meter.cpp
@@ -10,6 +10,7 @@ lastSampleSystemClock(system_clock::now()) {
 void RateMeter::updateRate() {
     time_point<system_clock> now = system_clock::now();
     std::chrono::duration<double> difference = now - lastSampleSystemClock;
+    lastSampleSystemClock = now;
     
     double count = difference.count();
     if (count == 0) {
diff --git a/src/lib/hpp/capture-image-from-camera.hpp b/src/lib/hpp/capture-image-from-camera.hpp
index 593623e..f4ba67a 100644
--- a/src/lib/hpp/capture-image-from-camera.hpp
+++ b/src/lib/hpp/capture-image-from-camera.hpp
@@ -2,11 +2,11 @@
 #define CAPTURE_IMAGE_FROM_CAMERA_HPP
 
 #include "event-bus.hpp"
+#include "rate-meter.hpp"
 
 #include <opencv2/highgui.hpp>
 
 #include <thread>
-#include <chrono>
 
 class EventImageCaptured {
 public:
@@ -23,16 +23,15 @@ class CaptureImageFromCamera {
 	virtual ~CaptureImageFromCamera();
 	void startCapturing();
 	void stopCapturing();
-    float getFrameRate();
+    double getFrameRate();
+    double getCaptureRate();
 private:
 	bool keepCapturing;
-    float frameRate;
-    void updateFrameRate();
 	void doCapture();
 	void doPropagate();
-	std::thread* captureThread;
-	std::thread* propagateThread;
-    std::chrono::time_point<std::chrono::system_clock> lastFrameSystemClock;
+    std::thread * captureThread;
+    std::thread * propagateThread;
+    RateMeter frameRate, captureRate;
 	cv::VideoCapture videoCapture;
 	cv::Mat webcam;
 	EventBus<EventImageCaptured> eventBus;
diff --git a/src/lib/tst/rate-meter-test.cpp b/src/lib/tst/rate-meter-test.cpp
index f9b25de..3849ba5 100644
--- a/src/lib/tst/rate-meter-test.cpp
+++ b/src/lib/tst/rate-meter-test.cpp
@@ -31,22 +31,21 @@ SCENARIO("Measure rates") {
 
         WHEN("Calling at different frequencies") {
             RateMeter rateMeter1, rateMeter2, rateMeter3;
-            wait(1000);
-            rateMeter1.updateRate();
-            wait(1000);
-            rateMeter1.updateRate();
-            rateMeter2.updateRate();
-            wait(1000);
-            rateMeter1.updateRate();
-            rateMeter3.updateRate();
-            wait(1000);
-            rateMeter1.updateRate();
-            rateMeter2.updateRate();
-            wait(1000);
-            rateMeter1.updateRate();
+
+            for (int n = 0; n < 10; n++) {
+                wait(1000);
+                rateMeter1.updateRate();
+                if (n % 2 == 0) {
+                    rateMeter2.updateRate();
+                }
+                if (n % 3 == 0) {
+                    rateMeter3.updateRate();
+                }
+            }
 
             THEN("Rates reflect frequency") {
-                REQUIRE(rateMeter.getRate() > 0);
+                REQUIRE(rateMeter1.getRate() > rateMeter2.getRate());
+                REQUIRE(rateMeter2.getRate() > rateMeter3.getRate());
             }
         }
     }

From c3621030b07d721837137137e52d91ec79d7e4ac Mon Sep 17 00:00:00 2001
From: jean-michel-gonet <jmgonet@yahoo.com>
Date: Thu, 24 Jan 2019 17:46:28 +0100
Subject: [PATCH 10/18] Removed unused buttons and labels

---
 src/app/cpp/main-window.cpp | 25 +------------------------
 src/app/hpp/main-window.hpp |  4 ----
 2 files changed, 1 insertion(+), 28 deletions(-)

diff --git a/src/app/cpp/main-window.cpp b/src/app/cpp/main-window.cpp
index 84644aa..f95e0b5 100644
--- a/src/app/cpp/main-window.cpp
+++ b/src/app/cpp/main-window.cpp
@@ -6,43 +6,20 @@
 
 MainWindow::MainWindow(int width, int height):
 probablyInFullScreen(false),
-m_button("Hello World"),
-m_box(Gtk::ORIENTATION_VERTICAL),
-m_label1("First Label"),
 movieMaker(ServiceLocator::getMovieMaker()){
     // Configure this window:
     this->set_default_size(width, height);
 
-    // Connect the 'click' signal and make the button visible:
-    m_button.signal_clicked().connect(
-            sigc::mem_fun(*this, &MainWindow::buttonClick));
-    m_button.show();
-
-    // Make the first label visible:
-    m_label1.show();
-
     // Make the second label visible:
     cameraDrawingArea.show();
 
-    // Pack all elements in the box:
-    m_box.pack_start(m_button, Gtk::PACK_SHRINK);
-    m_box.pack_start(m_label1, Gtk::PACK_SHRINK);
-    m_box.pack_start(cameraDrawingArea, Gtk::PACK_EXPAND_WIDGET);
-
     // Add the box in this window:
-    add(m_box);
-
-    // Make the box visible:
-    m_box.show();
+    add(cameraDrawingArea);
 
     // Activate Key-Press events
     add_events(Gdk::KEY_PRESS_MASK);
 }
 
-void MainWindow::buttonClick() {
-    std::cout << "Hello World" << std::endl;
-}
-
 bool MainWindow::on_key_press_event(GdkEventKey* event) {
     switch (event->keyval) {
         // Ctrl + C: Ends the application:
diff --git a/src/app/hpp/main-window.hpp b/src/app/hpp/main-window.hpp
index e27b0a7..237caf6 100644
--- a/src/app/hpp/main-window.hpp
+++ b/src/app/hpp/main-window.hpp
@@ -14,11 +14,7 @@ class MainWindow : public Gtk::Window {
     bool on_key_press_event(GdkEventKey* event) override;
 
 private:
-    void buttonClick();
     bool probablyInFullScreen;
-    Gtk::Button m_button;
-    Gtk::Box m_box;
-    Gtk::Label m_label1;
     CameraDrawingArea cameraDrawingArea;
     MovieMaker * movieMaker;
 };

From 41f47368716c9f8021a115a624c7bbb1ffad2531 Mon Sep 17 00:00:00 2001
From: jean-michel-gonet <jmgonet@yahoo.com>
Date: Fri, 25 Jan 2019 05:46:57 +0100
Subject: [PATCH 11/18] Force definition of math constants.

---
 src/app/cpp/camera-drawing-area.cpp | 28 ++++++++++++++++------------
 1 file changed, 16 insertions(+), 12 deletions(-)

diff --git a/src/app/cpp/camera-drawing-area.cpp b/src/app/cpp/camera-drawing-area.cpp
index 61aceaf..f88fae4 100644
--- a/src/app/cpp/camera-drawing-area.cpp
+++ b/src/app/cpp/camera-drawing-area.cpp
@@ -1,9 +1,13 @@
+#define _USE_MATH_DEFINES
+
 #include "camera-drawing-area.hpp"
 
 #include "service-locator.hpp"
 #include "system-helper.hpp"
 
-#include <opencv2/imgproc.hpp>
+#include <opencv2/imgproc.hpp>
+
+#include <math.h>
 
 CameraDrawingArea::CameraDrawingArea():
 dispatchInvalidate(),
@@ -15,7 +19,7 @@ captureImageFromCamera(ServiceLocator::getCaptureImageFromCamera()){
     fontDescription.set_size(10 * Pango::SCALE);
 
     dispatchInvalidate.
-        connect(sigc::mem_fun(*this, 
+        connect(sigc::mem_fun(*this,
                     &CameraDrawingArea::doInvalidate));
     eventBus.subscribe(this);
 }
@@ -77,16 +81,16 @@ void CameraDrawingArea::displayRec(const Cairo::RefPtr<Cairo::Context>& cr) {
 }
 
 void CameraDrawingArea::displayCaptureRate(const Cairo::RefPtr<Cairo::Context> &cr, double captureRate) {
-    
+
     char text[30];
-    sprintf(text, "CR %.1f V", captureRate);
-    
+    sprintf(text, "CR %.1f fps", captureRate);
+
     auto layout = create_pango_layout(text);
     layout->set_font_description(fontDescription);
-    
+
     int textWidth, textHeight;
     layout->get_pixel_size(textWidth, textHeight);
-    
+
     cr->move_to(0, height - textHeight);
 
     cr->set_source_rgb(1.0, 1.0, 1.0);
@@ -96,16 +100,16 @@ void CameraDrawingArea::displayCaptureRate(const Cairo::RefPtr<Cairo::Context> &
 void CameraDrawingArea::displayFrameRate(const Cairo::RefPtr<Cairo::Context> &cr, double frameRate) {
 
     char text[30];
-    sprintf(text, "FR %.1f V", frameRate);
-    
+    sprintf(text, "FR %.1f fps", frameRate);
+
     auto layout = create_pango_layout(text);
     layout->set_font_description(fontDescription);
-    
+
     int textWidth, textHeight;
     layout->get_pixel_size(textWidth, textHeight);
-    
+
     cr->move_to(width - textWidth, height - textHeight);
-    
+
     cr->set_source_rgb(1.0, 1.0, 1.0);
     layout->show_in_cairo_context(cr);
 }

From 7da7007ed89713cb14999b537f969c8d769ecb4d Mon Sep 17 00:00:00 2001
From: jean-michel-gonet <jmgonet@yahoo.com>
Date: Fri, 25 Jan 2019 05:47:40 +0100
Subject: [PATCH 12/18] Use high resolution clock for timing.

---
 src/lib/cpp/rate-meter.cpp | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/lib/cpp/rate-meter.cpp b/src/lib/cpp/rate-meter.cpp
index e20cd80..21d7e44 100644
--- a/src/lib/cpp/rate-meter.cpp
+++ b/src/lib/cpp/rate-meter.cpp
@@ -4,14 +4,14 @@ using namespace std::chrono;
 
 RateMeter::RateMeter():
 rate(0),
-lastSampleSystemClock(system_clock::now()) {
+lastSampleSystemClock(high_resolution_clock::now()) {
 }
 
 void RateMeter::updateRate() {
-    time_point<system_clock> now = system_clock::now();
+    time_point<high_resolution_clock> now = high_resolution_clock::now();
     std::chrono::duration<double> difference = now - lastSampleSystemClock;
     lastSampleSystemClock = now;
-    
+
     double count = difference.count();
     if (count == 0) {
         rate = 0;

From 58e1b5a279737f3b8d7c2b331620d6e05b6566c8 Mon Sep 17 00:00:00 2001
From: jean-michel-gonet <jmgonet@yahoo.com>
Date: Fri, 25 Jan 2019 05:48:16 +0100
Subject: [PATCH 13/18] Windows has 1ms resolution in timing, so unit test has
 to take longer to work

---
 src/lib/tst/rate-meter-test.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/lib/tst/rate-meter-test.cpp b/src/lib/tst/rate-meter-test.cpp
index 3849ba5..fe54f3f 100644
--- a/src/lib/tst/rate-meter-test.cpp
+++ b/src/lib/tst/rate-meter-test.cpp
@@ -4,7 +4,7 @@
 
 void wait(int c) {
     for (int n = 0; n < c; n++) {
-        for (int m = 0; m < 1000; m++);
+        for (int m = 0; m < 10000; m++);
     }
 }
 

From 4c833e83a98002d9dad42f589f05ba67519b902d Mon Sep 17 00:00:00 2001
From: jean-michel-gonet <jmgonet@yahoo.com>
Date: Fri, 25 Jan 2019 05:54:30 +0100
Subject: [PATCH 14/18] Use high resolution clock for timing.

---
 src/lib/hpp/rate-meter.hpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/lib/hpp/rate-meter.hpp b/src/lib/hpp/rate-meter.hpp
index 99314db..c695be6 100644
--- a/src/lib/hpp/rate-meter.hpp
+++ b/src/lib/hpp/rate-meter.hpp
@@ -13,7 +13,7 @@ class RateMeter {
     
 private:
     double rate;
-    time_point<system_clock> lastSampleSystemClock;
+    time_point<high_resolution_clock> lastSampleSystemClock;
 };
 
 #endif

From 8bac1395537bbca2e8f7469fb3c41b7f9f32c67f Mon Sep 17 00:00:00 2001
From: jgonet <jgonet@pictet.com>
Date: Fri, 25 Jan 2019 12:53:36 +0100
Subject: [PATCH 15/18] Force definition of math constants.

---
 src/app/cpp/camera-drawing-area.cpp | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/app/cpp/camera-drawing-area.cpp b/src/app/cpp/camera-drawing-area.cpp
index 61aceaf..a40ea40 100644
--- a/src/app/cpp/camera-drawing-area.cpp
+++ b/src/app/cpp/camera-drawing-area.cpp
@@ -1,3 +1,5 @@
+#define _USE_MATH_DEFINES
+
 #include "camera-drawing-area.hpp"
 
 #include "service-locator.hpp"

From ea96f9f395915ea61b640b132494ed9b8a11f34f Mon Sep 17 00:00:00 2001
From: jgonet <jgonet@pictet.com>
Date: Fri, 25 Jan 2019 15:50:34 +0100
Subject: [PATCH 16/18] Let app take care of drawing a circle, because this is
 UI

---
 src/app/cpp/camera-drawing-area.cpp  |  8 ++++++--
 src/lib/cpp/orange-ball-detector.cpp | 10 ++++------
 2 files changed, 10 insertions(+), 8 deletions(-)

diff --git a/src/app/cpp/camera-drawing-area.cpp b/src/app/cpp/camera-drawing-area.cpp
index d02d174..e42ad59 100644
--- a/src/app/cpp/camera-drawing-area.cpp
+++ b/src/app/cpp/camera-drawing-area.cpp
@@ -30,9 +30,13 @@ CameraDrawingArea::~CameraDrawingArea() {
 
 void CameraDrawingArea::receive(EventOrangeDetected e) {
     if (width > 0 && height > 0) {
-        resize(e.getCapturedImage(), output, cv::Size(width, height), 0, 0, cv::INTER_LINEAR);
+        // Resize captured image without changing its aspect:
+        cv::Mat capturedImage = e.getCapturedImage();
+        double capturedImageWidth = capturedImage.size().width;
+        double scale = width / capturedImageWidth;
+        resize(capturedImage, output, cv::Size(), scale, scale, cv::INTER_LINEAR);
 
-        // Initializes a pixbuf sharing the same data as the mat:
+        // Initialize a pixbuf based on the same data as the image:
         pixbuf = Gdk::Pixbuf::create_from_data(
                 (guint8*)output.data,
                 Gdk::COLORSPACE_RGB,
diff --git a/src/lib/cpp/orange-ball-detector.cpp b/src/lib/cpp/orange-ball-detector.cpp
index 13e20ff..d9f77c1 100644
--- a/src/lib/cpp/orange-ball-detector.cpp
+++ b/src/lib/cpp/orange-ball-detector.cpp
@@ -59,14 +59,9 @@ void OrangeBallDetector::detect(cv::Mat image) {
         // Assuming that biggest contour is a circle,
         // determine its center and size:
         cv::minEnclosingCircle(maxContour, ballPosition, radius);
-
-        // Draw the circle:
-        if (radius > 20) {
-            cv::circle(resizedImage, ballPosition, radius, cv::Scalar(0, 255, 0), 3);
-        }
         showIfDebug(resizedImage);
     }
-    eventBus.propagate(EventOrangeDetected(resizedImage, ballPosition, radius));
+    eventBus.propagate(EventOrangeDetected(image, ballPosition * 0.5, radius));
 }
 
 void OrangeBallDetector::setDebug(bool d) {
@@ -75,6 +70,9 @@ void OrangeBallDetector::setDebug(bool d) {
 
 void OrangeBallDetector::showIfDebug(cv::Mat m) {
     if (debug) {
+        if (radius > 20) {
+            cv::circle(m, ballPosition, radius, cv::Scalar(0, 255, 0), 3);
+        }
         imshow("orange-ball-detector", m);
         cv::waitKey(0);
     }

From e9bfb2616e9d11fe7b75eb02389bcaec82aa380c Mon Sep 17 00:00:00 2001
From: jean-michel-gonet <jmgonet@yahoo.com>
Date: Fri, 25 Jan 2019 17:40:29 +0100
Subject: [PATCH 17/18] Let app take care of drawing a circle, because this is
 UI

---
 src/app/cpp/camera-drawing-area.cpp       | 18 +++++++++++++++++-
 src/app/hpp/camera-drawing-area.hpp       |  2 ++
 src/lib/cpp/orange-ball-detector.cpp      | 11 ++++++++---
 src/lib/tst/orange-ball-detector-test.cpp | 10 +++++-----
 4 files changed, 32 insertions(+), 9 deletions(-)

diff --git a/src/app/cpp/camera-drawing-area.cpp b/src/app/cpp/camera-drawing-area.cpp
index e42ad59..7a2c147 100644
--- a/src/app/cpp/camera-drawing-area.cpp
+++ b/src/app/cpp/camera-drawing-area.cpp
@@ -45,7 +45,10 @@ void CameraDrawingArea::receive(EventOrangeDetected e) {
                 output.cols,
                 output.rows,
                 (int) output.step);
-
+        
+        // Remember the event for later.
+        lastDetectedOrange = e;
+        
         // Invalidate the window, but for that we need to be in the GUI thread.
         dispatchInvalidate.emit();
 	}
@@ -68,12 +71,25 @@ bool CameraDrawingArea::on_draw(const Cairo::RefPtr<Cairo::Context>& cr) {
         displayRec(cr);
     }
 
+    highlightOrange(cr);
     displayCaptureRate(cr, captureImageFromCamera->getCaptureRate());
     displayFrameRate(cr, captureImageFromCamera->getFrameRate());
 
     return true;
 }
 
+void CameraDrawingArea::highlightOrange(const Cairo::RefPtr<Cairo::Context>& cr) {
+    if (lastDetectedOrange.hasDetectedSomething()) {
+        double radius = lastDetectedOrange.getRadius() * width;
+        double x = lastDetectedOrange.getBallPosition().x * width;
+        double y = lastDetectedOrange.getBallPosition().y * width;
+        cr->set_source_rgb(1.0, 1.0, 0.1);
+        cr->arc(x, y, radius, 0, 2 * M_PI);
+        cr->fill();
+        cr->stroke();
+    }
+}
+
 void CameraDrawingArea::displayRec(const Cairo::RefPtr<Cairo::Context>& cr) {
     double radius = 7;
     double x = width / 2;
diff --git a/src/app/hpp/camera-drawing-area.hpp b/src/app/hpp/camera-drawing-area.hpp
index 61f0a17..f8b73f6 100644
--- a/src/app/hpp/camera-drawing-area.hpp
+++ b/src/app/hpp/camera-drawing-area.hpp
@@ -30,6 +30,8 @@ public Subscriptor<EventOrangeDetected> {
     CaptureImageFromCamera * captureImageFromCamera;
     Glib::RefPtr<Gdk::Pixbuf> pixbuf;
     Pango::FontDescription fontDescription;
+    EventOrangeDetected lastDetectedOrange;
+    void highlightOrange(const Cairo::RefPtr<Cairo::Context>& cr);
     void displayRec(const Cairo::RefPtr<Cairo::Context>& cr);
     void displayCaptureRate(const Cairo::RefPtr<Cairo::Context>& cr, double captureRate);
     void displayFrameRate(const Cairo::RefPtr<Cairo::Context>& cr, double captureRate);
diff --git a/src/lib/cpp/orange-ball-detector.cpp b/src/lib/cpp/orange-ball-detector.cpp
index d9f77c1..9d6b777 100644
--- a/src/lib/cpp/orange-ball-detector.cpp
+++ b/src/lib/cpp/orange-ball-detector.cpp
@@ -2,6 +2,8 @@
 
 #include <opencv2/imgproc.hpp>
 
+#define ORANGE_BALL_DETECTOR_IMAGE_WIDTH 600
+
 void OrangeBallDetector::receive(EventImageCaptured e) {
     detect(e.getCapturedImage());
 }
@@ -16,7 +18,7 @@ void OrangeBallDetector::detect(cv::Mat image) {
 
     // Make the image of a reasonable size:
     double width = image.size().width;
-    double scale = 600 / width;
+    double scale = ORANGE_BALL_DETECTOR_IMAGE_WIDTH / width;
     resize(image, resizedImage, cv::Size(), scale, scale, cv::INTER_LINEAR);
     showIfDebug(resizedImage);
 
@@ -61,7 +63,10 @@ void OrangeBallDetector::detect(cv::Mat image) {
         cv::minEnclosingCircle(maxContour, ballPosition, radius);
         showIfDebug(resizedImage);
     }
-    eventBus.propagate(EventOrangeDetected(image, ballPosition * 0.5, radius));
+    eventBus.propagate(EventOrangeDetected(
+            resizedImage,
+            ballPosition / ORANGE_BALL_DETECTOR_IMAGE_WIDTH,
+            radius / ORANGE_BALL_DETECTOR_IMAGE_WIDTH));
 }
 
 void OrangeBallDetector::setDebug(bool d) {
@@ -120,5 +125,5 @@ float EventOrangeDetected::getRadius() {
 }
 
 bool EventOrangeDetected::hasDetectedSomething() {
-    return radius > 10;
+    return radius > 10 / ORANGE_BALL_DETECTOR_IMAGE_WIDTH;
 }
diff --git a/src/lib/tst/orange-ball-detector-test.cpp b/src/lib/tst/orange-ball-detector-test.cpp
index ffbb2f1..b953457 100644
--- a/src/lib/tst/orange-ball-detector-test.cpp
+++ b/src/lib/tst/orange-ball-detector-test.cpp
@@ -46,7 +46,7 @@ SCENARIO("Can detect orange balls in an image") {
             THEN ( "Can find the orange") {
                 EventOrangeDetected e = receiver.getEvent();
                 REQUIRE(e.hasDetectedSomething() );
-                REQUIRE_THAT(e.getBallPosition().x, WithinAbs(435, 10));
+                REQUIRE_THAT(e.getBallPosition().x, WithinAbs(0.728, 0.01));
             }
         }
 
@@ -59,7 +59,7 @@ SCENARIO("Can detect orange balls in an image") {
             THEN ( "Can find the orange (2)") {
                 EventOrangeDetected e = receiver.getEvent();
                 REQUIRE(e.hasDetectedSomething());
-                REQUIRE_THAT(e.getBallPosition().x, WithinAbs(240, 10));
+                REQUIRE_THAT(e.getBallPosition().x, WithinAbs(0.401, 0.01));
             }
         }
 
@@ -72,7 +72,7 @@ SCENARIO("Can detect orange balls in an image") {
             THEN ( "Can find the ball") {
                 EventOrangeDetected e = receiver.getEvent();
                 REQUIRE(e.hasDetectedSomething());
-                REQUIRE_THAT(e.getBallPosition().x, WithinAbs(360, 10));
+                REQUIRE_THAT(e.getBallPosition().x, WithinAbs(0.599, 0.01));
             }
         }
 
@@ -85,7 +85,7 @@ SCENARIO("Can detect orange balls in an image") {
             THEN ( "Sadly, misses the orange") {
                 EventOrangeDetected e = receiver.getEvent();
                 REQUIRE(e.hasDetectedSomething());
-                REQUIRE_THAT(e.getBallPosition().x, WithinAbs(215, 10));
+                REQUIRE_THAT(e.getBallPosition().x, WithinAbs(0.355, 0.01));
             }
         }
 
@@ -99,7 +99,7 @@ SCENARIO("Can detect orange balls in an image") {
             THEN ( "Can find the ball") {
                 EventOrangeDetected e = receiver.getEvent();
                 REQUIRE(e.hasDetectedSomething());
-                REQUIRE_THAT( e.getBallPosition().x, WithinAbs(360, 10));
+                REQUIRE_THAT( e.getBallPosition().x, WithinAbs(0.613, 0.01));
             }
         }
         WHEN( "Shown with a broccoli") {

From d478d4c432acca84d9a0c0536622469935e62b50 Mon Sep 17 00:00:00 2001
From: jean-michel-gonet <jmgonet@yahoo.com>
Date: Fri, 25 Jan 2019 20:15:03 +0100
Subject: [PATCH 18/18] Let app take care of drawing a circle, because this is
 UI

---
 src/app/cpp/camera-drawing-area.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/app/cpp/camera-drawing-area.cpp b/src/app/cpp/camera-drawing-area.cpp
index 7a2c147..0561faf 100644
--- a/src/app/cpp/camera-drawing-area.cpp
+++ b/src/app/cpp/camera-drawing-area.cpp
@@ -83,9 +83,9 @@ void CameraDrawingArea::highlightOrange(const Cairo::RefPtr<Cairo::Context>& cr)
         double radius = lastDetectedOrange.getRadius() * width;
         double x = lastDetectedOrange.getBallPosition().x * width;
         double y = lastDetectedOrange.getBallPosition().y * width;
-        cr->set_source_rgb(1.0, 1.0, 0.1);
+        cr->set_source_rgb(1.0, 0.5, 0.1);
         cr->arc(x, y, radius, 0, 2 * M_PI);
-        cr->fill();
+        cr->set_line_width(5);
         cr->stroke();
     }
 }
