From 45e93059de067a31b1adb8c469c02b1e044ccf76 Mon Sep 17 00:00:00 2001
From: jean-michel-gonet <jmgonet@yahoo.com>
Date: Wed, 19 Dec 2018 07:47:07 +0100
Subject: [PATCH 01/35] Add the event bus, as decoupling strategy

---
 src/lib/hpp/event-bus.hpp      |  87 +++++++++++++++++++++++
 src/lib/tst/CMakeLists.txt     |   1 +
 src/lib/tst/event-bus-test.cpp | 124 +++++++++++++++++++++++++++++++++
 3 files changed, 212 insertions(+)
 create mode 100644 src/lib/hpp/event-bus.hpp
 create mode 100644 src/lib/tst/event-bus-test.cpp

diff --git a/src/lib/hpp/event-bus.hpp b/src/lib/hpp/event-bus.hpp
new file mode 100644
index 0000000..6b52816
--- /dev/null
+++ b/src/lib/hpp/event-bus.hpp
@@ -0,0 +1,87 @@
+#ifndef EVENT_BUS_HPP
+#define EVENT_BUS_HPP
+
+#include <vector>
+
+using namespace std;
+
+/**
+ * A class to receive events and redistribute them to
+ * all subscribers.
+ * This piece of code involves all kind of difficult stuff I had to learn:
+ * - Using static variables in template classes:
+ *   - https://www.fayewilliams.com/2014/12/23/static-variables-in-template-classes-and-methods/
+ * - Why do we need to have a header-only class:
+ *   - https://stackoverflow.com/questions/1639797/template-issue-causes-linker-error-c
+ * - Use erase-remove idiom to remove elements by value from a vector:
+     - https://stackoverflow.com/questions/3385229/c-erase-vector-element-by-value-rather-than-by-position
+ * - How to deal with static members in header-only classes:
+ *   - https://stackoverflow.com/questions/11709859/how-to-have-static-data-members-in-a-header-only-library
+ */
+
+/** @brief Describes the interface that a subscriber must implement.
+ * The subscriber has to implement the ability to receive events.
+ */
+template <class T>
+class Subscriptor {
+public:
+	/** @brief Calls the subscriptor providing the event.
+	 * @param event The event.
+	 */
+	virtual void receive(T event) = 0;
+};
+
+/** @brief A static vector of Subscriptor.
+ * This is a work around to use a static variables in header-only
+ * classes.
+ */
+template <class T>
+struct SubscriptorsHolder {
+	static vector<Subscriptor<T> *> subscriptors;
+};
+
+/** @brief Declare the static variable, so it can be linked.
+ */
+template <class T>
+vector<Subscriptor<T> *> SubscriptorsHolder<T>::subscriptors;
+
+/** @brief Propagates the events to all subscriptors in the list.
+ */
+template <class T>
+class EventBusService {
+public:
+	virtual ~EventBusService() = default;
+	
+	/** @brief empties the list of subscribers.
+	 * You should not use this outside unit testing.
+	 */
+	virtual void clear() {
+		SubscriptorsHolder<T>::subscriptors.clear();
+	}
+	 
+	/** @brief Propagates the event to all subscriptors.
+	 * @param event The event.
+	 */
+	virtual void propagate(T event) {
+		for (unsigned int i = 0; i < SubscriptorsHolder<T>::subscriptors.size(); i++) {
+			Subscriptor<T> *subscriptor = SubscriptorsHolder<T>::subscriptors[i];
+			subscriptor->receive(event);
+		}
+	};
+
+	/** @brief Any class able to receive the event can subscribe.
+	 * @param subscriptor A pointer to an instance able to receive events.
+	 */
+	virtual void subscribe(Subscriptor<T> *subscriptor) {
+		SubscriptorsHolder<T>::subscriptors.push_back(subscriptor);
+	};
+
+	/** @brief Unsubscribes the specified subscriptor.
+	 * @param subscriptor A pointer to the subscriptor.
+	 */
+	virtual void unsubscribe(Subscriptor<T> *subscriptor) {
+		SubscriptorsHolder<T>::subscriptors.erase(remove(SubscriptorsHolder<T>::subscriptors.begin(), SubscriptorsHolder<T>::subscriptors.end(), subscriptor), SubscriptorsHolder<T>::subscriptors.end());
+	}
+};
+
+#endif
diff --git a/src/lib/tst/CMakeLists.txt b/src/lib/tst/CMakeLists.txt
index bc6b7a8..55c4f06 100644
--- a/src/lib/tst/CMakeLists.txt
+++ b/src/lib/tst/CMakeLists.txt
@@ -17,6 +17,7 @@ add_executable(rasctest
 
 	orange-ball-detector-test
 	movie-maker-test
+	event-bus-test
 )
 
 target_include_directories(rasctest PRIVATE ${PROJECT_BINARY_DIR})
diff --git a/src/lib/tst/event-bus-test.cpp b/src/lib/tst/event-bus-test.cpp
new file mode 100644
index 0000000..d83a900
--- /dev/null
+++ b/src/lib/tst/event-bus-test.cpp
@@ -0,0 +1,124 @@
+#include "event-bus.hpp"
+#include "catch.hpp"
+
+/** Any class can be used as an event. */
+class AnyEvent {
+public:
+	AnyEvent(int id) {
+		identity = id;
+	}
+	int getIdentity() {
+		return identity;
+	}
+protected:
+	int identity;
+};
+
+/** Let's have another event. */
+class OtherEvent:public AnyEvent {
+public:
+	OtherEvent(int id):AnyEvent(id) {
+		// Nothing to do.
+	}
+};
+
+/** Any class can be a subscriber, as long as it
+  * implements the ability to receive events. */
+class SubscriberToAllEvents:public Subscriptor<AnyEvent>, public Subscriptor<OtherEvent> {
+public:
+	SubscriberToAllEvents(int id) {
+		identityOfLastReceivedEvent = id;
+	};
+	void receive(AnyEvent event) override {
+		identityOfLastReceivedEvent = event.getIdentity();
+	}
+	void receive(OtherEvent event) override {
+		identityOfLastReceivedEvent = event.getIdentity();
+	}
+	int getIdentityOfLastReceivedEvent() {
+		return identityOfLastReceivedEvent;
+	}
+	void setIdentityOfLastReceivedEvent(int id) {
+		identityOfLastReceivedEvent = id;
+	}
+private:
+	int identityOfLastReceivedEvent;
+};
+
+using namespace std::placeholders;
+using namespace std;
+
+SCENARIO("The Event Bus Service broadcasts to all subscribers of this event") {
+	EventBusService<AnyEvent> eventBus;
+
+	GIVEN( "Two classes subscribed to the same event") {
+		SubscriberToAllEvents subscriber1(10), subscriber2(20);
+		eventBus.clear();
+		eventBus.subscribe(&subscriber1);
+		eventBus.subscribe(&subscriber2);
+
+		WHEN( "When the event is fired") {
+			eventBus.propagate(AnyEvent(1));
+			THEN ( "All subscriptors are informed ") {
+				REQUIRE( subscriber1.getIdentityOfLastReceivedEvent() == 1);
+				REQUIRE( subscriber2.getIdentityOfLastReceivedEvent() == 1);
+			}
+		}
+		
+		WHEN( "One class unsubscribes") {
+			eventBus.unsubscribe(&subscriber2);
+			
+			THEN ("It doesn't receive events any more") {
+				eventBus.propagate(AnyEvent(2));
+				REQUIRE( subscriber1.getIdentityOfLastReceivedEvent() == 2);
+				REQUIRE( subscriber2.getIdentityOfLastReceivedEvent() == 20);
+			}
+		}
+	}
+}
+
+
+SCENARIO("The Event Bus Service broatcasts only to subscribers of this event") {
+	EventBusService<AnyEvent> eventBus1;
+	EventBusService<OtherEvent> eventBus2;
+	
+	GIVEN( "Two classes subscribed to different event") {
+		eventBus1.clear();
+		eventBus2.clear();
+		SubscriberToAllEvents subscriber1(10), subscriber2(20);
+		eventBus1.subscribe(&subscriber1);
+		eventBus2.subscribe(&subscriber2);
+		
+		WHEN( "When the event is fired") {
+			eventBus1.propagate(AnyEvent(1));
+			eventBus2.propagate(OtherEvent(2));
+
+			THEN ( "Only subscribers to specific event are informed ") {
+				REQUIRE( subscriber1.getIdentityOfLastReceivedEvent() == 1);
+				REQUIRE( subscriber2.getIdentityOfLastReceivedEvent() == 2);
+			}
+		}
+	}
+}
+
+SCENARIO("The Event Bus Service is a singleton") {
+	EventBusService<AnyEvent> eventBus1;
+	EventBusService<AnyEvent> eventBus2;
+	
+	GIVEN( "Two classes subscribed to same event using different instances of same event bus") {
+		eventBus1.clear();
+		eventBus2.clear();
+		SubscriberToAllEvents subscriber1(10), subscriber2(20);
+		eventBus1.subscribe(&subscriber1);
+		eventBus2.subscribe(&subscriber2);
+		
+		WHEN( "When the event is fired") {
+			eventBus1.propagate(AnyEvent(1));
+			
+			THEN ( "All subscriptors are informed") {
+				REQUIRE( subscriber1.getIdentityOfLastReceivedEvent() == 1);
+				REQUIRE( subscriber2.getIdentityOfLastReceivedEvent() == 1);
+			}
+		}
+	}
+}

From e1e77b383d47787a7779d7d4b8f2bbd8b98bf7e8 Mon Sep 17 00:00:00 2001
From: jean-michel-gonet <jmgonet@yahoo.com>
Date: Fri, 21 Dec 2018 07:31:28 +0100
Subject: [PATCH 02/35] Step2 - Use event bus to pass the captured images and
 things

---
 src/app/cpp/camera-drawing-area.cpp       |  90 ++++-----------
 src/app/cpp/main.cpp                      |  10 +-
 src/app/hpp/camera-drawing-area.hpp       |  18 +--
 src/lib/CMakeLists.txt                    |   1 +
 src/lib/cpp/capture-image-from-camera.cpp |  47 ++++++++
 src/lib/cpp/movie-maker.cpp               |  10 +-
 src/lib/cpp/orange-ball-detector.cpp      | 134 ++++++++++++++--------
 src/lib/hpp/capture-image-from-camera.hpp |  32 ++++++
 src/lib/hpp/event-bus.hpp                 |   4 +-
 src/lib/hpp/orange-ball-detector.hpp      |  35 ++++--
 src/lib/tst/event-bus-test.cpp            |  10 +-
 11 files changed, 244 insertions(+), 147 deletions(-)
 create mode 100644 src/lib/cpp/capture-image-from-camera.cpp
 create mode 100644 src/lib/hpp/capture-image-from-camera.hpp

diff --git a/src/app/cpp/camera-drawing-area.cpp b/src/app/cpp/camera-drawing-area.cpp
index 2bfb14b..66a2686 100644
--- a/src/app/cpp/camera-drawing-area.cpp
+++ b/src/app/cpp/camera-drawing-area.cpp
@@ -8,53 +8,21 @@
 #include <sys/types.h>
 
 CameraDrawingArea::CameraDrawingArea():
-captureThread(nullptr),
-videoCapture(0),
 movieMaker("./live.avi", 20.0) {
-	startCapturing();
-}
-
-CameraDrawingArea::~CameraDrawingArea() {
-	stopCapturing();
-}
-
-void CameraDrawingArea::startCapturing() {
-	if (!captureThread) {
-		keepCapturing = true;
-		captureThread = new std::thread([this] { doCapture(); });
-	}
-}
-
-void CameraDrawingArea::stopCapturing() {
-	keepCapturing = false;
-	captureThread->join();
-	free(captureThread);
-	captureThread = nullptr;
-}
-
-void CameraDrawingArea::doCapture() {
-	while (keepCapturing) {
-		videoCapture.grab();
-		videoCapture.grab();
-		videoCapture.grab();
-		videoCapture.read(webcam);
-		everyNowAndThen();
-	}
+	eventBus.subscribe(this);
 }
 
 /**
- * Every now and then, we invalidate the whole Widget rectangle,
- * forcing a complete refresh.
+ * Each time we receive a new image, we invalidate the
+ * whole Widget rectangle, forcing a complete refresh.
  */
-bool CameraDrawingArea::everyNowAndThen() {
+void CameraDrawingArea::receive(EventOrangeDetected e) {
+	webcam = e.getCapturedImage();
 	auto win = get_window();
 	if (win) {
 		Gdk::Rectangle r(0, 0, width, height);
 		win->invalidate_rect(r, false);
 	}
-	
-	// Don't stop calling me:
-	return true;
 }
 
 /**
@@ -75,37 +43,27 @@ void CameraDrawingArea::on_size_allocate (Gtk::Allocation& allocation) {
  * another object, or every now and then.
  */
 bool CameraDrawingArea::on_draw(const Cairo::RefPtr<Cairo::Context>& cr) {
-	
-	// Prevent the drawing if size is 0:
-	if (width == 0 || height == 0) {
-		return true;
-	}
-
-	// Capture one image from camera:
-	videoCapture.read(webcam);
+	if (webcam.size().width > 0) {
+		// Stream it in video:
+		movieMaker.addPhotogram(webcam);
+		
+		// Resize it to the allocated size of the Widget.
+		resize(webcam, output, cv::Size(width, height), 0, 0, cv::INTER_LINEAR);
 
-	// Stream it in video:
-	movieMaker.addPhotogram(webcam);
+		// Initializes a pixbuf sharing the same data as the mat:
+		Glib::RefPtr<Gdk::Pixbuf> pixbuf =
+			Gdk::Pixbuf::create_from_data((guint8*)output.data,
+										  Gdk::COLORSPACE_RGB,
+										  false,
+										  8,
+										  output.cols,
+										  output.rows,
+										  (int) output.step);
 
-	// Detect the ball:
-	orangeBallDetector.detect(webcam);
-	
-	// Resize it to the allocated size of the Widget.
-	resize(orangeBallDetector.getImage(), output, cv::Size(width, height), 0, 0, cv::INTER_LINEAR);
-
-	// Initializes a pixbuf sharing the same data as the mat:
-	Glib::RefPtr<Gdk::Pixbuf> pixbuf =
-		Gdk::Pixbuf::create_from_data((guint8*)output.data,
-									  Gdk::COLORSPACE_RGB,
-									  false,
-									  8,
-									  output.cols,
-									  output.rows,
-									  (int) output.step);
-
-	// Display
-	Gdk::Cairo::set_source_pixbuf(cr, pixbuf);
-	cr->paint();
+		// Display
+		Gdk::Cairo::set_source_pixbuf(cr, pixbuf);
+		cr->paint();
+	}
 	
 	// Don't stop calling me.
 	return true;
diff --git a/src/app/cpp/main.cpp b/src/app/cpp/main.cpp
index f91e5d6..4905dd8 100644
--- a/src/app/cpp/main.cpp
+++ b/src/app/cpp/main.cpp
@@ -1,6 +1,14 @@
 #include "main-window.hpp"
-
+#include "orange-ball-detector.hpp"
+#include "capture-image-from-camera.hpp"
+#include "event-bus.hpp"
 int main (int argc, char *argv[]) {
+	
+	CaptureImageFromCamera captureImageFromCamera;
+	OrangeBallDetector orangeBallDetector;
+	EventBus<EventImageCaptured> eventImageBus;
+	eventImageBus.subscribe(&orangeBallDetector);
+	
 	auto app = Gtk::Application::create(argc, argv, "ch.agl-developpement.cpp-tutorial.raspberry-cpp-gtk-opencv");
 	MainWindow mainWindow(300, 300);
 	return app->run(mainWindow);	
diff --git a/src/app/hpp/camera-drawing-area.hpp b/src/app/hpp/camera-drawing-area.hpp
index 7fc8a98..557677b 100644
--- a/src/app/hpp/camera-drawing-area.hpp
+++ b/src/app/hpp/camera-drawing-area.hpp
@@ -1,9 +1,6 @@
 #ifndef CAMERA_DRAWING_AREA_H
 #define CAMERA_DRAWING_AREA_H
 
-#include <thread>
-#include <mutex>
-
 #include <opencv2/highgui.hpp>
 #include <gtkmm.h>
 
@@ -11,10 +8,12 @@
 #include "orange-ball-detector.hpp"
 
 class CameraDrawingArea :
-public Gtk::DrawingArea {
+public Gtk::DrawingArea,
+public Subscriptor<EventOrangeDetected> {
 public:
     CameraDrawingArea();
-    virtual ~CameraDrawingArea();
+    virtual ~CameraDrawingArea() = default;
+	void receive(EventOrangeDetected e) override;
     
 protected:
     bool on_draw(const Cairo::RefPtr<Cairo::Context>& cr) override;
@@ -23,16 +22,11 @@ public Gtk::DrawingArea {
 	bool everyNowAndThen();
 
 private:
-	void startCapturing();
-	void stopCapturing();
-	bool keepCapturing;
-	void doCapture();
-	std::thread* captureThread;
-	cv::VideoCapture videoCapture;
 	MovieMaker movieMaker;
-	OrangeBallDetector orangeBallDetector;
 	cv::Mat webcam;
 	cv::Mat output;
 	int width, height;
+	EventBus<EventOrangeDetected> eventBus;
+
 };
 #endif
diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt
index ef1d660..30cbd33 100644
--- a/src/lib/CMakeLists.txt
+++ b/src/lib/CMakeLists.txt
@@ -7,6 +7,7 @@ find_package( OpenCV REQUIRED )
 add_library(rasclib
 	cpp/orange-ball-detector
 	cpp/movie-maker
+	cpp/capture-image-from-camera
 )
 
 # Define headers for this library. When PUBLIC, headers used for
diff --git a/src/lib/cpp/capture-image-from-camera.cpp b/src/lib/cpp/capture-image-from-camera.cpp
new file mode 100644
index 0000000..3f9c188
--- /dev/null
+++ b/src/lib/cpp/capture-image-from-camera.cpp
@@ -0,0 +1,47 @@
+#include "capture-image-from-camera.hpp"
+
+CaptureImageFromCamera::CaptureImageFromCamera():
+captureThread(nullptr),
+videoCapture(0) {
+	startCapturing();
+}
+
+CaptureImageFromCamera::~CaptureImageFromCamera() {
+	stopCapturing();
+}
+
+void CaptureImageFromCamera::startCapturing() {
+	if (!captureThread) {
+		keepCapturing = true;
+		captureThread = new std::thread([this] {
+			doCapture();
+		});
+	}
+}
+
+void CaptureImageFromCamera::stopCapturing() {
+	if (captureThread) {
+		keepCapturing = false;
+		captureThread->join();
+		free(captureThread);
+		captureThread = nullptr;
+	}
+}
+
+void CaptureImageFromCamera::doCapture() {
+	while (keepCapturing) {
+		videoCapture.grab();
+		videoCapture.grab();
+		videoCapture.grab();
+		videoCapture.read(webcam);
+		eventBus.propagate(EventImageCaptured(webcam));
+	}
+}
+
+EventImageCaptured::EventImageCaptured(cv::Mat i):
+capturedImage(i){
+}
+
+cv::Mat EventImageCaptured::getCapturedImage() {
+	return capturedImage;
+}
diff --git a/src/lib/cpp/movie-maker.cpp b/src/lib/cpp/movie-maker.cpp
index 98d1368..c4f6819 100644
--- a/src/lib/cpp/movie-maker.cpp
+++ b/src/lib/cpp/movie-maker.cpp
@@ -20,11 +20,13 @@ MovieMaker::~MovieMaker() {
 }
 
 void MovieMaker::addPhotogram(Mat photogram) {
-	if (!isMovieStarted) {
-		startMovie(photogram);
-		isMovieStarted = true;
+	if (photogram.size().width > 0) {
+		if (!isMovieStarted) {
+			startMovie(photogram);
+			isMovieStarted = true;
+		}
+		videoWriter.write(photogram);
 	}
-	videoWriter.write(photogram);
 }
 
 void MovieMaker::endMovie() {
diff --git a/src/lib/cpp/orange-ball-detector.cpp b/src/lib/cpp/orange-ball-detector.cpp
index a69d4c9..f46efee 100644
--- a/src/lib/cpp/orange-ball-detector.cpp
+++ b/src/lib/cpp/orange-ball-detector.cpp
@@ -4,59 +4,65 @@
 #include "opencv2/core.hpp"
 #include "opencv2/highgui.hpp"
 
+void OrangeBallDetector::receive(EventImageCaptured e) {
+	detect(e.getCapturedImage());
+}
+
 void OrangeBallDetector::detect(cv::Mat image) {
-	
 	// Make the image of a reasonable size:
 	double width = image.size().width;
-	double scale = 600 / width;
-	resize(image, resizedImage, cv::Size(), scale, scale, cv::INTER_LINEAR);
-	showIfDebug(resizedImage);
-
-	GaussianBlur(resizedImage, blurImage, cv::Size(11, 11), 0);
-	showIfDebug(blurImage);
-
-	cvtColor(blurImage, hsvImage, cv::COLOR_BGR2HSV );
-	showIfDebug(hsvImage);
-
-	
-	// Detect the color orange: (Hue, Sat, Val)
-	auto lowOrange =  cv::Scalar(  7, 170, 120);
-	auto highOrange = cv::Scalar( 18, 255, 255);
-	inRange(hsvImage, lowOrange, highOrange, rangeImage);
-
-	// Smooth it out:
-	erode(rangeImage, erodedImage, 2);
-	dilate(erodedImage, dilatedImage, 2);
-	showIfDebug(dilatedImage);
-
-	// Detect contours:
-	std::vector<std::vector<cv::Point> > contours;
-	std::vector<cv::Vec4i> hierarchy;
-	findContours( dilatedImage, contours, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE );
-
-	// If any contour:
-	if (!contours.empty()) {
-
-		// Find the biggest contour:
-		double maxArea = -1;
-		std::vector<cv::Point> maxContour;
-		for (auto contour : contours) {
-			double area = cv::contourArea(contour);
-			if (area > maxArea) {
-				maxArea = area;
-				maxContour = contour;
-			}
-		}
-		
-		// Assuming that biggest contour is a circle,
-		// determine its center and size:
-		cv::minEnclosingCircle(maxContour, ballPosition, radius);
+	if (width > 0) {
+		double scale = 600 / width;
+		resize(image, capturedImage, cv::Size(), scale, scale, cv::INTER_LINEAR);
+		showIfDebug(capturedImage);
+
+		GaussianBlur(capturedImage, blurImage, cv::Size(11, 11), 0);
+		showIfDebug(blurImage);
+
+		cvtColor(blurImage, hsvImage, cv::COLOR_BGR2HSV );
+		showIfDebug(hsvImage);
+
 		
-		// Draw the circle:
-		if (radius > 20) {
-			cv::circle(resizedImage, ballPosition, radius, cv::Scalar(0, 255, 0), 3);
+		// Detect the color orange: (Hue, Sat, Val)
+		auto lowOrange =  cv::Scalar(  7, 170, 120);
+		auto highOrange = cv::Scalar( 18, 255, 255);
+		inRange(hsvImage, lowOrange, highOrange, rangeImage);
+
+		// Smooth it out:
+		erode(rangeImage, erodedImage, 2);
+		dilate(erodedImage, dilatedImage, 2);
+		showIfDebug(dilatedImage);
+
+		// Detect contours:
+		std::vector<std::vector<cv::Point> > contours;
+		std::vector<cv::Vec4i> hierarchy;
+		findContours( dilatedImage, contours, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE );
+
+		// If any contour:
+		if (!contours.empty()) {
+
+			// Find the biggest contour:
+			double maxArea = -1;
+			std::vector<cv::Point> maxContour;
+			for (auto contour : contours) {
+				double area = cv::contourArea(contour);
+				if (area > maxArea) {
+					maxArea = area;
+					maxContour = contour;
+				}
+			}
+			
+			// 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(capturedImage, ballPosition, radius, cv::Scalar(0, 255, 0), 3);
+			}
+			showIfDebug(capturedImage);
+			eventBus.propagate(EventOrangeDetected(capturedImage, ballPosition, radius));
 		}
-		showIfDebug(resizedImage);
 	}
 }
 
@@ -71,6 +77,10 @@ void OrangeBallDetector::showIfDebug(cv::Mat m) {
 	}
 }
 
+cv::Mat OrangeBallDetector::getCapturedImage() {
+	return capturedImage;
+}
+
 cv::Point2f OrangeBallDetector::getBallPosition() {
 	return ballPosition;
 }
@@ -79,6 +89,30 @@ float OrangeBallDetector::getRadius() {
 	return radius;
 }
 
-cv::Mat OrangeBallDetector::getImage() {
-	return resizedImage;
+
+EventOrangeDetected::EventOrangeDetected(cv::Mat i):EventOrangeDetected(i, cv::Point2f(0, 0), 0) {
+	// Nothing to do.
+}
+
+EventOrangeDetected::EventOrangeDetected(cv::Mat i, cv::Point2f bp, float r):
+capturedImage(i),
+ballPosition(bp),
+radius(r) {
+	// Nothing to do
+}
+
+cv::Mat EventOrangeDetected::getCapturedImage() {
+	return capturedImage;
+}
+
+cv::Point2f EventOrangeDetected::getBallPosition() {
+	return ballPosition;
+}
+
+float EventOrangeDetected::getRadius() {
+	return radius;
+}
+
+bool EventOrangeDetected::hasDetectedSomething() {
+	return radius > 10;
 }
diff --git a/src/lib/hpp/capture-image-from-camera.hpp b/src/lib/hpp/capture-image-from-camera.hpp
new file mode 100644
index 0000000..d62e0f4
--- /dev/null
+++ b/src/lib/hpp/capture-image-from-camera.hpp
@@ -0,0 +1,32 @@
+#ifndef CAPTURE_IMAGE_FROM_CAMERA_HPP
+#define CAPTURE_IMAGE_FROM_CAMERA_HPP
+
+#include <thread>
+#include <opencv2/highgui.hpp>
+#include "event-bus.hpp"
+
+class EventImageCaptured {
+public:
+	EventImageCaptured(cv::Mat i);
+	virtual ~EventImageCaptured() = default;
+	cv::Mat getCapturedImage();
+private:
+	cv::Mat capturedImage;
+};
+
+class CaptureImageFromCamera {
+public:
+	CaptureImageFromCamera();
+	virtual ~CaptureImageFromCamera();
+	void startCapturing();
+	void stopCapturing();
+private:
+	bool keepCapturing;
+	void doCapture();
+	std::thread* captureThread;
+	cv::VideoCapture videoCapture;
+	cv::Mat webcam;
+	EventBus<EventImageCaptured> eventBus;
+};
+
+#endif
diff --git a/src/lib/hpp/event-bus.hpp b/src/lib/hpp/event-bus.hpp
index 6b52816..363e3f5 100644
--- a/src/lib/hpp/event-bus.hpp
+++ b/src/lib/hpp/event-bus.hpp
@@ -48,9 +48,9 @@ vector<Subscriptor<T> *> SubscriptorsHolder<T>::subscriptors;
 /** @brief Propagates the events to all subscriptors in the list.
  */
 template <class T>
-class EventBusService {
+class EventBus {
 public:
-	virtual ~EventBusService() = default;
+	virtual ~EventBus() = default;
 	
 	/** @brief empties the list of subscribers.
 	 * You should not use this outside unit testing.
diff --git a/src/lib/hpp/orange-ball-detector.hpp b/src/lib/hpp/orange-ball-detector.hpp
index 6f610be..1c8d6a6 100644
--- a/src/lib/hpp/orange-ball-detector.hpp
+++ b/src/lib/hpp/orange-ball-detector.hpp
@@ -2,18 +2,37 @@
 #define ORANGE_BALL_DETECTOR_HPP
 
 #include <opencv2/highgui.hpp>
+#include "event-bus.hpp"
+#include "capture-image-from-camera.hpp"
 
-class OrangeBallDetector {
+class EventOrangeDetected {
+	friend class OrangeBallDetector;
 public:
-	virtual ~OrangeBallDetector() = default;
-	void detect(cv::Mat image);
+	cv::Mat getCapturedImage();
 	cv::Point2f getBallPosition();
 	float getRadius();
-	void setDebug(bool d);
-	cv::Mat getImage();
+	bool hasDetectedSomething();
+private:
+	EventOrangeDetected(cv::Mat i);
+	EventOrangeDetected(cv::Mat i, cv::Point2f bp, float r);
+	cv::Mat capturedImage;
+	cv::Point2f ballPosition;
+	float radius;
+};
 
+class OrangeBallDetector :
+public Subscriptor<EventImageCaptured> {
+public:
+	void receive(EventImageCaptured e);
+	void detect(cv::Mat image);
+	virtual ~OrangeBallDetector() = default;
+	void setDebug(bool d);
+	cv::Mat getCapturedImage();
+	cv::Point2f getBallPosition();
+	float getRadius();
+	
 private:
-	cv::Mat resizedImage;
+	cv::Mat capturedImage;
 	cv::Mat blurImage;
 	cv::Mat hsvImage;
 	cv::Mat rangeImage;
@@ -22,9 +41,11 @@ class OrangeBallDetector {
 
 	cv::Point2f ballPosition;
 	float radius;
-	
+
 	void showIfDebug(cv::Mat m);
 	bool debug = false;
+	
+	EventBus<EventOrangeDetected> eventBus;
 };
 
 #endif
diff --git a/src/lib/tst/event-bus-test.cpp b/src/lib/tst/event-bus-test.cpp
index d83a900..7ac0bd0 100644
--- a/src/lib/tst/event-bus-test.cpp
+++ b/src/lib/tst/event-bus-test.cpp
@@ -49,7 +49,7 @@ using namespace std::placeholders;
 using namespace std;
 
 SCENARIO("The Event Bus Service broadcasts to all subscribers of this event") {
-	EventBusService<AnyEvent> eventBus;
+	EventBus<AnyEvent> eventBus;
 
 	GIVEN( "Two classes subscribed to the same event") {
 		SubscriberToAllEvents subscriber1(10), subscriber2(20);
@@ -79,8 +79,8 @@ SCENARIO("The Event Bus Service broadcasts to all subscribers of this event") {
 
 
 SCENARIO("The Event Bus Service broatcasts only to subscribers of this event") {
-	EventBusService<AnyEvent> eventBus1;
-	EventBusService<OtherEvent> eventBus2;
+	EventBus<AnyEvent> eventBus1;
+	EventBus<OtherEvent> eventBus2;
 	
 	GIVEN( "Two classes subscribed to different event") {
 		eventBus1.clear();
@@ -102,8 +102,8 @@ SCENARIO("The Event Bus Service broatcasts only to subscribers of this event") {
 }
 
 SCENARIO("The Event Bus Service is a singleton") {
-	EventBusService<AnyEvent> eventBus1;
-	EventBusService<AnyEvent> eventBus2;
+	EventBus<AnyEvent> eventBus1;
+	EventBus<AnyEvent> eventBus2;
 	
 	GIVEN( "Two classes subscribed to same event using different instances of same event bus") {
 		eventBus1.clear();

From a259644b2e1c99e7e5a3017c851616b956da7ae1 Mon Sep 17 00:00:00 2001
From: jean-michel-gonet <jmgonet@yahoo.com>
Date: Mon, 31 Dec 2018 11:15:13 +0100
Subject: [PATCH 03/35] Step2 - Use event bus to pass the captured image - test
 it

---
 src/lib/cpp/orange-ball-detector.cpp      |  25 ++++---
 src/lib/hpp/orange-ball-detector.hpp      |   3 +-
 src/lib/tst/orange-ball-detector-test.cpp |  78 ++++++++++++++++++----
 src/lib/tst/res/broccoli-01.jpg           | Bin 0 -> 127971 bytes
 4 files changed, 84 insertions(+), 22 deletions(-)
 create mode 100644 src/lib/tst/res/broccoli-01.jpg

diff --git a/src/lib/cpp/orange-ball-detector.cpp b/src/lib/cpp/orange-ball-detector.cpp
index f46efee..0a7b16a 100644
--- a/src/lib/cpp/orange-ball-detector.cpp
+++ b/src/lib/cpp/orange-ball-detector.cpp
@@ -12,11 +12,15 @@ void OrangeBallDetector::detect(cv::Mat image) {
 	// Make the image of a reasonable size:
 	double width = image.size().width;
 	if (width > 0) {
+		radius = 0;
+		ballPosition.x = 0;
+		ballPosition.y = 0;
+
 		double scale = 600 / width;
-		resize(image, capturedImage, cv::Size(), scale, scale, cv::INTER_LINEAR);
-		showIfDebug(capturedImage);
+		resize(image, resizedImage, cv::Size(), scale, scale, cv::INTER_LINEAR);
+		showIfDebug(resizedImage);
 
-		GaussianBlur(capturedImage, blurImage, cv::Size(11, 11), 0);
+		GaussianBlur(resizedImage, blurImage, cv::Size(11, 11), 0);
 		showIfDebug(blurImage);
 
 		cvtColor(blurImage, hsvImage, cv::COLOR_BGR2HSV );
@@ -58,11 +62,11 @@ void OrangeBallDetector::detect(cv::Mat image) {
 			
 			// Draw the circle:
 			if (radius > 20) {
-				cv::circle(capturedImage, ballPosition, radius, cv::Scalar(0, 255, 0), 3);
+				cv::circle(resizedImage, ballPosition, radius, cv::Scalar(0, 255, 0), 3);
 			}
-			showIfDebug(capturedImage);
-			eventBus.propagate(EventOrangeDetected(capturedImage, ballPosition, radius));
+			showIfDebug(resizedImage);
 		}
+		eventBus.propagate(EventOrangeDetected(resizedImage, ballPosition, radius));
 	}
 }
 
@@ -78,7 +82,7 @@ void OrangeBallDetector::showIfDebug(cv::Mat m) {
 }
 
 cv::Mat OrangeBallDetector::getCapturedImage() {
-	return capturedImage;
+	return resizedImage;
 }
 
 cv::Point2f OrangeBallDetector::getBallPosition() {
@@ -89,8 +93,13 @@ float OrangeBallDetector::getRadius() {
 	return radius;
 }
 
+EventOrangeDetected::EventOrangeDetected():
+EventOrangeDetected(cv::Mat(), cv::Point2f(), 0) {
+	// Nothing to do.
+}
 
-EventOrangeDetected::EventOrangeDetected(cv::Mat i):EventOrangeDetected(i, cv::Point2f(0, 0), 0) {
+EventOrangeDetected::EventOrangeDetected(cv::Mat i):
+EventOrangeDetected(i, cv::Point2f(), 0) {
 	// Nothing to do.
 }
 
diff --git a/src/lib/hpp/orange-ball-detector.hpp b/src/lib/hpp/orange-ball-detector.hpp
index 1c8d6a6..10d9298 100644
--- a/src/lib/hpp/orange-ball-detector.hpp
+++ b/src/lib/hpp/orange-ball-detector.hpp
@@ -8,6 +8,7 @@
 class EventOrangeDetected {
 	friend class OrangeBallDetector;
 public:
+	EventOrangeDetected();
 	cv::Mat getCapturedImage();
 	cv::Point2f getBallPosition();
 	float getRadius();
@@ -32,7 +33,7 @@ public Subscriptor<EventImageCaptured> {
 	float getRadius();
 	
 private:
-	cv::Mat capturedImage;
+	cv::Mat resizedImage;
 	cv::Mat blurImage;
 	cv::Mat hsvImage;
 	cv::Mat rangeImage;
diff --git a/src/lib/tst/orange-ball-detector-test.cpp b/src/lib/tst/orange-ball-detector-test.cpp
index 7fb1488..6f46d5e 100644
--- a/src/lib/tst/orange-ball-detector-test.cpp
+++ b/src/lib/tst/orange-ball-detector-test.cpp
@@ -10,18 +10,15 @@ using namespace std;
 using namespace cv;
 
 using Catch::Matchers::WithinAbs;
-
 SCENARIO("Can detect orange balls in an image") {
 	Mat mat;
-	string pathToTestData = PATH_TO_TEST_DATA;
 	
 	GIVEN( "An initialized ball detector") {
 		OrangeBallDetector orangeBallDetector;
-		orangeBallDetector.setDebug(true);
+		orangeBallDetector.setDebug(false);
 
 		WHEN( "Shown with a geek holding a cap") {
-			string ball01 = string(pathToTestData);
-			ball01.append("/orange-01.jpg");
+			string ball01 = string(PATH_TO_TEST_DATA).append("/orange-01.jpg");
 			
 			mat = imread(ball01);
 			orangeBallDetector.detect(mat);
@@ -31,8 +28,7 @@ SCENARIO("Can detect orange balls in an image") {
 			}
 		}
 		WHEN( "Shown with a geek holding a cap (2)") {
-			string ball01 = string(pathToTestData);
-			ball01.append("/orange-02.jpg");
+			string ball01 = string(PATH_TO_TEST_DATA).append("/orange-02.jpg");
 			
 			mat = imread(ball01);
 			orangeBallDetector.detect(mat);
@@ -43,8 +39,7 @@ SCENARIO("Can detect orange balls in an image") {
 		}
 
 		WHEN( "Shown with one nice lady") {
-			string ball01 = string(pathToTestData);
-			ball01.append("/ball-01.jpg");
+			string ball01 = string(PATH_TO_TEST_DATA).append("/ball-01.jpg");
 			
 			mat = imread(ball01);
 			orangeBallDetector.detect(mat);
@@ -55,8 +50,7 @@ SCENARIO("Can detect orange balls in an image") {
 		}
 		
 		WHEN( "Shown with a hand grabbing an orange") {
-			string ball01 = string(pathToTestData);
-			ball01.append("/ball-02.jpg");
+			string ball01 = string(PATH_TO_TEST_DATA).append("/ball-02.jpg");
 			
 			mat = imread(ball01);
 			orangeBallDetector.detect(mat);
@@ -67,8 +61,7 @@ SCENARIO("Can detect orange balls in an image") {
 		}
 
 		WHEN( "Shown with a lady holding a basked ball") {
-			string ball01 = string(pathToTestData);
-			ball01.append("/ball-03.jpg");
+			string ball01 = string(PATH_TO_TEST_DATA).append("/ball-03.jpg");
 			
 			mat = imread(ball01);
 			orangeBallDetector.detect(mat);
@@ -77,7 +70,66 @@ SCENARIO("Can detect orange balls in an image") {
 				REQUIRE_THAT( orangeBallDetector.getBallPosition().x, WithinAbs(360, 10));
 			}
 		}
+		WHEN( "Shown with a broccoli") {
+			string ball01 = string(PATH_TO_TEST_DATA).append("/broccoli-01.jpg");
+			
+			mat = imread(ball01);
+			orangeBallDetector.detect(mat);
+			
+			THEN ( "Won't find the ball") {
+				REQUIRE( orangeBallDetector.getBallPosition().x == 0);
+				REQUIRE( orangeBallDetector.getRadius() == 0);
+			}
+		}
 
 	}
 }
 
+class Receiver : public Subscriptor<EventOrangeDetected> {
+public:
+	void receive(EventOrangeDetected e) {
+		eventOrangeDetected = e;
+	}
+	EventOrangeDetected getEvent() {
+		return eventOrangeDetected;
+	}
+private:
+	EventOrangeDetected eventOrangeDetected;
+};
+
+SCENARIO("Sends and receives events") {
+	GIVEN ("An orange detector subscribed to image events") {
+		Receiver receiver;
+		EventBus<EventOrangeDetected> eventBusOranges;
+		eventBusOranges.subscribe(&receiver);
+		
+		EventBus<EventImageCaptured> eventBusImages;
+		OrangeBallDetector orangeBallDetector;
+		eventBusImages.subscribe(&orangeBallDetector);
+		
+		WHEN("Propagating a new image event containing a detectable orange") {
+			string ball01 = string(PATH_TO_TEST_DATA).append("/orange-01.jpg");
+			Mat mat = imread(ball01);
+
+			eventBusImages.propagate(EventImageCaptured(mat));
+			
+			THEN("It emits an orange detected event") {
+				EventOrangeDetected e = receiver.getEvent();
+				REQUIRE( e.hasDetectedSomething() );
+			}
+		}
+		WHEN("Propagating a new image event NOT containing a detectable orange") {
+			string ball01 = string(PATH_TO_TEST_DATA).append("/broccoli-01.jpg");
+			Mat mat = imread(ball01);
+			
+			eventBusImages.propagate(EventImageCaptured(mat));
+			
+			THEN("It emits an orange detected event") {
+				EventOrangeDetected e = receiver.getEvent();
+				REQUIRE( e.hasDetectedSomething() == false );
+			}
+		}
+	}
+}
+
+
diff --git a/src/lib/tst/res/broccoli-01.jpg b/src/lib/tst/res/broccoli-01.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..a476a104dc3a6ed3439cfff83365010dba0e39c5
GIT binary patch

From 4d501cc3cfa3050d5d76fd99e597f4385bb16f14 Mon Sep 17 00:00:00 2001
From: jean-michel-gonet <jmgonet@yahoo.com>
Date: Mon, 31 Dec 2018 11:40:52 +0100
Subject: [PATCH 04/35] Step2 - Use event bus to pass the captured image - test
 it even better

---
 src/lib/tst/orange-ball-detector-test.cpp | 124 ++++++++++------------
 1 file changed, 54 insertions(+), 70 deletions(-)

diff --git a/src/lib/tst/orange-ball-detector-test.cpp b/src/lib/tst/orange-ball-detector-test.cpp
index 6f46d5e..c41c242 100644
--- a/src/lib/tst/orange-ball-detector-test.cpp
+++ b/src/lib/tst/orange-ball-detector-test.cpp
@@ -10,31 +10,57 @@ using namespace std;
 using namespace cv;
 
 using Catch::Matchers::WithinAbs;
+
+// A trivial receiver, to obtain the responses from
+// the orange detector:
+class Receiver : public Subscriptor<EventOrangeDetected> {
+public:
+	void receive(EventOrangeDetected e) {
+		eventOrangeDetected = e;
+	}
+	EventOrangeDetected getEvent() {
+		return eventOrangeDetected;
+	}
+private:
+	EventOrangeDetected eventOrangeDetected;
+};
+
 SCENARIO("Can detect orange balls in an image") {
+	EventBus<EventImageCaptured> capturedImageEventBus;
+	EventBus<EventOrangeDetected> orangeDetectedEventBus;
+
+	Receiver receiver;
+	orangeDetectedEventBus.subscribe(&receiver);
+
 	Mat mat;
-	
-	GIVEN( "An initialized ball detector") {
+
+	GIVEN( "A ball detector subscribed to image captures") {
 		OrangeBallDetector orangeBallDetector;
-		orangeBallDetector.setDebug(false);
+		capturedImageEventBus.subscribe(&orangeBallDetector);
+		orangeBallDetector.setDebug(false); // true to see intermediary images.
 
 		WHEN( "Shown with a geek holding a cap") {
 			string ball01 = string(PATH_TO_TEST_DATA).append("/orange-01.jpg");
 			
 			mat = imread(ball01);
-			orangeBallDetector.detect(mat);
+			capturedImageEventBus.propagate(EventImageCaptured(mat));
 			
 			THEN ( "Can find the cap") {
-				REQUIRE_THAT( orangeBallDetector.getBallPosition().x, WithinAbs(435, 10));
+				EventOrangeDetected e = receiver.getEvent();
+				REQUIRE(e.hasDetectedSomething() );
+				REQUIRE_THAT(e.getBallPosition().x, WithinAbs(435, 10));
 			}
 		}
+
 		WHEN( "Shown with a geek holding a cap (2)") {
 			string ball01 = string(PATH_TO_TEST_DATA).append("/orange-02.jpg");
 			
 			mat = imread(ball01);
-			orangeBallDetector.detect(mat);
-			
+			capturedImageEventBus.propagate(EventImageCaptured(mat));
 			THEN ( "Can find the cap (2)") {
-				REQUIRE_THAT( orangeBallDetector.getBallPosition().x, WithinAbs(240, 10));
+				EventOrangeDetected e = receiver.getEvent();
+				REQUIRE(e.hasDetectedSomething());
+				REQUIRE_THAT(e.getBallPosition().x, WithinAbs(240, 10));
 			}
 		}
 
@@ -42,10 +68,12 @@ SCENARIO("Can detect orange balls in an image") {
 			string ball01 = string(PATH_TO_TEST_DATA).append("/ball-01.jpg");
 			
 			mat = imread(ball01);
-			orangeBallDetector.detect(mat);
-			
+			capturedImageEventBus.propagate(EventImageCaptured(mat));
+
 			THEN ( "Can find the ball") {
-				REQUIRE_THAT( orangeBallDetector.getBallPosition().x, WithinAbs(360, 10));
+				EventOrangeDetected e = receiver.getEvent();
+				REQUIRE(e.hasDetectedSomething());
+				REQUIRE_THAT(e.getBallPosition().x, WithinAbs(360, 10));
 			}
 		}
 		
@@ -53,10 +81,12 @@ SCENARIO("Can detect orange balls in an image") {
 			string ball01 = string(PATH_TO_TEST_DATA).append("/ball-02.jpg");
 			
 			mat = imread(ball01);
-			orangeBallDetector.detect(mat);
-			
+			capturedImageEventBus.propagate(EventImageCaptured(mat));
+
 			THEN ( "Sadly, misses the orange") {
-				REQUIRE_THAT( orangeBallDetector.getBallPosition().x, WithinAbs(215, 10));
+				EventOrangeDetected e = receiver.getEvent();
+				REQUIRE(e.hasDetectedSomething());
+				REQUIRE_THAT(e.getBallPosition().x, WithinAbs(215, 10));
 			}
 		}
 
@@ -64,72 +94,26 @@ SCENARIO("Can detect orange balls in an image") {
 			string ball01 = string(PATH_TO_TEST_DATA).append("/ball-03.jpg");
 			
 			mat = imread(ball01);
-			orangeBallDetector.detect(mat);
-			
+			capturedImageEventBus.propagate(EventImageCaptured(mat));
+
 			THEN ( "Can find the ball") {
-				REQUIRE_THAT( orangeBallDetector.getBallPosition().x, WithinAbs(360, 10));
+				EventOrangeDetected e = receiver.getEvent();
+				REQUIRE(e.hasDetectedSomething());
+				REQUIRE_THAT( e.getBallPosition().x, WithinAbs(360, 10));
 			}
 		}
 		WHEN( "Shown with a broccoli") {
 			string ball01 = string(PATH_TO_TEST_DATA).append("/broccoli-01.jpg");
 			
 			mat = imread(ball01);
-			orangeBallDetector.detect(mat);
-			
-			THEN ( "Won't find the ball") {
-				REQUIRE( orangeBallDetector.getBallPosition().x == 0);
-				REQUIRE( orangeBallDetector.getRadius() == 0);
-			}
-		}
-
-	}
-}
-
-class Receiver : public Subscriptor<EventOrangeDetected> {
-public:
-	void receive(EventOrangeDetected e) {
-		eventOrangeDetected = e;
-	}
-	EventOrangeDetected getEvent() {
-		return eventOrangeDetected;
-	}
-private:
-	EventOrangeDetected eventOrangeDetected;
-};
+			capturedImageEventBus.propagate(EventImageCaptured(mat));
 
-SCENARIO("Sends and receives events") {
-	GIVEN ("An orange detector subscribed to image events") {
-		Receiver receiver;
-		EventBus<EventOrangeDetected> eventBusOranges;
-		eventBusOranges.subscribe(&receiver);
-		
-		EventBus<EventImageCaptured> eventBusImages;
-		OrangeBallDetector orangeBallDetector;
-		eventBusImages.subscribe(&orangeBallDetector);
-		
-		WHEN("Propagating a new image event containing a detectable orange") {
-			string ball01 = string(PATH_TO_TEST_DATA).append("/orange-01.jpg");
-			Mat mat = imread(ball01);
-
-			eventBusImages.propagate(EventImageCaptured(mat));
-			
-			THEN("It emits an orange detected event") {
-				EventOrangeDetected e = receiver.getEvent();
-				REQUIRE( e.hasDetectedSomething() );
-			}
-		}
-		WHEN("Propagating a new image event NOT containing a detectable orange") {
-			string ball01 = string(PATH_TO_TEST_DATA).append("/broccoli-01.jpg");
-			Mat mat = imread(ball01);
-			
-			eventBusImages.propagate(EventImageCaptured(mat));
-			
-			THEN("It emits an orange detected event") {
+			THEN ( "Won't find the ball") {
 				EventOrangeDetected e = receiver.getEvent();
-				REQUIRE( e.hasDetectedSomething() == false );
+				REQUIRE( e.hasDetectedSomething() == false);
 			}
 		}
+		capturedImageEventBus.unsubscribe(&orangeBallDetector);
 	}
+	orangeDetectedEventBus.unsubscribe(&receiver);
 }
-
-

From 6860bf48d1acc7243132d2a5a7946888db57938a Mon Sep 17 00:00:00 2001
From: jean-michel-gonet <jmgonet@yahoo.com>
Date: Mon, 31 Dec 2018 13:40:29 +0100
Subject: [PATCH 05/35] Step2 - Use event bus to pass the captured image - test
 it even better

---
 src/lib/tst/orange-ball-detector-test.cpp | 32 +++++++++++------------
 1 file changed, 16 insertions(+), 16 deletions(-)

diff --git a/src/lib/tst/orange-ball-detector-test.cpp b/src/lib/tst/orange-ball-detector-test.cpp
index c41c242..a5e393c 100644
--- a/src/lib/tst/orange-ball-detector-test.cpp
+++ b/src/lib/tst/orange-ball-detector-test.cpp
@@ -26,23 +26,23 @@ class Receiver : public Subscriptor<EventOrangeDetected> {
 };
 
 SCENARIO("Can detect orange balls in an image") {
-	EventBus<EventImageCaptured> capturedImageEventBus;
-	EventBus<EventOrangeDetected> orangeDetectedEventBus;
-
-	Receiver receiver;
-	orangeDetectedEventBus.subscribe(&receiver);
-
-	Mat mat;
-
 	GIVEN( "A ball detector subscribed to image captures") {
+
+		EventBus<EventImageCaptured> capturedImageEventBus;
 		OrangeBallDetector orangeBallDetector;
 		capturedImageEventBus.subscribe(&orangeBallDetector);
-		orangeBallDetector.setDebug(false); // true to see intermediary images.
+
+		EventBus<EventOrangeDetected> orangeDetectedEventBus;
+		Receiver receiver;
+		orangeDetectedEventBus.subscribe(&receiver);
+
+		// Set debug to 'true' to see intermediary images:
+		orangeBallDetector.setDebug(false);
 
 		WHEN( "Shown with a geek holding a cap") {
 			string ball01 = string(PATH_TO_TEST_DATA).append("/orange-01.jpg");
 			
-			mat = imread(ball01);
+			Mat mat = imread(ball01);
 			capturedImageEventBus.propagate(EventImageCaptured(mat));
 			
 			THEN ( "Can find the cap") {
@@ -55,7 +55,7 @@ SCENARIO("Can detect orange balls in an image") {
 		WHEN( "Shown with a geek holding a cap (2)") {
 			string ball01 = string(PATH_TO_TEST_DATA).append("/orange-02.jpg");
 			
-			mat = imread(ball01);
+			Mat mat = imread(ball01);
 			capturedImageEventBus.propagate(EventImageCaptured(mat));
 			THEN ( "Can find the cap (2)") {
 				EventOrangeDetected e = receiver.getEvent();
@@ -67,7 +67,7 @@ SCENARIO("Can detect orange balls in an image") {
 		WHEN( "Shown with one nice lady") {
 			string ball01 = string(PATH_TO_TEST_DATA).append("/ball-01.jpg");
 			
-			mat = imread(ball01);
+			Mat mat = imread(ball01);
 			capturedImageEventBus.propagate(EventImageCaptured(mat));
 
 			THEN ( "Can find the ball") {
@@ -80,7 +80,7 @@ SCENARIO("Can detect orange balls in an image") {
 		WHEN( "Shown with a hand grabbing an orange") {
 			string ball01 = string(PATH_TO_TEST_DATA).append("/ball-02.jpg");
 			
-			mat = imread(ball01);
+			Mat mat = imread(ball01);
 			capturedImageEventBus.propagate(EventImageCaptured(mat));
 
 			THEN ( "Sadly, misses the orange") {
@@ -93,7 +93,7 @@ SCENARIO("Can detect orange balls in an image") {
 		WHEN( "Shown with a lady holding a basked ball") {
 			string ball01 = string(PATH_TO_TEST_DATA).append("/ball-03.jpg");
 			
-			mat = imread(ball01);
+			Mat mat = imread(ball01);
 			capturedImageEventBus.propagate(EventImageCaptured(mat));
 
 			THEN ( "Can find the ball") {
@@ -105,7 +105,7 @@ SCENARIO("Can detect orange balls in an image") {
 		WHEN( "Shown with a broccoli") {
 			string ball01 = string(PATH_TO_TEST_DATA).append("/broccoli-01.jpg");
 			
-			mat = imread(ball01);
+			Mat mat = imread(ball01);
 			capturedImageEventBus.propagate(EventImageCaptured(mat));
 
 			THEN ( "Won't find the ball") {
@@ -114,6 +114,6 @@ SCENARIO("Can detect orange balls in an image") {
 			}
 		}
 		capturedImageEventBus.unsubscribe(&orangeBallDetector);
+		orangeDetectedEventBus.unsubscribe(&receiver);
 	}
-	orangeDetectedEventBus.unsubscribe(&receiver);
 }

From 93a0d2d8f8c53e55bfacaa09e06ea57534ea9b1f Mon Sep 17 00:00:00 2001
From: jean-michel-gonet <jmgonet@yahoo.com>
Date: Mon, 31 Dec 2018 19:18:32 +0100
Subject: [PATCH 06/35] Step2 - Use bus events for movie maker

---
 src/app/cpp/camera-drawing-area.cpp       | 12 ++----------
 src/app/cpp/main.cpp                      | 12 +++++++++---
 src/app/hpp/camera-drawing-area.hpp       |  6 ++----
 src/lib/cpp/movie-maker.cpp               |  8 ++++----
 src/lib/hpp/capture-image-from-camera.hpp |  6 ++++--
 src/lib/hpp/movie-maker.hpp               |  7 ++++++-
 src/lib/tst/movie-maker-test.cpp          | 21 ++++++++++++---------
 7 files changed, 39 insertions(+), 33 deletions(-)

diff --git a/src/app/cpp/camera-drawing-area.cpp b/src/app/cpp/camera-drawing-area.cpp
index 66a2686..ca9b953 100644
--- a/src/app/cpp/camera-drawing-area.cpp
+++ b/src/app/cpp/camera-drawing-area.cpp
@@ -1,14 +1,8 @@
-#include "opencv2/imgproc.hpp"
-#include "opencv2/core.hpp"
-#include "opencv2/highgui.hpp"
-
 #include "camera-drawing-area.hpp"
 
-#include <unistd.h>
-#include <sys/types.h>
+#include <opencv2/imgproc.hpp>
 
-CameraDrawingArea::CameraDrawingArea():
-movieMaker("./live.avi", 20.0) {
+CameraDrawingArea::CameraDrawingArea() {
 	eventBus.subscribe(this);
 }
 
@@ -44,8 +38,6 @@ void CameraDrawingArea::on_size_allocate (Gtk::Allocation& allocation) {
  */
 bool CameraDrawingArea::on_draw(const Cairo::RefPtr<Cairo::Context>& cr) {
 	if (webcam.size().width > 0) {
-		// Stream it in video:
-		movieMaker.addPhotogram(webcam);
 		
 		// Resize it to the allocated size of the Widget.
 		resize(webcam, output, cv::Size(width, height), 0, 0, cv::INTER_LINEAR);
diff --git a/src/app/cpp/main.cpp b/src/app/cpp/main.cpp
index 4905dd8..fa81f64 100644
--- a/src/app/cpp/main.cpp
+++ b/src/app/cpp/main.cpp
@@ -1,14 +1,20 @@
 #include "main-window.hpp"
-#include "orange-ball-detector.hpp"
+
 #include "capture-image-from-camera.hpp"
 #include "event-bus.hpp"
+#include "movie-maker.hpp"
+#include "orange-ball-detector.hpp"
+
 int main (int argc, char *argv[]) {
-	
+
 	CaptureImageFromCamera captureImageFromCamera;
 	OrangeBallDetector orangeBallDetector;
+	MovieMaker movieMaker("/Users/jmgonet/Desktop/live.avi", 20.0);
+
 	EventBus<EventImageCaptured> eventImageBus;
 	eventImageBus.subscribe(&orangeBallDetector);
-	
+	eventImageBus.subscribe(&movieMaker);
+
 	auto app = Gtk::Application::create(argc, argv, "ch.agl-developpement.cpp-tutorial.raspberry-cpp-gtk-opencv");
 	MainWindow mainWindow(300, 300);
 	return app->run(mainWindow);	
diff --git a/src/app/hpp/camera-drawing-area.hpp b/src/app/hpp/camera-drawing-area.hpp
index 557677b..bd4d776 100644
--- a/src/app/hpp/camera-drawing-area.hpp
+++ b/src/app/hpp/camera-drawing-area.hpp
@@ -1,12 +1,11 @@
 #ifndef CAMERA_DRAWING_AREA_H
 #define CAMERA_DRAWING_AREA_H
 
+#include "orange-ball-detector.hpp"
+
 #include <opencv2/highgui.hpp>
 #include <gtkmm.h>
 
-#include "movie-maker.hpp"
-#include "orange-ball-detector.hpp"
-
 class CameraDrawingArea :
 public Gtk::DrawingArea,
 public Subscriptor<EventOrangeDetected> {
@@ -22,7 +21,6 @@ public Subscriptor<EventOrangeDetected> {
 	bool everyNowAndThen();
 
 private:
-	MovieMaker movieMaker;
 	cv::Mat webcam;
 	cv::Mat output;
 	int width, height;
diff --git a/src/lib/cpp/movie-maker.cpp b/src/lib/cpp/movie-maker.cpp
index c4f6819..db6f211 100644
--- a/src/lib/cpp/movie-maker.cpp
+++ b/src/lib/cpp/movie-maker.cpp
@@ -1,9 +1,5 @@
 #include "movie-maker.hpp"
 
-#include <opencv2/highgui.hpp>
-#include <opencv2/videoio.hpp>
-#include <cstring>
-
 using namespace std;
 using namespace cv;
 
@@ -19,6 +15,10 @@ MovieMaker::~MovieMaker() {
 	endMovie();
 }
 
+void MovieMaker::receive(EventImageCaptured e) {
+	addPhotogram(e.getCapturedImage());
+}
+
 void MovieMaker::addPhotogram(Mat photogram) {
 	if (photogram.size().width > 0) {
 		if (!isMovieStarted) {
diff --git a/src/lib/hpp/capture-image-from-camera.hpp b/src/lib/hpp/capture-image-from-camera.hpp
index d62e0f4..e5daba9 100644
--- a/src/lib/hpp/capture-image-from-camera.hpp
+++ b/src/lib/hpp/capture-image-from-camera.hpp
@@ -1,10 +1,12 @@
 #ifndef CAPTURE_IMAGE_FROM_CAMERA_HPP
 #define CAPTURE_IMAGE_FROM_CAMERA_HPP
 
-#include <thread>
-#include <opencv2/highgui.hpp>
 #include "event-bus.hpp"
 
+#include <opencv2/highgui.hpp>
+
+#include <thread>
+
 class EventImageCaptured {
 public:
 	EventImageCaptured(cv::Mat i);
diff --git a/src/lib/hpp/movie-maker.hpp b/src/lib/hpp/movie-maker.hpp
index 409dd29..4319dba 100644
--- a/src/lib/hpp/movie-maker.hpp
+++ b/src/lib/hpp/movie-maker.hpp
@@ -1,15 +1,20 @@
 #ifndef MOVIE_MAKER_HPP
 #define MOVIE_MAKER_HPP
 
+#include "capture-image-from-camera.hpp"
+#include "event-bus.hpp"
+
 #include <opencv2/videoio.hpp>
+
 #include <cstring>
 
-class MovieMaker {
+class MovieMaker: public Subscriptor<EventImageCaptured> {
 public:
 	MovieMaker(std::string fileName, double fps);
 	virtual ~MovieMaker();
 	void endMovie();
 	void addPhotogram(cv::Mat photogram);
+	void receive(EventImageCaptured e);
 private:
 	cv::VideoWriter videoWriter;
 	bool isMovieStarted;
diff --git a/src/lib/tst/movie-maker-test.cpp b/src/lib/tst/movie-maker-test.cpp
index b7ac4d2..d65c8f5 100644
--- a/src/lib/tst/movie-maker-test.cpp
+++ b/src/lib/tst/movie-maker-test.cpp
@@ -9,20 +9,22 @@ using namespace std;
 using namespace cv;
 
 SCENARIO("Can make a movie") {
-	Mat mat;
-	string pathToTestData = PATH_TO_TEST_DATA;
-	
 	GIVEN( "An initialized movie maker") {
 		string filename("./live.avi");
+
+		EventBus<EventImageCaptured> capturedImageEventBus;
 		MovieMaker movieMaker(filename, 10.0);
+		capturedImageEventBus.subscribe(&movieMaker);
 		
 		WHEN( "Fed with images") {
-			mat = imread(string(pathToTestData).append("/abc-a.png"));
-			movieMaker.addPhotogram(mat);
-			mat = imread(string(pathToTestData).append("/abc-b.png"));
-			movieMaker.addPhotogram(mat);
-			mat = imread(string(pathToTestData).append("/abc-c.png"));
-			movieMaker.addPhotogram(mat);
+			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));
+
 			movieMaker.endMovie();
 			
 			THEN ( "Can make a movie") {
@@ -40,6 +42,7 @@ SCENARIO("Can make a movie") {
 				REQUIRE( n == 3);
 			}
 		}
+		capturedImageEventBus.unsubscribe(&movieMaker);
 	}
 }
 

From 7b1d3bd4a2c27705c702a239ed92ac128194e25c Mon Sep 17 00:00:00 2001
From: jean-michel-gonet <jmgonet@yahoo.com>
Date: Sun, 13 Jan 2019 20:22:45 +0100
Subject: [PATCH 07/35] Close Conque when quitting

---
 .vimrc | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/.vimrc b/.vimrc
index a34b09d..ebe93d0 100644
--- a/.vimrc
+++ b/.vimrc
@@ -4,3 +4,8 @@ set cindent shiftwidth=4        " In C/C++, size of the tabulation increase each
 set tabstop=4                   " In all files, size of the Tab
 set expandtab                   " Use spaces instead of Tab
 autocmd vimenter * NERDTreeVCS  " Automatically opens NERDTree
+let g:ConqueTerm_StartMessages = 0
+let g:ConqueTerm_Color = 0
+let g:ConqueTerm_CloseOnEnd = 1
+let g:ConqueTerm_Interrupt = '<C-g><C-c>'
+let g:ConqueTerm_ReadUnfocused = 1

From 85e6cccf478b3afe5af6ea67e3778b95efe37b7f Mon Sep 17 00:00:00 2001
From: jean-michel-gonet <jmgonet@yahoo.com>
Date: Mon, 14 Jan 2019 19:48:56 +0100
Subject: [PATCH 08/35] Unsubscribe camera-drawing-area from eventbus when
 destroying

---
 src/app/cpp/camera-drawing-area.cpp | 3 +++
 src/app/hpp/camera-drawing-area.hpp | 2 +-
 2 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/src/app/cpp/camera-drawing-area.cpp b/src/app/cpp/camera-drawing-area.cpp
index 1d4bbb6..acc1e3f 100644
--- a/src/app/cpp/camera-drawing-area.cpp
+++ b/src/app/cpp/camera-drawing-area.cpp
@@ -6,6 +6,9 @@
 CameraDrawingArea::CameraDrawingArea() {
 	eventBus.subscribe(this);
 }
+CameraDrawingArea::~CameraDrawingArea() {
+	eventBus.unsubscribe(this);
+}
 
 /**
  * Each time we receive a new image, we invalidate the
diff --git a/src/app/hpp/camera-drawing-area.hpp b/src/app/hpp/camera-drawing-area.hpp
index 3bca519..030c33b 100644
--- a/src/app/hpp/camera-drawing-area.hpp
+++ b/src/app/hpp/camera-drawing-area.hpp
@@ -13,7 +13,7 @@ public Gtk::DrawingArea,
 public Subscriptor<EventOrangeDetected> {
 public:
     CameraDrawingArea();
-    virtual ~CameraDrawingArea() = default;
+    virtual ~CameraDrawingArea();
 	void receive(EventOrangeDetected e) override;
     
 protected:

From 703a855ac6f238030072fd4d4a4c52b520bd50d2 Mon Sep 17 00:00:00 2001
From: jean-michel-gonet <jmgonet@yahoo.com>
Date: Fri, 18 Jan 2019 19:28:42 +0100
Subject: [PATCH 09/35] Removed excessive verbosity

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

diff --git a/src/app/cpp/camera-drawing-area.cpp b/src/app/cpp/camera-drawing-area.cpp
index 5cd9ff4..4573a1d 100644
--- a/src/app/cpp/camera-drawing-area.cpp
+++ b/src/app/cpp/camera-drawing-area.cpp
@@ -15,10 +15,6 @@ CameraDrawingArea::~CameraDrawingArea() {
 	eventBus.unsubscribe(this);
 }
 
-/**
- * Each time we receive a new image, we invalidate the
- * whole Widget rectangle, forcing a complete refresh.
- */
 void CameraDrawingArea::receive(EventOrangeDetected e) {
 	resize(e.getCapturedImage(), output, cv::Size(width, height), 0, 0, cv::INTER_LINEAR);
     

From 5178ee6756ab14e3e317aa245e0b1a064b65681d Mon Sep 17 00:00:00 2001
From: jean-michel-gonet <jmgonet@yahoo.com>
Date: Fri, 18 Jan 2019 19:40:22 +0100
Subject: [PATCH 10/35] Do not propagate event if captured image is not valid

---
 src/lib/cpp/capture-image-from-camera.cpp |   6 +-
 src/lib/cpp/movie-maker.cpp               |  10 +-
 src/lib/cpp/orange-ball-detector.cpp      | 106 +++++++++++-----------
 3 files changed, 60 insertions(+), 62 deletions(-)

diff --git a/src/lib/cpp/capture-image-from-camera.cpp b/src/lib/cpp/capture-image-from-camera.cpp
index 3f9c188..0914d92 100644
--- a/src/lib/cpp/capture-image-from-camera.cpp
+++ b/src/lib/cpp/capture-image-from-camera.cpp
@@ -34,8 +34,10 @@ void CaptureImageFromCamera::doCapture() {
 		videoCapture.grab();
 		videoCapture.grab();
 		videoCapture.read(webcam);
-		eventBus.propagate(EventImageCaptured(webcam));
-	}
+        if (webcam.size().width > 0) {
+		    eventBus.propagate(EventImageCaptured(webcam));
+        }
+    }
 }
 
 EventImageCaptured::EventImageCaptured(cv::Mat i):
diff --git a/src/lib/cpp/movie-maker.cpp b/src/lib/cpp/movie-maker.cpp
index 7e8e731..db20b42 100644
--- a/src/lib/cpp/movie-maker.cpp
+++ b/src/lib/cpp/movie-maker.cpp
@@ -20,13 +20,11 @@ void MovieMaker::receive(EventImageCaptured e) {
 }
 
 void MovieMaker::addPhotogram(Mat photogram) {
-    if (photogram.size().width > 0) {
-        if (!isMovieStarted) {
-            startMovie(photogram);
-            isMovieStarted = true;
-        }
-        videoWriter.write(photogram);
+    if (!isMovieStarted) {
+        startMovie(photogram);
+        isMovieStarted = true;
     }
+    videoWriter.write(photogram);
 }
 
 void MovieMaker::endMovie() {
diff --git a/src/lib/cpp/orange-ball-detector.cpp b/src/lib/cpp/orange-ball-detector.cpp
index c566b45..e76637b 100644
--- a/src/lib/cpp/orange-ball-detector.cpp
+++ b/src/lib/cpp/orange-ball-detector.cpp
@@ -7,65 +7,63 @@ void OrangeBallDetector::receive(EventImageCaptured e) {
 }
 
 void OrangeBallDetector::detect(cv::Mat image) {
-    // Make the image of a reasonable size:
     double width = image.size().width;
-    if (width > 0) {
-        radius = 0;
-        ballPosition.x = 0;
-        ballPosition.y = 0;
-
-        double scale = 600 / width;
-        resize(image, resizedImage, cv::Size(), scale, scale, cv::INTER_LINEAR);
-        showIfDebug(resizedImage);
+    radius = 0;
+    ballPosition.x = 0;
+    ballPosition.y = 0;
 
-        GaussianBlur(resizedImage, blurImage, cv::Size(11, 11), 0);
-        showIfDebug(blurImage);
-
-        cvtColor(blurImage, hsvImage, cv::COLOR_BGR2HSV );
-        showIfDebug(hsvImage);
-
-        
-        // Detect the color orange: (Hue, Sat, Val)
-        auto lowOrange =  cv::Scalar(  7, 170, 120);
-        auto highOrange = cv::Scalar( 18, 255, 255);
-        inRange(hsvImage, lowOrange, highOrange, rangeImage);
-
-        // Smooth it out:
-        erode(rangeImage, erodedImage, 2);
-        dilate(erodedImage, dilatedImage, 2);
-        showIfDebug(dilatedImage);
-
-        // Detect contours:
-        std::vector<std::vector<cv::Point> > contours;
-        std::vector<cv::Vec4i> hierarchy;
-        findContours( dilatedImage, contours, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE );
-
-        // If any contour:
-        if (!contours.empty()) {
-
-            // Find the biggest contour:
-            double maxArea = -1;
-            std::vector<cv::Point> maxContour;
-            for (auto contour : contours) {
-                double area = cv::contourArea(contour);
-                if (area > maxArea) {
-                    maxArea = area;
-                    maxContour = contour;
-                }
-            }
-            
-            // 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);
+    // Make the image of a reasonable size:
+    double scale = 600 / width;
+    resize(image, resizedImage, cv::Size(), scale, scale, cv::INTER_LINEAR);
+    showIfDebug(resizedImage);
+
+    GaussianBlur(resizedImage, blurImage, cv::Size(11, 11), 0);
+    showIfDebug(blurImage);
+
+    cvtColor(blurImage, hsvImage, cv::COLOR_BGR2HSV );
+    showIfDebug(hsvImage);
+
+
+    // Detect the color orange: (Hue, Sat, Val)
+    auto lowOrange =  cv::Scalar(  7, 170, 120);
+    auto highOrange = cv::Scalar( 18, 255, 255);
+    inRange(hsvImage, lowOrange, highOrange, rangeImage);
+
+    // Smooth it out:
+    erode(rangeImage, erodedImage, 2);
+    dilate(erodedImage, dilatedImage, 2);
+    showIfDebug(dilatedImage);
+
+    // Detect contours:
+    std::vector<std::vector<cv::Point> > contours;
+    std::vector<cv::Vec4i> hierarchy;
+    findContours( dilatedImage, contours, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE );
+
+    // If any contour:
+    if (!contours.empty()) {
+
+        // Find the biggest contour:
+        double maxArea = -1;
+        std::vector<cv::Point> maxContour;
+        for (auto contour : contours) {
+            double area = cv::contourArea(contour);
+            if (area > maxArea) {
+                maxArea = area;
+                maxContour = contour;
             }
-            showIfDebug(resizedImage);
         }
-        eventBus.propagate(EventOrangeDetected(resizedImage, ballPosition, radius));
+
+        // 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));
 }
 
 void OrangeBallDetector::setDebug(bool d) {

From 621c3cb5272b2092b513ed15c0f594e2d64e57ea Mon Sep 17 00:00:00 2001
From: jean-michel-gonet <jmgonet@yahoo.com>
Date: Fri, 18 Jan 2019 19:57:32 +0100
Subject: [PATCH 11/35] In some systems, the first image may come before the
 window size is initialized

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

diff --git a/src/app/cpp/camera-drawing-area.cpp b/src/app/cpp/camera-drawing-area.cpp
index 4573a1d..9c5c507 100644
--- a/src/app/cpp/camera-drawing-area.cpp
+++ b/src/app/cpp/camera-drawing-area.cpp
@@ -16,10 +16,12 @@ CameraDrawingArea::~CameraDrawingArea() {
 }
 
 void CameraDrawingArea::receive(EventOrangeDetected e) {
-	resize(e.getCapturedImage(), output, cv::Size(width, height), 0, 0, cv::INTER_LINEAR);
-    
-    // Invalidate the window, but for that we need to be in the GUI thread.
-    dispatchInvalidate.emit();
+	if (width > 0 && height > 0) {
+		resize(e.getCapturedImage(), output, cv::Size(width, height), 0, 0, cv::INTER_LINEAR);
+		
+		// Invalidate the window, but for that we need to be in the GUI thread.
+		dispatchInvalidate.emit();
+	}
 }
 
 void CameraDrawingArea::doInvalidate() {

From 05f311b76aeb0c37df448e4c8ad6c8383d43fa63 Mon Sep 17 00:00:00 2001
From: jean-michel-gonet <jmgonet@yahoo.com>
Date: Sat, 19 Jan 2019 06:55:24 +0100
Subject: [PATCH 12/35] Step2 - Special propagation thread, so it can run in
 parallel with grabs

---
 src/lib/cpp/capture-image-from-camera.cpp | 27 ++++++++++++++++++-----
 src/lib/hpp/capture-image-from-camera.hpp |  6 +++--
 2 files changed, 25 insertions(+), 8 deletions(-)

diff --git a/src/lib/cpp/capture-image-from-camera.cpp b/src/lib/cpp/capture-image-from-camera.cpp
index 0914d92..4b500cd 100644
--- a/src/lib/cpp/capture-image-from-camera.cpp
+++ b/src/lib/cpp/capture-image-from-camera.cpp
@@ -1,7 +1,8 @@
 #include "capture-image-from-camera.hpp"
 
 CaptureImageFromCamera::CaptureImageFromCamera():
-captureThread(nullptr),
+captureThread(nullptr),
+propagateThread(nullptr),
 videoCapture(0) {
 	startCapturing();
 }
@@ -25,20 +26,34 @@ void CaptureImageFromCamera::stopCapturing() {
 		captureThread->join();
 		free(captureThread);
 		captureThread = nullptr;
+	}
+	if (propagateThread) {
+        propagateThread->join();
+        free(propagateThread);
+        propagateThread = nullptr;
 	}
 }
 
 void CaptureImageFromCamera::doCapture() {
-	while (keepCapturing) {
+	do {
 		videoCapture.grab();
 		videoCapture.grab();
 		videoCapture.grab();
 		videoCapture.read(webcam);
-        if (webcam.size().width > 0) {
-		    eventBus.propagate(EventImageCaptured(webcam));
+        if (webcam.size().width > 0) {
+            doPropagate();
         }
-    }
-}
+    } while (keepCapturing);
+}
+
+void CaptureImageFromCamera::doPropagate() {
+    if (propagateThread) {
+        propagateThread->join();
+    }
+    propagateThread = new std::thread([this] {
+        eventBus.propagate(EventImageCaptured(webcam));
+    });
+}
 
 EventImageCaptured::EventImageCaptured(cv::Mat i):
 capturedImage(i){
diff --git a/src/lib/hpp/capture-image-from-camera.hpp b/src/lib/hpp/capture-image-from-camera.hpp
index e5daba9..c903b75 100644
--- a/src/lib/hpp/capture-image-from-camera.hpp
+++ b/src/lib/hpp/capture-image-from-camera.hpp
@@ -24,8 +24,10 @@ class CaptureImageFromCamera {
 	void stopCapturing();
 private:
 	bool keepCapturing;
-	void doCapture();
-	std::thread* captureThread;
+	void doCapture();
+	void doPropagate();
+	std::thread* captureThread;
+	std::thread* propagateThread;
 	cv::VideoCapture videoCapture;
 	cv::Mat webcam;
 	EventBus<EventImageCaptured> eventBus;

From 1f1db9a53a7d5e4ba92db4a1b779001feee64a79 Mon Sep 17 00:00:00 2001
From: jean-michel-gonet <jmgonet@yahoo.com>
Date: Sat, 19 Jan 2019 07:02:38 +0100
Subject: [PATCH 13/35] Step2 - When the application is closing, parent object
 may have been destroyed, so we need to watch out for invalid images

---
 src/lib/cpp/movie-maker.cpp          | 10 ++++++----
 src/lib/cpp/orange-ball-detector.cpp |  3 +++
 2 files changed, 9 insertions(+), 4 deletions(-)

diff --git a/src/lib/cpp/movie-maker.cpp b/src/lib/cpp/movie-maker.cpp
index db20b42..7e8e731 100644
--- a/src/lib/cpp/movie-maker.cpp
+++ b/src/lib/cpp/movie-maker.cpp
@@ -20,11 +20,13 @@ void MovieMaker::receive(EventImageCaptured e) {
 }
 
 void MovieMaker::addPhotogram(Mat photogram) {
-    if (!isMovieStarted) {
-        startMovie(photogram);
-        isMovieStarted = true;
+    if (photogram.size().width > 0) {
+        if (!isMovieStarted) {
+            startMovie(photogram);
+            isMovieStarted = true;
+        }
+        videoWriter.write(photogram);
     }
-    videoWriter.write(photogram);
 }
 
 void MovieMaker::endMovie() {
diff --git a/src/lib/cpp/orange-ball-detector.cpp b/src/lib/cpp/orange-ball-detector.cpp
index e76637b..1577f1e 100644
--- a/src/lib/cpp/orange-ball-detector.cpp
+++ b/src/lib/cpp/orange-ball-detector.cpp
@@ -8,6 +8,9 @@ void OrangeBallDetector::receive(EventImageCaptured e) {
 
 void OrangeBallDetector::detect(cv::Mat image) {
     double width = image.size().width;
+    if (image.size().width == 0) {
+        return;
+    }
     radius = 0;
     ballPosition.x = 0;
     ballPosition.y = 0;

From b9f3fbaaa35036b9cd2db674e929ca6578672503 Mon Sep 17 00:00:00 2001
From: jean-michel-gonet <jmgonet@yahoo.com>
Date: Sat, 19 Jan 2019 18:58:35 +0100
Subject: [PATCH 14/35] Use spaces for tabs

---
 src/lib/cpp/capture-image-from-camera.cpp | 72 +++++++++++------------
 1 file changed, 36 insertions(+), 36 deletions(-)

diff --git a/src/lib/cpp/capture-image-from-camera.cpp b/src/lib/cpp/capture-image-from-camera.cpp
index 4b500cd..9ff2d44 100644
--- a/src/lib/cpp/capture-image-from-camera.cpp
+++ b/src/lib/cpp/capture-image-from-camera.cpp
@@ -4,61 +4,61 @@ CaptureImageFromCamera::CaptureImageFromCamera():
 captureThread(nullptr),
 propagateThread(nullptr),
 videoCapture(0) {
-	startCapturing();
+    startCapturing();
 }
 
 CaptureImageFromCamera::~CaptureImageFromCamera() {
-	stopCapturing();
+    stopCapturing();
 }
 
 void CaptureImageFromCamera::startCapturing() {
-	if (!captureThread) {
-		keepCapturing = true;
-		captureThread = new std::thread([this] {
-			doCapture();
-		});
-	}
+    if (!captureThread) {
+        keepCapturing = true;
+        captureThread = new std::thread([this] {
+            doCapture();
+        });
+    }
 }
 
 void CaptureImageFromCamera::stopCapturing() {
-	if (captureThread) {
-		keepCapturing = false;
-		captureThread->join();
-		free(captureThread);
-		captureThread = nullptr;
-	}
-	if (propagateThread) {
-        propagateThread->join();
-        free(propagateThread);
-        propagateThread = nullptr;
-	}
+    if (captureThread) {
+        keepCapturing = false;
+        captureThread->join();
+        free(captureThread);
+        captureThread = nullptr;
+    }
+    if (propagateThread) {
+        propagateThread->join();
+        free(propagateThread);
+        propagateThread = nullptr;
+    }
 }
 
 void CaptureImageFromCamera::doCapture() {
-	do {
-		videoCapture.grab();
-		videoCapture.grab();
-		videoCapture.grab();
-		videoCapture.read(webcam);
-        if (webcam.size().width > 0) {
+    do {
+        videoCapture.grab();
+        videoCapture.grab();
+        videoCapture.grab();
+        videoCapture.read(webcam);
+        if (webcam.size().width > 0) {
             doPropagate();
         }
     } while (keepCapturing);
-}
-
-void CaptureImageFromCamera::doPropagate() {
-    if (propagateThread) {
-        propagateThread->join();
-    }
-    propagateThread = new std::thread([this] {
-        eventBus.propagate(EventImageCaptured(webcam));
-    });
-}
+}
+
+void CaptureImageFromCamera::doPropagate() {
+    if (propagateThread) {
+        propagateThread->join();
+    }
+    propagateThread = new std::thread([this] {
+        eventBus.propagate(EventImageCaptured(webcam));
+    });
+}
 
 EventImageCaptured::EventImageCaptured(cv::Mat i):
 capturedImage(i){
 }
 
 cv::Mat EventImageCaptured::getCapturedImage() {
-	return capturedImage;
+    return capturedImage;
 }

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 15/35] 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 16/35] 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 17/35] 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 18/35] 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 19/35] 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 20/35] 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 21/35] 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 e70f8d24716454d7bf85ce65374d475919ec8282 Mon Sep 17 00:00:00 2001
From: jean-michel-gonet <jmgonet@yahoo.com>
Date: Wed, 23 Jan 2019 17:14:42 +0100
Subject: [PATCH 22/35] Move the pixbuf transformation in the image reception

---
 src/app/cpp/camera-drawing-area.cpp | 36 ++++++++++++++---------------
 src/app/hpp/camera-drawing-area.hpp | 12 +++++-----
 2 files changed, 23 insertions(+), 25 deletions(-)

diff --git a/src/app/cpp/camera-drawing-area.cpp b/src/app/cpp/camera-drawing-area.cpp
index 9c5c507..e029752 100644
--- a/src/app/cpp/camera-drawing-area.cpp
+++ b/src/app/cpp/camera-drawing-area.cpp
@@ -12,15 +12,25 @@ dispatchInvalidate() {
 }
 
 CameraDrawingArea::~CameraDrawingArea() {
-	eventBus.unsubscribe(this);
+    eventBus.unsubscribe(this);
 }
 
 void CameraDrawingArea::receive(EventOrangeDetected e) {
-	if (width > 0 && height > 0) {
-		resize(e.getCapturedImage(), output, cv::Size(width, height), 0, 0, cv::INTER_LINEAR);
-		
-		// Invalidate the window, but for that we need to be in the GUI thread.
-		dispatchInvalidate.emit();
+    if (width > 0 && height > 0) {
+        resize(e.getCapturedImage(), output, cv::Size(width, height), 0, 0, cv::INTER_LINEAR);
+
+        // Initializes a pixbuf sharing the same data as the mat:
+        pixbuf = Gdk::Pixbuf::create_from_data(
+                (guint8*)output.data,
+                Gdk::COLORSPACE_RGB,
+                false,
+                8,
+                output.cols,
+                output.rows,
+                (int) output.step);
+
+        // Invalidate the window, but for that we need to be in the GUI thread.
+        dispatchInvalidate.emit();
 	}
 }
 
@@ -32,19 +42,7 @@ 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,
-                    Gdk::COLORSPACE_RGB,
-                    false,
-                    8,
-                    output.cols,
-                    output.rows,
-                    (int) output.step);
-
-        // Display
+    if (pixbuf) {
         Gdk::Cairo::set_source_pixbuf(cr, pixbuf);
         cr->paint();
     }
diff --git a/src/app/hpp/camera-drawing-area.hpp b/src/app/hpp/camera-drawing-area.hpp
index 7e5c64b..591bb27 100644
--- a/src/app/hpp/camera-drawing-area.hpp
+++ b/src/app/hpp/camera-drawing-area.hpp
@@ -13,18 +13,18 @@ public Subscriptor<EventOrangeDetected> {
 public:
     CameraDrawingArea();
     virtual ~CameraDrawingArea();
-	void receive(EventOrangeDetected e) override;
+    void receive(EventOrangeDetected e) override;
     
 protected:
     bool on_draw(const Cairo::RefPtr<Cairo::Context>& cr) override;
-	void on_size_allocate (Gtk::Allocation& allocation) override;
+    void on_size_allocate (Gtk::Allocation& allocation) override;
 
 private:
     Glib::Dispatcher dispatchInvalidate;
     void doInvalidate();
-	cv::Mat webcam;
-	cv::Mat output;
-	int width, height;
-	EventBus<EventOrangeDetected> eventBus;
+    cv::Mat output;
+    Glib::RefPtr<Gdk::Pixbuf> pixbuf;
+    int width, height;
+    EventBus<EventOrangeDetected> eventBus;
 };
 #endif

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 23/35] 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 24/35] 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 25/35] 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 26/35] 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 27/35] 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 28/35] 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 29/35] 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 a99c0607fcad00cb2bf0738ce7f72ca5222969a0 Mon Sep 17 00:00:00 2001
From: jgonet <jgonet@pictet.com>
Date: Fri, 25 Jan 2019 12:43:12 +0100
Subject: [PATCH 30/35] Use 'empty' to detect empty images

---
 src/lib/cpp/capture-image-from-camera.cpp | 4 ++--
 src/lib/cpp/movie-maker.cpp               | 2 +-
 src/lib/cpp/orange-ball-detector.cpp      | 4 ++--
 3 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/src/lib/cpp/capture-image-from-camera.cpp b/src/lib/cpp/capture-image-from-camera.cpp
index 9ff2d44..31b8c77 100644
--- a/src/lib/cpp/capture-image-from-camera.cpp
+++ b/src/lib/cpp/capture-image-from-camera.cpp
@@ -1,7 +1,7 @@
 #include "capture-image-from-camera.hpp"
 
 CaptureImageFromCamera::CaptureImageFromCamera():
-captureThread(nullptr),
+captureThread(nullptr),
 propagateThread(nullptr),
 videoCapture(0) {
     startCapturing();
@@ -40,7 +40,7 @@ void CaptureImageFromCamera::doCapture() {
         videoCapture.grab();
         videoCapture.grab();
         videoCapture.read(webcam);
-        if (webcam.size().width > 0) {
+        if (!webcam.empty()) {
             doPropagate();
         }
     } while (keepCapturing);
diff --git a/src/lib/cpp/movie-maker.cpp b/src/lib/cpp/movie-maker.cpp
index 7e8e731..dd72718 100644
--- a/src/lib/cpp/movie-maker.cpp
+++ b/src/lib/cpp/movie-maker.cpp
@@ -20,7 +20,7 @@ void MovieMaker::receive(EventImageCaptured e) {
 }
 
 void MovieMaker::addPhotogram(Mat photogram) {
-    if (photogram.size().width > 0) {
+    if (!photogram.empty()) {
         if (!isMovieStarted) {
             startMovie(photogram);
             isMovieStarted = true;
diff --git a/src/lib/cpp/orange-ball-detector.cpp b/src/lib/cpp/orange-ball-detector.cpp
index 1577f1e..13e20ff 100644
--- a/src/lib/cpp/orange-ball-detector.cpp
+++ b/src/lib/cpp/orange-ball-detector.cpp
@@ -7,8 +7,7 @@ void OrangeBallDetector::receive(EventImageCaptured e) {
 }
 
 void OrangeBallDetector::detect(cv::Mat image) {
-    double width = image.size().width;
-    if (image.size().width == 0) {
+    if (image.empty()) {
         return;
     }
     radius = 0;
@@ -16,6 +15,7 @@ void OrangeBallDetector::detect(cv::Mat image) {
     ballPosition.y = 0;
 
     // Make the image of a reasonable size:
+    double width = image.size().width;
     double scale = 600 / width;
     resize(image, resizedImage, cv::Size(), scale, scale, cv::INTER_LINEAR);
     showIfDebug(resizedImage);

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 31/35] 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 32/35] 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 33/35] 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 34/35] 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();
     }
 }

From 118efcc5585c28958c98169717c9f9343d2a8130 Mon Sep 17 00:00:00 2001
From: jean-michel-gonet <jmgonet@yahoo.com>
Date: Sat, 26 Jan 2019 07:43:11 +0100
Subject: [PATCH 35/35] Use a mutex to avoid changing the pixbuf while it is
 about to be displayed

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

diff --git a/src/app/cpp/camera-drawing-area.cpp b/src/app/cpp/camera-drawing-area.cpp
index 51a362b..5648929 100644
--- a/src/app/cpp/camera-drawing-area.cpp
+++ b/src/app/cpp/camera-drawing-area.cpp
@@ -20,6 +20,7 @@ void CameraDrawingArea::receive(EventOrangeDetected e) {
         resize(e.getCapturedImage(), output, cv::Size(width, height), 0, 0, cv::INTER_LINEAR);
 
         // Initializes a pixbuf sharing the same data as the mat:
+        std::lock_guard<std::mutex> guard(pixbufMutex);
         pixbuf = Gdk::Pixbuf::create_from_data(
                 (guint8*)output.data,
                 Gdk::COLORSPACE_RGB,
@@ -42,6 +43,7 @@ void CameraDrawingArea::doInvalidate() {
 }
 
 bool CameraDrawingArea::on_draw(const Cairo::RefPtr<Cairo::Context>& cr) {
+    std::lock_guard<std::mutex> guard(pixbufMutex);
     if (pixbuf) {
         Gdk::Cairo::set_source_pixbuf(cr, pixbuf);
         cr->paint();
diff --git a/src/app/hpp/camera-drawing-area.hpp b/src/app/hpp/camera-drawing-area.hpp
index 591bb27..8dcdca0 100644
--- a/src/app/hpp/camera-drawing-area.hpp
+++ b/src/app/hpp/camera-drawing-area.hpp
@@ -24,6 +24,7 @@ public Subscriptor<EventOrangeDetected> {
     void doInvalidate();
     cv::Mat output;
     Glib::RefPtr<Gdk::Pixbuf> pixbuf;
+    std::mutex pixbufMutex;
     int width, height;
     EventBus<EventOrangeDetected> eventBus;
 };
