From 7c8d9b7b36a683875e414f464a8a14b92fc83916 Mon Sep 17 00:00:00 2001
From: Nicolas Pope <nicolas.pope@utu.fi>
Date: Wed, 29 Jan 2020 10:00:09 +0200
Subject: [PATCH] Improves build times and exception messages

---
 .gitlab-ci.yml                                |   2 +-
 CMakeLists.txt                                |   7 +
 applications/gui/src/camera.cpp               |   3 +
 applications/gui/src/config_window.cpp        |   4 +-
 applications/gui/src/ctrl_window.cpp          |   2 +-
 applications/gui/src/src_window.cpp           |   3 +
 .../reconstruct/src/reconstruction.cpp        |   6 +-
 components/audio/include/ftl/audio/frame.hpp  |   6 +-
 components/audio/src/source.cpp               |   5 +-
 components/audio/src/speaker.cpp              |   3 +
 components/codecs/CMakeLists.txt              |  37 ++-
 components/codecs/test/CMakeLists.txt         |   6 +-
 components/common/cpp/CMakeLists.txt          |   7 +-
 .../common/cpp/include/ftl/configurable.hpp   |  12 +-
 .../common/cpp/include/ftl/configuration.hpp  |  33 ++-
 .../common/cpp/include/ftl/cuda_common.hpp    |   5 +-
 .../common/cpp/include/ftl/exception.hpp      |   2 +
 components/common/cpp/src/configurable.cpp    |   2 +
 components/common/cpp/src/cuda_common.cpp     |   2 +
 components/common/cpp/test/CMakeLists.txt     |  31 +-
 .../common/cpp/test/configurable_unit.cpp     |   5 -
 components/common/cpp/test/timer_unit.cpp     |   6 +-
 components/control/cpp/include/ftl/master.hpp |   2 +-
 components/control/cpp/src/master.cpp         |   6 +-
 components/net/cpp/CMakeLists.txt             |   7 +-
 components/net/cpp/include/ftl/net/peer.hpp   |   6 +-
 .../net/cpp/include/ftl/net/universe.hpp      |   9 +-
 components/net/cpp/src/dispatcher.cpp         |   6 +-
 components/net/cpp/src/net_internal.hpp       |   8 +-
 components/net/cpp/src/universe.cpp           |   2 +
 components/net/cpp/test/CMakeLists.txt        |   6 +-
 components/operators/src/depth.cpp            |   6 +-
 .../src/disparity/bilateral_filter.cpp        |   6 +-
 .../src/disparity/disparity_to_depth.cpp      |   3 +-
 components/operators/src/mvmls.cpp            |   6 +-
 components/operators/src/normals.cpp          |   6 +-
 components/operators/src/operator.cpp         |  12 +-
 components/operators/src/smoothing.cpp        |   3 +
 components/renderers/cpp/src/tri_render.cpp   |   3 +
 components/rgbd-sources/CMakeLists.txt        |   2 +
 .../rgbd-sources/include/ftl/rgbd/format.hpp  |   2 +-
 .../rgbd-sources/include/ftl/rgbd/frame.hpp   |  25 +-
 .../rgbd-sources/include/ftl/rgbd/source.hpp  |   2 +-
 components/rgbd-sources/src/frame.cpp         | 269 +-----------------
 components/rgbd-sources/src/frameset.cpp      |   7 +-
 components/rgbd-sources/src/group.cpp         |   3 +
 components/rgbd-sources/test/CMakeLists.txt   |   4 +-
 components/streams/CMakeLists.txt             |  11 +-
 .../streams/include/ftl/streams/stream.hpp    |   1 -
 components/streams/src/filestream.cpp         |   3 +
 components/streams/src/netstream.cpp          |   3 +
 components/streams/src/receiver.cpp           |   3 +
 components/streams/src/sender.cpp             |   8 +-
 components/streams/src/stream.cpp             |   8 +-
 components/streams/test/CMakeLists.txt        |   8 +-
 .../structures/include/ftl/data/frame.hpp     |  24 +-
 .../include/ftl/data/framestate.hpp           |   4 +-
 57 files changed, 238 insertions(+), 435 deletions(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 3d0318ec6..3d371af7b 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -65,7 +65,7 @@ windows-vision:
     - master
   stage: all
   variables:
-    CMAKE_ARGS: '-DWITH_OPTFLOW=TRUE -DBUILD_VISION=TRUE -DBUILD_CALIBRATION=FALSE -DBUILDRECONSTRUCT=FALSE -DBUILDRENDERER=FALSE -DBUILD_TESTING=FALSE'
+    CMAKE_ARGS: '-DWITH_OPTFLOW=TRUE -DBUILD_VISION=TRUE -DBUILD_CALIBRATION=FALSE -DBUILDRECONSTRUCT=FALSE -DBUILDRENDERER=FALSE -DBUILD_TESTING=FALSE -DBUILD_TESTS=FALSE'
     DEPLOY_DIR: 'D:/Shared/AutoDeploy'
   tags:
     - win
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 5b08d4d0c..2f6d5b6d5 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -21,6 +21,7 @@ option(BUILD_RENDERER "Enable the renderer component" ON)
 option(BUILD_GUI "Enable the GUI" ON)
 option(BUILD_CALIBRATION "Enable the calibration component" OFF)
 option(BUILD_TOOLS "Compile developer and research tools" ON)
+option(BUILD_TESTS "Compile all unit and integration tests" ON)
 
 set(THREADS_PREFER_PTHREAD_FLAG ON)
 set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/")
@@ -33,6 +34,12 @@ find_package( URIParser REQUIRED )
 find_package( MsgPack REQUIRED )
 find_package( Eigen3 REQUIRED )
 
+find_program(CCACHE_PROGRAM ccache)
+if(CCACHE_PROGRAM)
+	set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CCACHE_PROGRAM}")
+	message(STATUS "Found ccache: ${CCACHE_PROGRAM}")
+endif()
+
 # find_package( ffmpeg )
 
 if (WITH_OPTFLOW)
diff --git a/applications/gui/src/camera.cpp b/applications/gui/src/camera.cpp
index 4a30668cc..4488fbeaf 100644
--- a/applications/gui/src/camera.cpp
+++ b/applications/gui/src/camera.cpp
@@ -5,6 +5,9 @@
 
 #include <ftl/operators/antialiasing.hpp>
 
+#define LOGURU_REPLACE_GLOG 1
+#include <loguru.hpp>
+
 #include <fstream>
 
 #ifdef HAVE_OPENVR
diff --git a/applications/gui/src/config_window.cpp b/applications/gui/src/config_window.cpp
index b3c5ef874..1f8e3a31a 100644
--- a/applications/gui/src/config_window.cpp
+++ b/applications/gui/src/config_window.cpp
@@ -132,7 +132,7 @@ ConfigWindow::ConfigWindow(nanogui::Widget *parent, ftl::ctrl::Master *ctrl)
 		itembutton->setTooltip(c);
 		itembutton->setBackgroundColor(nanogui::Color(0.9f,0.9f,0.9f,0.9f));
 		itembutton->setCallback([this,c]() {
-			LOG(INFO) << "Change configurable: " << c;
+			//LOG(INFO) << "Change configurable: " << c;
 			_buildForm(c);
 			setVisible(false);
 			//this->parent()->removeChild(this);
@@ -160,7 +160,7 @@ void ConfigWindow::_addElements(nanogui::FormHelper *form, const std::string &su
 		if (i.key() == "$id") continue;
 
 		if (i.key() == "$ref" && i.value().is_string()) {
-			LOG(INFO) << "Follow $ref: " << i.value();
+			//LOG(INFO) << "Follow $ref: " << i.value();
 			const std::string suri = std::string(i.value().get<string>());
 			_addElements(form, suri);
 			continue;
diff --git a/applications/gui/src/ctrl_window.cpp b/applications/gui/src/ctrl_window.cpp
index 3681022bd..37d311964 100644
--- a/applications/gui/src/ctrl_window.cpp
+++ b/applications/gui/src/ctrl_window.cpp
@@ -51,7 +51,7 @@ ControlWindow::ControlWindow(nanogui::Widget *parent, ftl::ctrl::Master *ctrl)
 	new Label(this, "Select Node","sans-bold");
 	auto select = new ComboBox(this, node_titles_);
 	select->setCallback([this](int ix) {
-		LOG(INFO) << "Change node: " << ix;
+		//LOG(INFO) << "Change node: " << ix;
 		_changeActive(ix);
 	});
 
diff --git a/applications/gui/src/src_window.cpp b/applications/gui/src/src_window.cpp
index fe63a858b..fbb38e1d9 100644
--- a/applications/gui/src/src_window.cpp
+++ b/applications/gui/src/src_window.cpp
@@ -15,6 +15,9 @@
 #include <nanogui/layout.h>
 #include <nanogui/vscrollpanel.h>
 
+#define LOGURU_REPLACE_GLOG 1
+#include <loguru.hpp>
+
 #include <ftl/streams/netstream.hpp>
 
 #include "ftl/operators/colours.hpp"
diff --git a/applications/reconstruct/src/reconstruction.cpp b/applications/reconstruct/src/reconstruction.cpp
index 5fab0496b..e4e448793 100644
--- a/applications/reconstruct/src/reconstruction.cpp
+++ b/applications/reconstruct/src/reconstruction.cpp
@@ -66,7 +66,7 @@ ftl::rgbd::FrameState &Reconstruction::state(size_t ix) {
 	if (ix < fs_align_.frames.size()) {
 		return *fs_align_.frames[ix].origin();
 	}
-	throw ftl::exception("State index out-of-bounds");
+	throw FTL_Error("State index out-of-bounds");
 }
 
 void Reconstruction::onFrameSet(const ftl::rgbd::VideoCallback &cb) {
@@ -78,7 +78,7 @@ bool Reconstruction::post(ftl::rgbd::FrameSet &fs) {
 		
 	{
 		UNIQUE_LOCK(exchange_mtx_, lk);
-		if (new_frame_ == true) LOG(WARNING) << "Frame lost";
+		//if (new_frame_ == true) LOG(WARNING) << "Frame lost";
 		fs.swapTo(fs_align_);
 		new_frame_ = true;
 	}
@@ -102,7 +102,7 @@ bool Reconstruction::post(ftl::rgbd::FrameSet &fs) {
 
 void Reconstruction::setGenerator(ftl::rgbd::Generator *gen) {
 	if (gen_) {
-		throw ftl::exception("Reconstruction already has generator");
+		throw FTL_Error("Reconstruction already has generator");
 	}
 
 	gen_ = gen;
diff --git a/components/audio/include/ftl/audio/frame.hpp b/components/audio/include/ftl/audio/frame.hpp
index efd333849..845123a8f 100644
--- a/components/audio/include/ftl/audio/frame.hpp
+++ b/components/audio/include/ftl/audio/frame.hpp
@@ -17,17 +17,17 @@ struct AudioSettings {
 struct AudioData {
 	template <typename T>
 	const T &as() const {
-		throw ftl::exception("Type not valid for audio channel");
+		throw FTL_Error("Type not valid for audio channel");
 	}
 
 	template <typename T>
 	T &as() {
-		throw ftl::exception("Type not valid for audio channel");
+		throw FTL_Error("Type not valid for audio channel");
 	}
 
 	template <typename T>
 	T &make() {
-		throw ftl::exception("Type not valid for audio channel");
+		throw FTL_Error("Type not valid for audio channel");
 	}
 
 	inline void reset() {}
diff --git a/components/audio/src/source.cpp b/components/audio/src/source.cpp
index f43caeba2..cec0c2d98 100644
--- a/components/audio/src/source.cpp
+++ b/components/audio/src/source.cpp
@@ -2,6 +2,9 @@
 #include <ftl/audio/audio.hpp>
 #include <ftl/audio/portaudio.hpp>
 
+#define LOGURU_REPLACE_GLOG 1
+#include <loguru.hpp>
+
 using ftl::audio::Source;
 using ftl::audio::Frame;
 using ftl::audio::FrameSet;
@@ -176,7 +179,7 @@ size_t Source::size() {
 }
 
 ftl::audio::FrameState &Source::state(size_t ix) {
-    if (ix >= 1) throw ftl::exception("State index out-of-bounds");
+    if (ix >= 1) throw FTL_Error("State index out-of-bounds");
     return state_;
 }
 
diff --git a/components/audio/src/speaker.cpp b/components/audio/src/speaker.cpp
index 4670ca385..41836b837 100644
--- a/components/audio/src/speaker.cpp
+++ b/components/audio/src/speaker.cpp
@@ -2,6 +2,9 @@
 #include <ftl/audio/audio.hpp>
 #include <ftl/audio/portaudio.hpp>
 
+#define LOGURU_REPLACE_GLOG 1
+#include <loguru.hpp>
+
 using ftl::audio::Speaker;
 using ftl::audio::Frame;
 using ftl::audio::FrameSet;
diff --git a/components/codecs/CMakeLists.txt b/components/codecs/CMakeLists.txt
index 31496df7d..4334e6dbe 100644
--- a/components/codecs/CMakeLists.txt
+++ b/components/codecs/CMakeLists.txt
@@ -1,19 +1,44 @@
-set(CODECSRC
+add_library(BaseCodec OBJECT
 	src/bitrates.cpp
 	src/encoder.cpp
 	src/decoder.cpp
-	src/opencv_encoder.cpp
-	src/opencv_decoder.cpp
 	src/generate.cpp
 	src/writer.cpp
 	src/reader.cpp
 	src/channels.cpp
 	src/depth_convert.cu
 )
+target_include_directories(BaseCodec PUBLIC
+	${CMAKE_CURRENT_SOURCE_DIR}/include
+	$<TARGET_PROPERTY:ftlcommon,INTERFACE_INCLUDE_DIRECTORIES>
+	$<TARGET_PROPERTY:nvpipe,INTERFACE_INCLUDE_DIRECTORIES>
+)
+
+add_library(OpenCVCodec OBJECT	
+	src/opencv_encoder.cpp
+	src/opencv_decoder.cpp
+)
+target_include_directories(OpenCVCodec PUBLIC
+	${CMAKE_CURRENT_SOURCE_DIR}/include
+	$<TARGET_PROPERTY:ftlcommon,INTERFACE_INCLUDE_DIRECTORIES>
+)
+
+set(CODECSRC
+$<TARGET_OBJECTS:BaseCodec>
+$<TARGET_OBJECTS:OpenCVCodec>
+)
 
 if (HAVE_NVPIPE)
-	list(APPEND CODECSRC src/nvpipe_encoder.cpp)
-	list(APPEND CODECSRC src/nvpipe_decoder.cpp)
+	add_library(NvPipeCodec OBJECT	
+		src/nvpipe_encoder.cpp
+		src/nvpipe_decoder.cpp
+	)
+	target_include_directories(NvPipeCodec PUBLIC
+		${CMAKE_CURRENT_SOURCE_DIR}/include
+		$<TARGET_PROPERTY:ftlcommon,INTERFACE_INCLUDE_DIRECTORIES>
+		$<TARGET_PROPERTY:nvpipe,INTERFACE_INCLUDE_DIRECTORIES>
+	)
+	list(APPEND CODECSRC $<TARGET_OBJECTS:NvPipeCodec>)
 endif()
 
 add_library(ftlcodecs ${CODECSRC})
@@ -26,5 +51,7 @@ target_include_directories(ftlcodecs PUBLIC
 #target_include_directories(cv-node PUBLIC ${PROJECT_SOURCE_DIR}/include)
 target_link_libraries(ftlcodecs ftlcommon ${OpenCV_LIBS} ${CUDA_LIBRARIES} Eigen3::Eigen nvpipe)
 
+if (BUILD_TESTS)
 add_subdirectory(test)
+endif()
 
diff --git a/components/codecs/test/CMakeLists.txt b/components/codecs/test/CMakeLists.txt
index 63d40a0ea..ea703c7a6 100644
--- a/components/codecs/test/CMakeLists.txt
+++ b/components/codecs/test/CMakeLists.txt
@@ -1,6 +1,6 @@
 ### OpenCV Codec Unit ################################################################
 add_executable(opencv_codec_unit
-	./tests.cpp
+$<TARGET_OBJECTS:CatchTest>
 	../src/bitrates.cpp
 	../src/encoder.cpp
 	../src/opencv_encoder.cpp
@@ -17,7 +17,7 @@ add_test(OpenCVCodecUnitTest opencv_codec_unit)
 
 ### NvPipe Codec Unit ################################################################
 add_executable(nvpipe_codec_unit
-	./tests.cpp
+$<TARGET_OBJECTS:CatchTest>
 	../src/bitrates.cpp
 	../src/encoder.cpp
 	../src/nvpipe_encoder.cpp
@@ -48,7 +48,7 @@ add_test(NvPipeCodecUnitTest nvpipe_codec_unit)
 
 ### Channel Unit ###############################################################
 add_executable(channel_unit
-	./tests.cpp
+$<TARGET_OBJECTS:CatchTest>
 	./channel_unit.cpp
 )
 target_include_directories(channel_unit PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../include")
diff --git a/components/common/cpp/CMakeLists.txt b/components/common/cpp/CMakeLists.txt
index e2f1e70fa..81816912e 100644
--- a/components/common/cpp/CMakeLists.txt
+++ b/components/common/cpp/CMakeLists.txt
@@ -1,9 +1,12 @@
+add_library(Loguru OBJECT src/loguru.cpp)
+target_include_directories(Loguru PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
+
 set(COMMONSRC
 	src/config.cpp
 	src/uri.cpp
 	src/configuration.cpp
 	src/configurable.cpp
-	src/loguru.cpp
+	$<TARGET_OBJECTS:Loguru>
 	src/cuda_common.cpp
 	src/ctpl_stl.cpp
 	src/timer.cpp
@@ -21,5 +24,7 @@ target_include_directories(ftlcommon PUBLIC
 	PRIVATE src)
 target_link_libraries(ftlcommon Threads::Threads ${OS_LIBS} ${OpenCV_LIBS} ${URIPARSER_LIBRARIES} ${CUDA_LIBRARIES})
 
+if (BUILD_TESTS)
 add_subdirectory(test)
+endif()
 
diff --git a/components/common/cpp/include/ftl/configurable.hpp b/components/common/cpp/include/ftl/configurable.hpp
index 61dca8ea3..75f3cdc82 100644
--- a/components/common/cpp/include/ftl/configurable.hpp
+++ b/components/common/cpp/include/ftl/configurable.hpp
@@ -2,8 +2,9 @@
 #ifndef _FTL_CONFIGURABLE_HPP_
 #define _FTL_CONFIGURABLE_HPP_
 
-#define LOGURU_REPLACE_GLOG 1
-#include <loguru.hpp>
+//#define LOGURU_REPLACE_GLOG 1
+//#include <loguru.hpp>
+#include <ftl/exception.hpp>
 #include <nlohmann/json.hpp>
 #include <string>
 #include <tuple>
@@ -131,7 +132,7 @@ void Configurable::set<const std::string&>(const std::string &name, const std::s
 
 template <typename T>
 std::optional<T> ftl::Configurable::get(const std::string &name) {
-	if (!config_->is_object() && !config_->is_null()) LOG(FATAL) << "Config is not an object";
+	if (!config_->is_object() && !config_->is_null()) throw FTL_Error("Config is not an object");
 	if (!(*config_)[name].is_null()) {
 		try {
 			return (*config_)[name].get<T>();
@@ -144,13 +145,12 @@ std::optional<T> ftl::Configurable::get(const std::string &name) {
 		std::string res_uri = (*config_)["$ref"].get<std::string>()+"/"+name;
 		auto &r = ftl::config::resolve(res_uri);
 
-		DLOG(2) << "GET: " << res_uri << " = " << r;
+		//DLOG(2) << "GET: " << res_uri << " = " << r;
 
 		try {
 			return r.get<T>();
 		} catch (...) {
-			LOG(ERROR) << "Missing: " << (*config_)["$id"].get<std::string>()+"/"+name;
-			return {};
+			throw FTL_Error("Missing: " << (*config_)["$id"].get<std::string>()+"/"+name);
 		}
 	} else {
 		return {};
diff --git a/components/common/cpp/include/ftl/configuration.hpp b/components/common/cpp/include/ftl/configuration.hpp
index ed7eee95d..d4a4e1a47 100644
--- a/components/common/cpp/include/ftl/configuration.hpp
+++ b/components/common/cpp/include/ftl/configuration.hpp
@@ -2,8 +2,8 @@
 #ifndef _FTL_COMMON_CONFIGURATION_HPP_
 #define _FTL_COMMON_CONFIGURATION_HPP_
 
-#define LOGURU_REPLACE_GLOG 1
-#include <loguru.hpp>
+//#define LOGURU_REPLACE_GLOG 1
+//#include <loguru.hpp>
 #include <nlohmann/json.hpp>
 //#include <ftl/configurable.hpp>
 #include <string>
@@ -139,21 +139,20 @@ T *ftl::config::create(json_t &link, ARGS ...args) {
 	//auto &r = link; // = ftl::config::resolve(link);
 
 	if (!link["$id"].is_string()) {
-		LOG(FATAL) << "Entity does not have $id or parent: " << link;
-		return nullptr;
+		throw FTL_Error("Entity does not have $id or parent: " << link);
 	}
 
 	ftl::Configurable *cfg = ftl::config::find(link["$id"].get<std::string>());
 	if (!cfg) {
-		try {
+		//try {
 			cfg = new T(link, args...);
-		} catch (std::exception &ex) {	
-			LOG(ERROR) << ex.what();
-			LOG(FATAL) << "Could not construct " << link;
-		} catch(...) {
-			LOG(ERROR) << "Unknown exception";
-			LOG(FATAL) << "Could not construct " << link;
-		}
+		//} catch (std::exception &ex) {	
+		//	LOG(ERROR) << ex.what();
+		//	LOG(FATAL) << "Could not construct " << link;
+		//} catch(...) {
+		//	LOG(ERROR) << "Unknown exception";
+		//	LOG(FATAL) << "Could not construct " << link;
+		//}
 	} else {
 		// Make sure configurable has newest object pointer
 		cfg->patchPtr(link);
@@ -162,8 +161,8 @@ T *ftl::config::create(json_t &link, ARGS ...args) {
 	try {
 		return dynamic_cast<T*>(cfg);
 	} catch(...) {
-		LOG(FATAL) << "Configuration URI object is of wrong type: " << link;
-		return nullptr;
+		throw FTL_Error("Configuration URI object is of wrong type: " << link.dump());
+		//return nullptr;
 	}
 }
 
@@ -201,8 +200,8 @@ T *ftl::config::create(ftl::Configurable *parent, const std::string &name, ARGS
 		return create<T>(entity2, args...);
 	}
 
-	LOG(ERROR) << "Unable to create Configurable entity '" << name << "'";
-	return nullptr;
+	throw FTL_Error("Unable to create Configurable entity '" << name << "'");
+	//return nullptr;
 }
 
 template <typename T, typename... ARGS>
@@ -246,7 +245,7 @@ std::vector<T*> ftl::config::createArray(ftl::Configurable *parent, const std::s
 			i++;
 		}
 	} else {
-		LOG(WARNING) << "Expected an array for '" << name << "' in " << parent->getID();
+		//LOG(WARNING) << "Expected an array for '" << name << "' in " << parent->getID();
 	}
 
 	return result;
diff --git a/components/common/cpp/include/ftl/cuda_common.hpp b/components/common/cpp/include/ftl/cuda_common.hpp
index 576b383a9..a70413609 100644
--- a/components/common/cpp/include/ftl/cuda_common.hpp
+++ b/components/common/cpp/include/ftl/cuda_common.hpp
@@ -11,7 +11,6 @@
 #include <opencv2/core/cuda/common.hpp>
 
 #ifndef __CUDACC__
-#include <loguru.hpp>
 #include <exception>
 #endif
 
@@ -150,7 +149,7 @@ class TextureObject : public TextureObjectBase {
 template <typename T>
 TextureObject<T> &TextureObject<T>::cast(TextureObjectBase &b) {
 	if (b.cvType() != ftl::traits::OpenCVType<T>::value) {
-		LOG(ERROR) << "Bad cast of texture object";
+		//LOG(ERROR) << "Bad cast of texture object";
 		throw std::bad_cast();
 	}
 	return reinterpret_cast<TextureObject<T>&>(b);
@@ -162,7 +161,7 @@ TextureObject<T> &TextureObject<T>::cast(TextureObjectBase &b) {
 template <typename T>
 TextureObject<T>::TextureObject(const cv::cuda::GpuMat &d, bool interpolated) {
 	// GpuMat must have correct data type
-	CHECK(d.type() == ftl::traits::OpenCVType<T>::value);
+	//CHECK(d.type() == ftl::traits::OpenCVType<T>::value);
 
 	cudaResourceDesc resDesc;
 	memset(&resDesc, 0, sizeof(resDesc));
diff --git a/components/common/cpp/include/ftl/exception.hpp b/components/common/cpp/include/ftl/exception.hpp
index 921e53493..de21aea54 100644
--- a/components/common/cpp/include/ftl/exception.hpp
+++ b/components/common/cpp/include/ftl/exception.hpp
@@ -47,4 +47,6 @@ class exception : public std::exception
 };
 }
 
+#define FTL_Error(A) (ftl::exception(ftl::Formatter() << __FILE__ << ":" << __LINE__ << ": " << A))
+
 #endif  // _FTL_EXCEPTION_HPP_
diff --git a/components/common/cpp/src/configurable.cpp b/components/common/cpp/src/configurable.cpp
index 8186713b1..fdbd94703 100644
--- a/components/common/cpp/src/configurable.cpp
+++ b/components/common/cpp/src/configurable.cpp
@@ -1,3 +1,5 @@
+#define LOGURU_REPLACE_GLOG 1
+#include <loguru.hpp>
 #include <ftl/configurable.hpp>
 
 using ftl::Configurable;
diff --git a/components/common/cpp/src/cuda_common.cpp b/components/common/cpp/src/cuda_common.cpp
index 01b0bb346..1fb7b68e5 100644
--- a/components/common/cpp/src/cuda_common.cpp
+++ b/components/common/cpp/src/cuda_common.cpp
@@ -1,3 +1,5 @@
+#define LOGURU_REPLACE_GLOG 1
+#include <loguru.hpp>
 #include <ftl/cuda_common.hpp>
 
 using ftl::cuda::TextureObjectBase;
diff --git a/components/common/cpp/test/CMakeLists.txt b/components/common/cpp/test/CMakeLists.txt
index 1b8dfc7ba..3ef38991a 100644
--- a/components/common/cpp/test/CMakeLists.txt
+++ b/components/common/cpp/test/CMakeLists.txt
@@ -1,48 +1,37 @@
+add_library(CatchTest OBJECT ./tests.cpp)
+
 ### Configurable Unit ################################################################
 add_executable(configurable_unit
-	./tests.cpp
-	../src/configurable.cpp
-	../src/uri.cpp
-	../src/config.cpp
-	../src/configuration.cpp
-	../src/loguru.cpp
-	../src/ctpl_stl.cpp
-	../src/cuda_common.cpp
+	$<TARGET_OBJECTS:CatchTest>
 	./configurable_unit.cpp
 )
 target_include_directories(configurable_unit PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../include")
-target_link_libraries(configurable_unit
+target_link_libraries(configurable_unit ftlcommon
 	${URIPARSER_LIBRARIES}
 	Threads::Threads ${OS_LIBS} ${OpenCV_LIBS} ${CUDA_LIBRARIES})
 
 ### URI ########################################################################
 add_executable(uri_unit
-	./tests.cpp
-	../src/uri.cpp
-	../src/loguru.cpp
+	$<TARGET_OBJECTS:CatchTest>
 	./uri_unit.cpp)
 target_include_directories(uri_unit PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../include")
-target_link_libraries(uri_unit
+target_link_libraries(uri_unit ftlcommon
 	Threads::Threads ${OS_LIBS}
 	${URIPARSER_LIBRARIES})
 
 ### Timer Unit ################################################################
 add_executable(timer_unit
-	./tests.cpp
-	../src/timer.cpp
-	../src/config.cpp
-	../src/loguru.cpp
-	../src/ctpl_stl.cpp
+	$<TARGET_OBJECTS:CatchTest>
 	./timer_unit.cpp
 )
 target_include_directories(timer_unit PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../include")
-target_link_libraries(timer_unit
+target_link_libraries(timer_unit ftlcommon
 	Threads::Threads ${OS_LIBS})
 
 ### URI ########################################################################
 add_executable(msgpack_unit
-	./tests.cpp
-	../src/loguru.cpp
+	$<TARGET_OBJECTS:CatchTest>
+	$<TARGET_OBJECTS:Loguru>
 	./msgpack_unit.cpp)
 target_include_directories(msgpack_unit PUBLIC ${OpenCV_INCLUDE_DIRS} "${CMAKE_CURRENT_SOURCE_DIR}/../include")
 target_link_libraries(msgpack_unit Threads::Threads ${OS_LIBS} ${OpenCV_LIBS})
diff --git a/components/common/cpp/test/configurable_unit.cpp b/components/common/cpp/test/configurable_unit.cpp
index af44e026a..a52548bec 100644
--- a/components/common/cpp/test/configurable_unit.cpp
+++ b/components/common/cpp/test/configurable_unit.cpp
@@ -6,11 +6,6 @@
 using ftl::Configurable;
 using std::string;
 
-namespace ftl {
-namespace timer {
-void setInterval(int i) {}
-}
-}
 
 SCENARIO( "Configurable::get()" ) {
 	GIVEN( "a non-existent property" ) {
diff --git a/components/common/cpp/test/timer_unit.cpp b/components/common/cpp/test/timer_unit.cpp
index 6cdea157e..2fdc70034 100644
--- a/components/common/cpp/test/timer_unit.cpp
+++ b/components/common/cpp/test/timer_unit.cpp
@@ -4,11 +4,11 @@
 #include <ftl/timer.hpp>
 #include <ftl/threads.hpp>
 
-ctpl::thread_pool ftl::pool(4);
+//ctpl::thread_pool ftl::pool(4);
 
-namespace ftl {
+/*namespace ftl {
 	bool running = true;
-}
+}*/
 
 TEST_CASE( "Timer::add() High Precision Accuracy" ) {
 	SECTION( "An instantly returning callback" ) {
diff --git a/components/control/cpp/include/ftl/master.hpp b/components/control/cpp/include/ftl/master.hpp
index c4782d16e..fd173cdc0 100644
--- a/components/control/cpp/include/ftl/master.hpp
+++ b/components/control/cpp/include/ftl/master.hpp
@@ -74,7 +74,7 @@ class Master {
 	/**
 	 * Do not call! Automatically called from logging subsystem.
 	 */
-	void sendLog(const loguru::Message& message);
+	//void sendLog(const loguru::Message& message);
 
 	bool isPaused() const { return state_.paused; }
 
diff --git a/components/control/cpp/src/master.cpp b/components/control/cpp/src/master.cpp
index 4c1354bee..9a3ee7f54 100644
--- a/components/control/cpp/src/master.cpp
+++ b/components/control/cpp/src/master.cpp
@@ -1,5 +1,7 @@
 #include <ftl/master.hpp>
 #include <ftl/net_configurable.hpp>
+#define LOGURU_REPLACE_GLOG 1
+#include <loguru.hpp>
 
 using ftl::ctrl::Master;
 using ftl::net::Universe;
@@ -217,7 +219,7 @@ void Master::stop() {
 	net_->unbind("log");
 }
 
-void Master::sendLog(const loguru::Message& message) {
+/*void Master::sendLog(const loguru::Message& message) {
 	UNIQUE_LOCK(mutex_, lk);
 	if (in_log_) return;
 	in_log_ = true;
@@ -233,4 +235,4 @@ void Master::sendLog(const loguru::Message& message) {
 	}
 
 	in_log_ = false;
-}
\ No newline at end of file
+}*/
\ No newline at end of file
diff --git a/components/net/cpp/CMakeLists.txt b/components/net/cpp/CMakeLists.txt
index 9df16c0b2..c765fa538 100644
--- a/components/net/cpp/CMakeLists.txt
+++ b/components/net/cpp/CMakeLists.txt
@@ -24,8 +24,7 @@ install(TARGETS ftlnet EXPORT ftlnet-config
 	RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
 install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
 
-add_executable(net-cli src/main.cpp)
-target_link_libraries(net-cli ftlnet ftlcommon glog::glog Threads::Threads ${READLINE_LIBRARY} ${UUID_LIBRARIES})
-add_dependencies(net-cli ftlnet)
 
-ADD_SUBDIRECTORY(test)
+if (BUILD_TESTS)
+add_subdirectory(test)
+endif()
diff --git a/components/net/cpp/include/ftl/net/peer.hpp b/components/net/cpp/include/ftl/net/peer.hpp
index 9978da922..95eccc014 100644
--- a/components/net/cpp/include/ftl/net/peer.hpp
+++ b/components/net/cpp/include/ftl/net/peer.hpp
@@ -11,7 +11,6 @@
 #include <ftl/exception.hpp>
 
 //#define GLOG_NO_ABBREVIATED_SEVERITIES
-#include <loguru.hpp>
 #include <ftl/net/protocol.hpp>
 #include <ftl/net/dispatcher.hpp>
 #include <ftl/uri.hpp>
@@ -347,8 +346,7 @@ R Peer::call(const std::string &name, ARGS... args) {
 	
 	if (!hasreturned) {
 		cancelCall(id);
-		LOG(ERROR) << "RPC Timeout: " << name;
-		throw ftl::exception("RPC failed with timeout");
+		throw FTL_Error("RPC failed with timeout: " << name);
 	}
 	
 	return result;
@@ -371,7 +369,7 @@ int Peer::asyncCall(
 		callbacks_[rpcid] = std::make_unique<caller<T>>(cb);
 	}
 
-	DLOG(INFO) << "RPC " << name << "(" << rpcid << ") -> " << uri_;
+	//DLOG(INFO) << "RPC " << name << "(" << rpcid << ") -> " << uri_;
 
 	auto call_obj = std::make_tuple(0,rpcid,name,args_obj);
 	
diff --git a/components/net/cpp/include/ftl/net/universe.hpp b/components/net/cpp/include/ftl/net/universe.hpp
index 9bd17cc4e..737955b53 100644
--- a/components/net/cpp/include/ftl/net/universe.hpp
+++ b/components/net/cpp/include/ftl/net/universe.hpp
@@ -383,9 +383,8 @@ template <typename R, typename... ARGS>
 R Universe::call(const ftl::UUID &pid, const std::string &name, ARGS... args) {
 	Peer *p = getPeer(pid);
 	if (p == nullptr || !p->isConnected()) {
-		if (p == nullptr) DLOG(WARNING) << "Attempting to call an unknown peer : " << pid.to_string();
-		else DLOG(WARNING) << "Attempting to call an disconnected peer : " << pid.to_string();
-		throw ftl::exception("Calling disconnected peer");
+		if (p == nullptr) throw FTL_Error("Attempting to call an unknown peer : " << pid.to_string());
+		else throw FTL_Error("Attempting to call an disconnected peer : " << pid.to_string());
 	}
 	return p->call<R>(name, args...);
 }
@@ -394,7 +393,7 @@ template <typename... ARGS>
 bool Universe::send(const ftl::UUID &pid, const std::string &name, ARGS... args) {
 	Peer *p = getPeer(pid);
 	if (p == nullptr) {
-		DLOG(WARNING) << "Attempting to call an unknown peer : " << pid.to_string();
+		//DLOG(WARNING) << "Attempting to call an unknown peer : " << pid.to_string();
 		return false;
 	}
 #ifdef WIN32
@@ -409,7 +408,7 @@ template <typename... ARGS>
 int Universe::try_send(const ftl::UUID &pid, const std::string &name, ARGS... args) {
 	Peer *p = getPeer(pid);
 	if (p == nullptr) {
-		DLOG(WARNING) << "Attempting to call an unknown peer : " << pid.to_string();
+		//DLOG(WARNING) << "Attempting to call an unknown peer : " << pid.to_string();
 		return false;
 	}
 
diff --git a/components/net/cpp/src/dispatcher.cpp b/components/net/cpp/src/dispatcher.cpp
index eac33f5e1..b1a5b265e 100644
--- a/components/net/cpp/src/dispatcher.cpp
+++ b/components/net/cpp/src/dispatcher.cpp
@@ -147,16 +147,14 @@ void ftl::net::Dispatcher::dispatch_notification(Peer &s, msgpack::object const
 void ftl::net::Dispatcher::enforce_arg_count(std::string const &func, std::size_t found,
                                    std::size_t expected) {
     if (found != expected) {
-    	LOG(FATAL) << "RPC argument missmatch for '" << func << "' - " << found << " != " << expected;
-        throw ftl::exception("RPC argument missmatch");
+    	throw FTL_Error("RPC argument missmatch for '" << func << "' - " << found << " != " << expected);
     }
 }
 
 void ftl::net::Dispatcher::enforce_unique_name(std::string const &func) {
     auto pos = funcs_.find(func);
     if (pos != end(funcs_)) {
-    	LOG(FATAL) << "RPC non unique binding for '" << func << "'";
-        throw ftl::exception("RPC binding not unique");
+    	throw FTL_Error("RPC non unique binding for '" << func << "'");
     }
 }
 
diff --git a/components/net/cpp/src/net_internal.hpp b/components/net/cpp/src/net_internal.hpp
index f8586ea77..77aab1eca 100644
--- a/components/net/cpp/src/net_internal.hpp
+++ b/components/net/cpp/src/net_internal.hpp
@@ -2,8 +2,8 @@
 #define _FTL_NET_INTERNAL_HPP_
 
 #if defined _DEBUG && DEBUG_NET
-#include <loguru.hpp>
-#include <chrono>
+//#include <loguru.hpp>
+//#include <chrono>
 #endif
 
 namespace ftl { namespace net { namespace internal {
@@ -27,9 +27,9 @@ namespace ftl { namespace net { namespace internal {
 	inline ssize_t writev(int sd, const struct iovec *v, int cnt) {
 		auto start = std::chrono::high_resolution_clock::now();
 		return ::writev(sd,v,cnt);
-		std::chrono::duration<double> elapsed =
+		/*std::chrono::duration<double> elapsed =
 				std::chrono::high_resolution_clock::now() - start;
-		LOG(INFO) << "WRITEV in " << elapsed.count() << "s";
+		LOG(INFO) << "WRITEV in " << elapsed.count() << "s";*/
 	}
 #else
 	inline ssize_t recv(int sd, void *buf, size_t n, int f) { return ::recv(sd,buf,n,f); }
diff --git a/components/net/cpp/src/universe.cpp b/components/net/cpp/src/universe.cpp
index 29cf1254f..c279cad20 100644
--- a/components/net/cpp/src/universe.cpp
+++ b/components/net/cpp/src/universe.cpp
@@ -1,6 +1,8 @@
 #include <ftl/net/universe.hpp>
 #include <ftl/timer.hpp>
 #include <chrono>
+#define LOGURU_REPLACE_GLOG 1
+#include <loguru.hpp>
 
 #ifdef WIN32
 #include <Ws2tcpip.h>
diff --git a/components/net/cpp/test/CMakeLists.txt b/components/net/cpp/test/CMakeLists.txt
index c786f73d8..30d0f1787 100644
--- a/components/net/cpp/test/CMakeLists.txt
+++ b/components/net/cpp/test/CMakeLists.txt
@@ -1,6 +1,6 @@
 ### Socket Unit ################################################################
 add_executable(peer_unit
-	./tests.cpp
+$<TARGET_OBJECTS:CatchTest>
 	../src/ws_internal.cpp
 	../src/dispatcher.cpp
 	./peer_unit.cpp
@@ -15,7 +15,7 @@ target_link_libraries(peer_unit
 
 ### Net Integration ############################################################
 add_executable(net_integration
-	./tests.cpp
+$<TARGET_OBJECTS:CatchTest>
 	../../../common/cpp/src/config.cpp
 	./net_integration.cpp)
 add_dependencies(net_integration ftlnet)
@@ -29,7 +29,7 @@ target_link_libraries(net_integration
 
 ### NetConfigurable Unit #######################################################
 add_executable(net_configurable_unit
-	./tests.cpp
+$<TARGET_OBJECTS:CatchTest>
 	./net_configurable_unit.cpp)
 target_link_libraries(net_configurable_unit
 	ftlnet)
diff --git a/components/operators/src/depth.cpp b/components/operators/src/depth.cpp
index c8a48df63..0a93061ed 100644
--- a/components/operators/src/depth.cpp
+++ b/components/operators/src/depth.cpp
@@ -97,11 +97,9 @@ bool DepthBilateralFilter::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out,
 									 cudaStream_t stream) {
 
 	if (!in.hasChannel(Channel::Colour)) {
-		LOG(ERROR) << "Joint Bilateral Filter is missing Colour";
-		return false;
+		throw FTL_Error("Joint Bilateral Filter is missing Colour");
 	} else if (!in.hasChannel(Channel::Depth)) {
-		LOG(ERROR) << "Joint Bilateral Filter is missing Depth";
-		return false;
+		throw FTL_Error("Joint Bilateral Filter is missing Depth");
 	}
 
 	auto cvstream = cv::cuda::StreamAccessor::wrapStream(stream);
diff --git a/components/operators/src/disparity/bilateral_filter.cpp b/components/operators/src/disparity/bilateral_filter.cpp
index 0c766596e..cc0285ece 100644
--- a/components/operators/src/disparity/bilateral_filter.cpp
+++ b/components/operators/src/disparity/bilateral_filter.cpp
@@ -23,7 +23,7 @@ bool DisparityBilateralFilter::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out
 									 cudaStream_t stream) {
 
 	if (!in.hasChannel(Channel::Colour)) {
-		LOG(ERROR) << "Joint Bilateral Filter is missing Colour";
+		throw FTL_Error("Joint Bilateral Filter is missing Colour");
 		return false;
 	} else if (!in.hasChannel(Channel::Disparity)) {
 		// Have depth, so calculate disparity...
@@ -35,11 +35,11 @@ bool DisparityBilateralFilter::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out
 			GpuMat &disp = out.create<GpuMat>(Channel::Disparity);
 			disp.create(depth.size(), CV_32FC1);
 
-			LOG(ERROR) << "Calculated disparity from depth";
+			//LOG(ERROR) << "Calculated disparity from depth";
 
 			ftl::cuda::depth_to_disparity(disp, depth, params, stream);
 		} else {
-			LOG(ERROR) << "Joint Bilateral Filter is missing Disparity and Depth";
+			throw FTL_Error("Joint Bilateral Filter is missing Disparity and Depth");
 			return false;
 		}
 	}
diff --git a/components/operators/src/disparity/disparity_to_depth.cpp b/components/operators/src/disparity/disparity_to_depth.cpp
index 18a284915..f9ae8b127 100644
--- a/components/operators/src/disparity/disparity_to_depth.cpp
+++ b/components/operators/src/disparity/disparity_to_depth.cpp
@@ -10,8 +10,7 @@ bool DisparityToDepth::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out,
 							cudaStream_t stream) {
 	
 	if (!in.hasChannel(Channel::Disparity)) {
-		LOG(ERROR) << "Missing disparity before convert to depth";
-		return false;
+		throw FTL_Error("Missing disparity before convert to depth");
 	}
 
 	const auto params = in.getLeftCamera();
diff --git a/components/operators/src/mvmls.cpp b/components/operators/src/mvmls.cpp
index 52bc22a02..305bdd98f 100644
--- a/components/operators/src/mvmls.cpp
+++ b/components/operators/src/mvmls.cpp
@@ -59,12 +59,10 @@ bool MultiViewMLS::apply(ftl::rgbd::FrameSet &in, ftl::rgbd::FrameSet &out, cuda
         contributions_[i].create(size.width, size.height);
 
         if (!f.hasChannel(Channel::Normals)) {
-            LOG(ERROR) << "Required normals channel missing for MLS";
-            return false;
+            throw FTL_Error("Required normals channel missing for MLS");
         }
         if (!f.hasChannel(Channel::Support2)) {
-            LOG(ERROR) << "Required cross support channel missing for MLS";
-            return false;
+            throw FTL_Error("Required cross support channel missing for MLS");
         }
 
         // Create required channels
diff --git a/components/operators/src/normals.cpp b/components/operators/src/normals.cpp
index 8e2ab7d3f..0551a1e4e 100644
--- a/components/operators/src/normals.cpp
+++ b/components/operators/src/normals.cpp
@@ -17,8 +17,7 @@ Normals::~Normals() {
 
 bool Normals::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) {
 	if (!in.hasChannel(Channel::Depth)) {
-		LOG(ERROR) << "Missing depth channel in Normals operator";
-		return false;
+		throw FTL_Error("Missing depth channel in Normals operator");
 	}
 
 	if (out.hasChannel(Channel::Normals)) {
@@ -49,8 +48,7 @@ bool SmoothNormals::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStrea
     int radius = max(0, min(config()->value("radius",1), 5));
 
 	if (!in.hasChannel(Channel::Depth)) {
-		LOG(ERROR) << "Missing depth channel in SmoothNormals operator";
-		return false;
+		throw FTL_Error("Missing depth channel in SmoothNormals operator");
 	}
 
 	if (out.hasChannel(Channel::Normals)) {
diff --git a/components/operators/src/operator.cpp b/components/operators/src/operator.cpp
index 8dbbf168c..0ca639e1e 100644
--- a/components/operators/src/operator.cpp
+++ b/components/operators/src/operator.cpp
@@ -1,5 +1,8 @@
 #include <ftl/operators/operator.hpp>
 
+#define LOGURU_REPLACE_GLOG 1
+#include <loguru.hpp>
+
 using ftl::operators::Operator;
 using ftl::operators::Graph;
 using ftl::rgbd::Frame;
@@ -17,18 +20,15 @@ Operator::Operator(ftl::Configurable *config) : config_(config) {
 Operator::~Operator() {}
 
 bool Operator::apply(Frame &in, Frame &out, cudaStream_t stream) {
-	LOG(ERROR) << "Operation application to frame not supported";
-	return false;
+	throw FTL_Error("Operation application to frame not supported");
 }
 
 bool Operator::apply(FrameSet &in, FrameSet &out, cudaStream_t stream) {
-	LOG(ERROR) << "Operation application to frameset not supported";
-	return false;
+	throw FTL_Error("Operation application to frameset not supported");
 }
 
 bool Operator::apply(FrameSet &in, Frame &out, cudaStream_t stream) {
-	LOG(ERROR) << "Operation application as a reduction not supported";
-	return false;
+	throw FTL_Error("Operation application as a reduction not supported");
 }
 
 
diff --git a/components/operators/src/smoothing.cpp b/components/operators/src/smoothing.cpp
index ef9cb58cb..3f6862072 100644
--- a/components/operators/src/smoothing.cpp
+++ b/components/operators/src/smoothing.cpp
@@ -1,6 +1,9 @@
 #include <ftl/operators/smoothing.hpp>
 #include "smoothing_cuda.hpp"
 
+#define LOGURU_REPLACE_GLOG 1
+#include <loguru.hpp>
+
 #include <ftl/cuda/normals.hpp>
 
 using ftl::operators::HFSmoother;
diff --git a/components/renderers/cpp/src/tri_render.cpp b/components/renderers/cpp/src/tri_render.cpp
index c9001bd2d..f221f16ec 100644
--- a/components/renderers/cpp/src/tri_render.cpp
+++ b/components/renderers/cpp/src/tri_render.cpp
@@ -5,6 +5,9 @@
 #include <ftl/cuda/normals.hpp>
 #include <ftl/cuda/mask.hpp>
 
+#define LOGURU_REPLACE_GLOG 1
+#include <loguru.hpp>
+
 #include <opencv2/core/cuda_stream_accessor.hpp>
 
 //#include <ftl/filters/smoothing.hpp>
diff --git a/components/rgbd-sources/CMakeLists.txt b/components/rgbd-sources/CMakeLists.txt
index 92bab62ac..4001d5c1c 100644
--- a/components/rgbd-sources/CMakeLists.txt
+++ b/components/rgbd-sources/CMakeLists.txt
@@ -43,5 +43,7 @@ endif()
 
 target_link_libraries(ftlrgbd ftlcommon ${OpenCV_LIBS} ${LIBSGM_LIBRARIES} ${CUDA_LIBRARIES} Eigen3::Eigen ${REALSENSE_LIBRARY} ftlnet ${LibArchive_LIBRARIES} ftlcodecs ftloperators ftldata)
 
+if (BUILD_TESTS)
 add_subdirectory(test)
+endif()
 
diff --git a/components/rgbd-sources/include/ftl/rgbd/format.hpp b/components/rgbd-sources/include/ftl/rgbd/format.hpp
index e52a6627f..032e2948d 100644
--- a/components/rgbd-sources/include/ftl/rgbd/format.hpp
+++ b/components/rgbd-sources/include/ftl/rgbd/format.hpp
@@ -42,7 +42,7 @@ struct Format : public ftl::rgbd::FormatBase {
 			a.cols,
 			a.rows,
 			ftl::traits::OpenCVType<T>::value) {
-		CHECK(cvType == a.type());
+		if (cvType != a.type()) throw FTL_Error("Type mismatch");
 	}
 };
 
diff --git a/components/rgbd-sources/include/ftl/rgbd/frame.hpp b/components/rgbd-sources/include/ftl/rgbd/frame.hpp
index 65837020f..a2e7813bc 100644
--- a/components/rgbd-sources/include/ftl/rgbd/frame.hpp
+++ b/components/rgbd-sources/include/ftl/rgbd/frame.hpp
@@ -39,17 +39,17 @@ struct VideoData {
 
 	template <typename T>
 	T &as() {
-		throw ftl::exception("Unsupported type for Video data channel");
+		throw FTL_Error("Unsupported type for Video data channel");
 	};
 
 	template <typename T>
 	const T &as() const {
-		throw ftl::exception("Unsupported type for Video data channel");
+		throw FTL_Error("Unsupported type for Video data channel");
 	};
 
 	template <typename T>
 	T &make() {
-		throw ftl::exception("Unsupported type for Video data channel");
+		throw FTL_Error("Unsupported type for Video data channel");
 	};
 
 	inline void reset() {
@@ -194,14 +194,13 @@ template <> cv::cuda::GpuMat &Frame::create(ftl::codecs::Channel c, const ftl::r
 
 template <typename T>
 ftl::cuda::TextureObject<T> &Frame::getTexture(ftl::codecs::Channel c) {
-	if (!hasChannel(c)) throw ftl::exception(ftl::Formatter() << "Texture channel does not exist: " << (int)c);
+	if (!hasChannel(c)) throw FTL_Error("Texture channel does not exist: " << (int)c);
 
 	auto &m = getData(c);
-	if (!m.isgpu) throw ftl::exception("Texture channel is not on GPU");
+	if (!m.isgpu) throw FTL_Error("Texture channel is not on GPU");
 
 	if (m.tex.cvType() != ftl::traits::OpenCVType<T>::value || m.tex.width() != m.gpu.cols || m.tex.height() != m.gpu.rows || m.gpu.type() != m.tex.cvType()) {
-		LOG(ERROR) << "Texture has not been created for channel = " << (int)c;
-		throw ftl::exception("Texture has not been created properly for this channel");
+		throw FTL_Error("Texture has not been created properly for this channel: " << (int)c);
 	}
 
 	return ftl::cuda::TextureObject<T>::cast(m.tex);
@@ -216,14 +215,13 @@ ftl::cuda::TextureObject<T> &Frame::createTexture(ftl::codecs::Channel c, const
 	auto &m = getData(c);
 
 	if (f.empty()) {
-		throw ftl::exception("createTexture needs a non-empty format");
+		throw FTL_Error("createTexture needs a non-empty format");
 	} else {
 		m.gpu.create(f.size(), f.cvType);
 	}
 
 	if (m.gpu.type() != ftl::traits::OpenCVType<T>::value) {
-		LOG(ERROR) << "Texture type mismatch: " << (int)c << " " << m.gpu.type() << " != " << ftl::traits::OpenCVType<T>::value;
-		throw ftl::exception("Texture type does not match underlying data type");
+		throw FTL_Error("Texture type mismatch: " << (int)c << " " << m.gpu.type() << " != " << ftl::traits::OpenCVType<T>::value);
 	}
 
 	// TODO: Check tex cvType
@@ -242,7 +240,7 @@ ftl::cuda::TextureObject<T> &Frame::createTexture(ftl::codecs::Channel c, const
 
 template <typename T>
 ftl::cuda::TextureObject<T> &Frame::createTexture(ftl::codecs::Channel c, bool interpolated) {
-	if (!hasChannel(c)) throw ftl::exception(ftl::Formatter() << "createTexture needs a format if the channel does not exist: " << (int)c);
+	if (!hasChannel(c)) throw FTL_Error("createTexture needs a format if the channel does not exist: " << (int)c);
 
 	auto &m = getData(c);
 
@@ -251,12 +249,11 @@ ftl::cuda::TextureObject<T> &Frame::createTexture(ftl::codecs::Channel c, bool i
 		// TODO: Should this upload to GPU or not?
 		//gpu_ += c;
 	} else if (!m.isgpu || (m.isgpu && m.gpu.empty())) {
-		throw ftl::exception("createTexture needs a format if no memory is allocated");
+		throw FTL_Error("createTexture needs a format if no memory is allocated");
 	}
 
 	if (m.gpu.type() != ftl::traits::OpenCVType<T>::value) {
-		LOG(ERROR) << "Texture type mismatch: " << (int)c << " " << m.gpu.type() << " != " << ftl::traits::OpenCVType<T>::value;
-		throw ftl::exception("Texture type does not match underlying data type");
+		throw FTL_Error("Texture type mismatch: " << (int)c << " " << m.gpu.type() << " != " << ftl::traits::OpenCVType<T>::value);
 	}
 
 	// TODO: Check tex cvType
diff --git a/components/rgbd-sources/include/ftl/rgbd/source.hpp b/components/rgbd-sources/include/ftl/rgbd/source.hpp
index 23fff9440..d26d8942a 100644
--- a/components/rgbd-sources/include/ftl/rgbd/source.hpp
+++ b/components/rgbd-sources/include/ftl/rgbd/source.hpp
@@ -134,7 +134,7 @@ class Source : public ftl::Configurable {
 	 */
 	const Camera &parameters() const {
 		if (impl_) return impl_->params_;
-		else throw ftl::exception("Cannot get parameters for bad source");
+		else throw FTL_Error("Cannot get parameters for bad source");
 	}
 
 	/**
diff --git a/components/rgbd-sources/src/frame.cpp b/components/rgbd-sources/src/frame.cpp
index 2a9765cee..faaf78cf3 100644
--- a/components/rgbd-sources/src/frame.cpp
+++ b/components/rgbd-sources/src/frame.cpp
@@ -12,25 +12,25 @@ static cv::cuda::GpuMat noneGPU;
 
 template <>
 cv::Mat &VideoData::as<cv::Mat>() {
-	if (isgpu) throw ftl::exception("Host request for GPU data without download");
+	if (isgpu) throw FTL_Error("Host request for GPU data without download");
 	return host;
 }
 
 template <>
 const cv::Mat &VideoData::as<cv::Mat>() const {
-	if (isgpu) throw ftl::exception("Host request for GPU data without download");
+	if (isgpu) throw FTL_Error("Host request for GPU data without download");
 	return host;
 }
 
 template <>
 cv::cuda::GpuMat &VideoData::as<cv::cuda::GpuMat>() {
-	if (!isgpu) throw ftl::exception("GPU request for Host data without upload");
+	if (!isgpu) throw FTL_Error("GPU request for Host data without upload");
 	return gpu;
 }
 
 template <>
 const cv::cuda::GpuMat &VideoData::as<cv::cuda::GpuMat>() const {
-	if (!isgpu) throw ftl::exception("GPU request for Host data without upload");
+	if (!isgpu) throw FTL_Error("GPU request for Host data without upload");
 	return gpu;
 }
 
@@ -102,13 +102,13 @@ void Frame::pushPacket(ftl::codecs::Channel c, ftl::codecs::Packet &pkt) {
 		auto &m1 = getData(c);
 		m1.encoded.emplace_back() = std::move(pkt);
 	} else {
-		LOG(ERROR) << "Channel " << (int)c << " doesn't exist for packet push";
+		throw FTL_Error("Channel " << (int)c << " doesn't exist for packet push");
 	}
 }
 
 const std::list<ftl::codecs::Packet> &Frame::getPackets(ftl::codecs::Channel c) const {
 	if (!hasChannel(c)) {
-		throw ftl::exception(ftl::Formatter() << "Frame channel does not exist: " << (int)c);
+		throw FTL_Error("Frame channel does not exist: " << (int)c);
 	}
 
 	auto &m1 = getData(c);
@@ -135,184 +135,9 @@ bool Frame::empty(ftl::codecs::Channels<0> channels) {
 	return false;
 }
 
-/*void Frame::swapTo(ftl::codecs::Channels<0> channels, Frame &f) {
-	f.reset();
-	f.origin_ = origin_;
-	f.state_ = state_;
-
-	// For all channels in this frame object
-	for (auto c : channels_) {
-		// Should we swap this channel?
-		if (channels.has(c)) {
-			// Does 'f' have this channel?
-			//if (!f.hasChannel(c)) {
-				// No, so create it first
-				// FIXME: Allocate the memory as well?
-				if (isCPU(c)) f.create<cv::Mat>(c);
-				else f.create<cv::cuda::GpuMat>(c);
-			//}
-
-			auto &m1 = _get(c);
-			auto &m2 = f._get(c);
-
-			cv::swap(m1.host, m2.host);
-			cv::cuda::swap(m1.gpu, m2.gpu);
-
-			auto temptex = std::move(m2.tex);
-			m2.tex = std::move(m1.tex);
-			m1.tex = std::move(temptex);
-
-			if (m2.encoded.size() > 0 || m1.encoded.size() > 0) {
-				auto tempenc = std::move(m2.encoded);
-				m2.encoded = std::move(m1.encoded);
-				m1.encoded = std::move(tempenc);
-			}
-		}
-	}
-
-	f.data_data_ = std::move(data_data_);
-	f.data_channels_ = data_channels_;
-	data_channels_.clear();
-}
-
-void Frame::swapChannels(ftl::codecs::Channel a, ftl::codecs::Channel b) {
-	auto &m1 = _get(a);
-	auto &m2 = _get(b);
-	cv::swap(m1.host, m2.host);
-	cv::cuda::swap(m1.gpu, m2.gpu);
-
-	auto temptex = std::move(m2.tex);
-	m2.tex = std::move(m1.tex);
-	m1.tex = std::move(temptex);
-
-	if (m2.encoded.size() > 0 || m1.encoded.size() > 0) {
-		auto tempenc = std::move(m2.encoded);
-		m2.encoded = std::move(m1.encoded);
-		m1.encoded = std::move(tempenc);
-	}
-}
-
-void Frame::copyTo(ftl::codecs::Channels<0> channels, Frame &f) {
-	f.reset();
-	f.origin_ = origin_;
-	f.state_ = state_;
-
-	// For all channels in this frame object
-	for (auto c : channels_) {
-		// Should we copy this channel?
-		if (channels.has(c)) {
-			if (isCPU(c)) get<cv::Mat>(c).copyTo(f.create<cv::Mat>(c));
-			else get<cv::cuda::GpuMat>(c).copyTo(f.create<cv::cuda::GpuMat>(c));
-			auto &m1 = _get(c);
-			auto &m2 = f._get(c);
-			m2.encoded = m1.encoded; //std::move(m1.encoded);  // TODO: Copy?
-		}
-	}
-
-	f.data_data_ = data_data_;
-	f.data_channels_ = data_channels_;
-}
-
-template<> cv::Mat& Frame::get(ftl::codecs::Channel channel) {
-	if (channel == Channel::None) {
-		DLOG(WARNING) << "Cannot get the None channel from a Frame";
-		none.release();
-		return none;
-	}
-
-	if (isGPU(channel)) {
-		download(Channels(channel));
-		LOG(WARNING) << "Getting GPU channel on CPU without explicit 'download'";
-	}
-
-	// Add channel if not already there
-	if (!channels_.has(channel)) {
-		throw ftl::exception(ftl::Formatter() << "Frame channel does not exist: " << (int)channel);
-	}
-
-	return _get(channel).host;
-}
-
-template<> cv::cuda::GpuMat& Frame::get(ftl::codecs::Channel channel) {
-	if (channel == Channel::None) {
-		DLOG(WARNING) << "Cannot get the None channel from a Frame";
-		noneGPU.release();
-		return noneGPU;
-	}
-
-	if (isCPU(channel)) {
-		upload(Channels(channel));
-		LOG(WARNING) << "Getting CPU channel on GPU without explicit 'upload'";
-	}
-
-	// Add channel if not already there
-	if (!channels_.has(channel)) {
-		throw ftl::exception(ftl::Formatter() << "Frame channel does not exist: " << (int)channel);
-	}
-
-	return _get(channel).gpu;
-}
-
-template<> const cv::Mat& Frame::get(ftl::codecs::Channel channel) const {
-	if (channel == Channel::None) {
-		LOG(FATAL) << "Cannot get the None channel from a Frame";
-	}
-
-	if (isGPU(channel)) {
-		LOG(FATAL) << "Getting GPU channel on CPU without explicit 'download'";
-	}
-
-	if (!channels_.has(channel)) throw ftl::exception(ftl::Formatter() << "Frame channel does not exist: " << (int)channel);
-
-	return _get(channel).host;
-}
-
-template<> const cv::cuda::GpuMat& Frame::get(ftl::codecs::Channel channel) const {
-	if (channel == Channel::None) {
-		LOG(FATAL) << "Cannot get the None channel from a Frame";
-	}
-
-	if (isCPU(channel)) {
-		LOG(FATAL) << "Getting CPU channel on GPU without explicit 'upload'";
-	}
-
-	// Add channel if not already there
-	if (!channels_.has(channel)) {
-		throw ftl::exception(ftl::Formatter() << "Frame channel does not exist: " << (int)channel);
-	}
-
-	return _get(channel).gpu;
-}
-
-template<> const Eigen::Matrix4d& Frame::get(ftl::codecs::Channel channel) const {
-	if (channel == Channel::Pose) {
-		return state_.getPose();
-	}
-
-	throw ftl::exception(ftl::Formatter() << "Invalid pose channel: " << (int)channel);
-}
-
-template<> const ftl::rgbd::Camera& Frame::get(ftl::codecs::Channel channel) const {
-	if (channel == Channel::Calibration) {
-		return state_.getLeft();
-	} else if (channel == Channel::Calibration2) {
-		return state_.getRight();
-	}
-
-	throw ftl::exception(ftl::Formatter() << "Invalid calibration channel: " << (int)channel);
-}
-
-template<> const nlohmann::json& Frame::get(ftl::codecs::Channel channel) const {
-	if (channel == Channel::Configuration) {
-		return state_.getConfig();
-	}
-
-	throw ftl::exception(ftl::Formatter() << "Invalid configuration channel: " << (int)channel);
-}*/
-
 template <> cv::Mat &Frame::create(ftl::codecs::Channel c, const ftl::rgbd::FormatBase &f) {
 	if (c == Channel::None) {
-		throw ftl::exception("Cannot create a None channel");
+		throw FTL_Error("Cannot create a None channel");
 	}
 	
 	create<cv::Mat>(c);
@@ -329,7 +154,7 @@ template <> cv::Mat &Frame::create(ftl::codecs::Channel c, const ftl::rgbd::Form
 
 template <> cv::cuda::GpuMat &Frame::create(ftl::codecs::Channel c, const ftl::rgbd::FormatBase &f) {
 	if (c == Channel::None) {
-		throw ftl::exception("Cannot create a None channel");
+		throw FTL_Error("Cannot create a None channel");
 	}
 
 	create<cv::cuda::GpuMat>(c);
@@ -349,85 +174,7 @@ void Frame::clearPackets(ftl::codecs::Channel c) {
 	m.encoded.clear();
 }
 
-/*template <> cv::Mat &Frame::create(ftl::codecs::Channel c) {
-	if (c == Channel::None) {
-		throw ftl::exception("Cannot create a None channel");
-	}
-	channels_ += c;
-	gpu_ -= c;
-
-	auto &m = _get(c);
-
-	m.encoded.clear();  // Remove all old encoded data
-
-	return m.host;
-}
-
-template <> cv::cuda::GpuMat &Frame::create(ftl::codecs::Channel c) {
-	if (c == Channel::None) {
-		throw ftl::exception("Cannot create a None channel");
-	}
-	channels_ += c;
-	gpu_ += c;
-
-	auto &m = _get(c);
-
-	m.encoded.clear();  // Remove all old encoded data
-
-	return m.gpu;
-}*/
-
 void Frame::resetTexture(ftl::codecs::Channel c) {
 	auto &m = getData(c);
 	m.tex.free();
 }
-
-/*void Frame::setOrigin(ftl::rgbd::FrameState *state) {
-	if (origin_ != nullptr) {
-		throw ftl::exception("Can only set origin once after reset");
-	}
-
-	origin_ = state;
-	state_ = *state;
-}
-
-const Eigen::Matrix4d &Frame::getPose() const {
-	return get<Eigen::Matrix4d>(ftl::codecs::Channel::Pose);
-}
-
-const ftl::rgbd::Camera &Frame::getLeftCamera() const {
-	return get<ftl::rgbd::Camera>(ftl::codecs::Channel::Calibration);
-}
-
-const ftl::rgbd::Camera &Frame::getRightCamera() const {
-	return get<ftl::rgbd::Camera>(ftl::codecs::Channel::Calibration2);
-}
-
-void ftl::rgbd::Frame::setPose(const Eigen::Matrix4d &pose) {
-	if (origin_) origin_->setPose(pose);
-}
-
-void ftl::rgbd::Frame::setLeftCamera(const ftl::rgbd::Camera &c) {
-	if (origin_) origin_->setLeft(c);
-}
-
-void ftl::rgbd::Frame::setRightCamera(const ftl::rgbd::Camera &c) {
-	if (origin_) origin_->setRight(c);
-}
-
-std::string ftl::rgbd::Frame::getConfigString() const {
-	return get<nlohmann::json>(ftl::codecs::Channel::Configuration).dump();
-}
-
-const std::vector<unsigned char> &ftl::rgbd::Frame::getRawData(ftl::codecs::Channel channel) const {
-	if (static_cast<int>(channel) < static_cast<int>(ftl::codecs::Channel::Data)) throw ftl::exception("Non data channel");
-	if (!hasChannel(channel)) throw ftl::exception("Data channel does not exist");
-
-	return data_data_.at(static_cast<int>(channel));
-}
-
-void ftl::rgbd::Frame::createRawData(ftl::codecs::Channel c, const std::vector<unsigned char> &v) {
-	data_data_.insert({static_cast<int>(c), v});
-	data_channels_ += c;
-}
-*/
diff --git a/components/rgbd-sources/src/frameset.cpp b/components/rgbd-sources/src/frameset.cpp
index c632d0585..70e6c0c47 100644
--- a/components/rgbd-sources/src/frameset.cpp
+++ b/components/rgbd-sources/src/frameset.cpp
@@ -1,6 +1,9 @@
 #include <ftl/rgbd/frameset.hpp>
 #include <ftl/timer.hpp>
 
+#define LOGURU_REPLACE_GLOG 1
+#include <loguru.hpp>
+
 #include <chrono>
 
 using ftl::rgbd::Builder;
@@ -188,9 +191,9 @@ void Builder::onFrameSet(const std::function<bool(ftl::rgbd::FrameSet &)> &cb) {
 ftl::rgbd::FrameState &Builder::state(size_t ix) {
 	UNIQUE_LOCK(mutex_, lk);
 	if (ix >= states_.size()) {
-		throw ftl::exception("Frame state out-of-bounds");
+		throw FTL_Error("Frame state out-of-bounds: " << ix);
 	}
-	if (!states_[ix]) throw ftl::exception("Missing framestate");
+	if (!states_[ix]) throw FTL_Error("Missing framestate");
 	return *states_[ix];
 }
 
diff --git a/components/rgbd-sources/src/group.cpp b/components/rgbd-sources/src/group.cpp
index 02f3b6f70..5739d0396 100644
--- a/components/rgbd-sources/src/group.cpp
+++ b/components/rgbd-sources/src/group.cpp
@@ -5,6 +5,9 @@
 
 #include <chrono>
 
+#define LOGURU_REPLACE_GLOG 1
+#include <loguru.hpp>
+
 using ftl::rgbd::Group;
 using ftl::rgbd::Source;
 using ftl::rgbd::kMaxFramesets;
diff --git a/components/rgbd-sources/test/CMakeLists.txt b/components/rgbd-sources/test/CMakeLists.txt
index 9065105d6..b0d54ac91 100644
--- a/components/rgbd-sources/test/CMakeLists.txt
+++ b/components/rgbd-sources/test/CMakeLists.txt
@@ -1,6 +1,6 @@
 ### Source Unit ################################################################
 add_executable(source_unit
-	./tests.cpp
+$<TARGET_OBJECTS:CatchTest>
 	../src/frame.cpp
 	./source_unit.cpp
 )
@@ -12,7 +12,7 @@ add_test(SourceUnitTest source_unit)
 
 ### Frame Unit #################################################################
 add_executable(frame_unit
-	./tests.cpp
+$<TARGET_OBJECTS:CatchTest>
 	./frame_unit.cpp
 	../src/frame.cpp
 )
diff --git a/components/streams/CMakeLists.txt b/components/streams/CMakeLists.txt
index d971532fb..43722b888 100644
--- a/components/streams/CMakeLists.txt
+++ b/components/streams/CMakeLists.txt
@@ -1,3 +1,10 @@
+#add_library(FtlStream OBJECT src/stream.cpp) 
+#target_include_directories(FtlStream PUBLIC
+#	$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
+#	$<INSTALL_INTERFACE:include>
+#	PRIVATE src)
+#add_dependencies(FtlStream ftlcommon)
+
 set(STREAMSRC
 	src/stream.cpp
 	src/filestream.cpp
@@ -21,4 +28,6 @@ target_include_directories(ftlstreams PUBLIC
 #target_include_directories(cv-node PUBLIC ${PROJECT_SOURCE_DIR}/include)
 target_link_libraries(ftlstreams ftlrgbd ftlcommon ${OpenCV_LIBS} Eigen3::Eigen ftlnet ftlcodecs ftlaudio)
 
-add_subdirectory(test)
\ No newline at end of file
+if (BUILD_TESTS)
+add_subdirectory(test)
+endif()
diff --git a/components/streams/include/ftl/streams/stream.hpp b/components/streams/include/ftl/streams/stream.hpp
index 397d2f117..04adcf4e8 100644
--- a/components/streams/include/ftl/streams/stream.hpp
+++ b/components/streams/include/ftl/streams/stream.hpp
@@ -1,7 +1,6 @@
 #ifndef _FTL_STREAM_STREAM_HPP_
 #define _FTL_STREAM_STREAM_HPP_
 
-#include <loguru.hpp>
 #include <ftl/configuration.hpp>
 #include <ftl/configurable.hpp>
 #include <ftl/rgbd/source.hpp>
diff --git a/components/streams/src/filestream.cpp b/components/streams/src/filestream.cpp
index 7d7f5d5f4..62256438b 100644
--- a/components/streams/src/filestream.cpp
+++ b/components/streams/src/filestream.cpp
@@ -1,6 +1,9 @@
 #include <fstream>
 #include <ftl/streams/filestream.hpp>
 
+#define LOGURU_REPLACE_GLOG 1
+#include <loguru.hpp>
+
 using ftl::stream::File;
 using ftl::codecs::StreamPacket;
 using ftl::codecs::Packet;
diff --git a/components/streams/src/netstream.cpp b/components/streams/src/netstream.cpp
index f25983ccd..95a4ca941 100644
--- a/components/streams/src/netstream.cpp
+++ b/components/streams/src/netstream.cpp
@@ -1,5 +1,8 @@
 #include <ftl/streams/netstream.hpp>
 
+#define LOGURU_REPLACE_GLOG 1
+#include <loguru.hpp>
+
 using ftl::stream::Net;
 using ftl::codecs::StreamPacket;
 using ftl::codecs::Packet;
diff --git a/components/streams/src/receiver.cpp b/components/streams/src/receiver.cpp
index 36d321c43..93ead3465 100644
--- a/components/streams/src/receiver.cpp
+++ b/components/streams/src/receiver.cpp
@@ -4,6 +4,9 @@
 #include "parsers.hpp"
 #include "injectors.hpp"
 
+#define LOGURU_REPLACE_GLOG 1
+#include <loguru.hpp>
+
 using ftl::stream::Receiver;
 using ftl::stream::Stream;
 using ftl::codecs::StreamPacket;
diff --git a/components/streams/src/sender.cpp b/components/streams/src/sender.cpp
index 239597c71..e1aa69b58 100644
--- a/components/streams/src/sender.cpp
+++ b/components/streams/src/sender.cpp
@@ -3,6 +3,9 @@
 
 #include "injectors.hpp"
 
+#define LOGURU_REPLACE_GLOG 1
+#include <loguru.hpp>
+
 using ftl::stream::Sender;
 using ftl::codecs::StreamPacket;
 using ftl::codecs::Packet;
@@ -28,11 +31,6 @@ Sender::~Sender() {
 	}
 }
 
-/*void Sender::onStateChange(const std::function<void(ftl::codecs::Channel,const ftl::rgbd::FrameState&)> &cb) {
-	if (cb && state_cb_) throw ftl::exception("State change callback already set");
-	state_cb_ = cb;
-}*/
-
 void Sender::setStream(ftl::stream::Stream*s) {
 	if (stream_) stream_->onPacket(nullptr);
     stream_ = s;
diff --git a/components/streams/src/stream.cpp b/components/streams/src/stream.cpp
index 2c409d3b4..332c9215f 100644
--- a/components/streams/src/stream.cpp
+++ b/components/streams/src/stream.cpp
@@ -7,26 +7,26 @@ using ftl::stream::Stream;
 
 const ftl::codecs::Channels<0> &Stream::available(int fs) const {
 	SHARED_LOCK(mtx_, lk);
-	if (fs < 0 || static_cast<uint32_t>(fs) >= state_.size()) throw ftl::exception("Frameset index out-of-bounds");
+	if (fs < 0 || static_cast<uint32_t>(fs) >= state_.size()) throw FTL_Error("Frameset index out-of-bounds: " << fs);
 	return state_[fs].available;
 }
 
 const ftl::codecs::Channels<0> &Stream::selected(int fs) const {
 	SHARED_LOCK(mtx_, lk);
-	if (fs < 0 || static_cast<uint32_t>(fs) >= state_.size()) throw ftl::exception("Frameset index out-of-bounds");
+	if (fs < 0 || static_cast<uint32_t>(fs) >= state_.size()) throw FTL_Error("Frameset index out-of-bounds: " << fs);
 	return state_[fs].selected;
 }
 
 void Stream::select(int fs, const ftl::codecs::Channels<0> &s, bool make) {
 	UNIQUE_LOCK(mtx_, lk);
-	if (fs < 0 || (!make && static_cast<uint32_t>(fs) >= state_.size())) throw ftl::exception("Frameset index out-of-bounds");
+	if (fs < 0 || (!make && static_cast<uint32_t>(fs) >= state_.size())) throw FTL_Error("Frameset index out-of-bounds: " << fs);
 	if (static_cast<uint32_t>(fs) >= state_.size()) state_.resize(fs+1);
 	state_[fs].selected = s;
 }
 
 ftl::codecs::Channels<0> &Stream::available(int fs) {
 	UNIQUE_LOCK(mtx_, lk);
-	if (fs < 0) throw ftl::exception("Frameset index out-of-bounds");
+	if (fs < 0) throw FTL_Error("Frameset index out-of-bounds: " << fs);
 	if (static_cast<uint32_t>(fs) >= state_.size()) state_.resize(fs+1);
 	return state_[fs].available;
 }
diff --git a/components/streams/test/CMakeLists.txt b/components/streams/test/CMakeLists.txt
index cb2b1e5a1..288f2ff07 100644
--- a/components/streams/test/CMakeLists.txt
+++ b/components/streams/test/CMakeLists.txt
@@ -1,6 +1,6 @@
 ### Stream Unit ################################################################
 add_executable(stream_unit
-	./tests.cpp
+	$<TARGET_OBJECTS:CatchTest>
 	./stream_unit.cpp
 	../src/stream.cpp
 )
@@ -12,7 +12,7 @@ add_test(StreamUnitTest stream_unit)
 
 ### File Stream Unit ###########################################################
 add_executable(filestream_unit
-	./tests.cpp
+$<TARGET_OBJECTS:CatchTest>
 	./filestream_unit.cpp
 	../src/filestream.cpp
 	../src/stream.cpp
@@ -37,7 +37,7 @@ add_test(FileStreamUnitTest filestream_unit)
 
 ### Sender Unit ################################################################
 add_executable(sender_unit
-	./tests.cpp
+$<TARGET_OBJECTS:CatchTest>
 	./sender_unit.cpp
 	../src/sender.cpp
 	../src/stream.cpp
@@ -52,7 +52,7 @@ add_test(SenderUnitTest sender_unit)
 
 ### Receiver Unit ##############################################################
 add_executable(receiver_unit
-	./tests.cpp
+$<TARGET_OBJECTS:CatchTest>
 	./receiver_unit.cpp
 	../src/receiver.cpp
 	../src/stream.cpp
diff --git a/components/structures/include/ftl/data/frame.hpp b/components/structures/include/ftl/data/frame.hpp
index b0fa41922..14f1c1e25 100644
--- a/components/structures/include/ftl/data/frame.hpp
+++ b/components/structures/include/ftl/data/frame.hpp
@@ -335,12 +335,12 @@ template <int BASE, int N, typename STATE, typename DATA>
 template <typename T>
 T& ftl::data::Frame<BASE,N,STATE,DATA>::get(ftl::codecs::Channel channel) {
 	if (channel == ftl::codecs::Channel::None) {
-		throw ftl::exception("Attempting to get channel 'None'");
+		throw FTL_Error("Attempting to get channel 'None'");
 	}
 
 	// Add channel if not already there
 	if (!channels_.has(channel)) {
-		throw ftl::exception(ftl::Formatter() << "Frame channel does not exist: " << (int)channel);
+		throw FTL_Error("Frame channel does not exist: " << (int)channel);
 	}
 
 	return getData(channel).template as<T>();
@@ -351,7 +351,7 @@ template <int BASE, int N, typename STATE, typename DATA>
 template <typename T>
 const T& ftl::data::Frame<BASE,N,STATE,DATA>::get(ftl::codecs::Channel channel) const {
 	if (channel == ftl::codecs::Channel::None) {
-		throw ftl::exception("Attempting to get channel 'None'");
+		throw FTL_Error("Attempting to get channel 'None'");
 	} else if (channel == ftl::codecs::Channel::Pose) {
 		return state_.template as<T,ftl::codecs::Channel::Pose>();
 	} else if (channel == ftl::codecs::Channel::Calibration) {
@@ -364,7 +364,7 @@ const T& ftl::data::Frame<BASE,N,STATE,DATA>::get(ftl::codecs::Channel channel)
 
 	// Add channel if not already there
 	if (!channels_.has(channel)) {
-		throw ftl::exception(ftl::Formatter() << "Frame channel does not exist: " << (int)channel);
+		throw FTL_Error("Frame channel does not exist: " << (int)channel);
 	}
 
 	return getData(channel).template as<T>();
@@ -375,11 +375,11 @@ template <int BASE, int N, typename STATE, typename DATA>
 // cppcheck-suppress *
 template <typename T>
 void ftl::data::Frame<BASE,N,STATE,DATA>::get(ftl::codecs::Channel channel, T &params) const {
-	if (static_cast<int>(channel) < static_cast<int>(ftl::codecs::Channel::Data)) throw ftl::exception("Cannot use generic type with non data channel");
-	if (!hasChannel(channel)) throw ftl::exception("Data channel does not exist");
+	if (static_cast<int>(channel) < static_cast<int>(ftl::codecs::Channel::Data)) throw FTL_Error("Cannot use generic type with non data channel");
+	if (!hasChannel(channel)) throw FTL_Error("Data channel does not exist");
 
 	const auto &i = data_data_.find(static_cast<int>(channel));
-	if (i == data_data_.end()) throw ftl::exception("Data channel does not exist");
+	if (i == data_data_.end()) throw FTL_Error("Data channel does not exist");
 
 	auto unpacked = msgpack::unpack((const char*)(*i).second.data(), (*i).second.size());
 	unpacked.get().convert(params);
@@ -390,7 +390,7 @@ template <int BASE, int N, typename STATE, typename DATA>
 template <typename T>
 T &ftl::data::Frame<BASE,N,STATE,DATA>::create(ftl::codecs::Channel c) {
 	if (c == ftl::codecs::Channel::None) {
-		throw ftl::exception("Cannot create a None channel");
+		throw FTL_Error("Cannot create a None channel");
 	}
 	channels_ += c;
 
@@ -402,7 +402,7 @@ template <int BASE, int N, typename STATE, typename DATA>
 // cppcheck-suppress *
 template <typename T>
 void ftl::data::Frame<BASE,N,STATE,DATA>::create(ftl::codecs::Channel channel, const T &value) {
-	if (static_cast<int>(channel) < static_cast<int>(ftl::codecs::Channel::Data)) throw ftl::exception("Cannot use generic type with non data channel");
+	if (static_cast<int>(channel) < static_cast<int>(ftl::codecs::Channel::Data)) throw FTL_Error("Cannot use generic type with non data channel");
 
 	data_channels_ += channel;
 
@@ -415,7 +415,7 @@ void ftl::data::Frame<BASE,N,STATE,DATA>::create(ftl::codecs::Channel channel, c
 template <int BASE, int N, typename STATE, typename DATA>
 void ftl::data::Frame<BASE,N,STATE,DATA>::setOrigin(STATE *state) {
 	if (origin_ != nullptr) {
-		throw ftl::exception("Can only set origin once after reset");
+		throw FTL_Error("Can only set origin once after reset");
 	}
 
 	origin_ = state;
@@ -459,8 +459,8 @@ std::string ftl::data::Frame<BASE,N,STATE,DATA>::getConfigString() const {
 
 template <int BASE, int N, typename STATE, typename DATA>
 const std::vector<unsigned char> &ftl::data::Frame<BASE,N,STATE,DATA>::getRawData(ftl::codecs::Channel channel) const {
-	if (static_cast<int>(channel) < static_cast<int>(ftl::codecs::Channel::Data)) throw ftl::exception("Non data channel");
-	if (!hasChannel(channel)) throw ftl::exception("Data channel does not exist");
+	if (static_cast<int>(channel) < static_cast<int>(ftl::codecs::Channel::Data)) throw FTL_Error("Non data channel");
+	if (!hasChannel(channel)) throw FTL_Error("Data channel does not exist");
 
 	return data_data_.at(static_cast<int>(channel));
 }
diff --git a/components/structures/include/ftl/data/framestate.hpp b/components/structures/include/ftl/data/framestate.hpp
index 7d7255f60..3c06bd9a8 100644
--- a/components/structures/include/ftl/data/framestate.hpp
+++ b/components/structures/include/ftl/data/framestate.hpp
@@ -128,11 +128,11 @@ class FrameState {
 	 */
 	template <typename T, ftl::codecs::Channel C, typename S, int N> struct As {
 		static const T &func(const ftl::data::FrameState<S,N> &t) {
-			throw ftl::exception("Type not supported for state channel");
+			throw FTL_Error("Type not supported for state channel");
 		}
 
 		static T &func(ftl::data::FrameState<S,N> &t) {
-			throw ftl::exception("Type not supported for state channel");
+			throw FTL_Error("Type not supported for state channel");
 		}
 	};
 
-- 
GitLab