diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index da5fd16da6596671e3441b510943609e8881c50b..512a4d19087bd66c5c895f3f4c5595190122132c 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -25,14 +25,14 @@ linux:
   script:
     - mkdir build
     - cd build
-    - cmake ..
+    - cmake .. -DWITH_OPTFLOW=TRUE -DBUILD_CALIBRATION=TRUE
     - make
     - ctest --output-on-failure
 
 windows:
   stage: all
   variables:
-    CMAKE_ARGS: '-DWITH_PCL=FALSE -DCMAKE_GENERATOR_PLATFORM=x64 -DEigen3_DIR="C:/Program Files (x86)/Eigen3/share/eigen3/cmake" -DOpenCV_DIR="D:/opencv-4.0.1/build/install" -DCUDA_TOOLKIT_ROOT_DIR="C:/Program Files/NVIDIA GPU Computing Toolkit/CUDA/v10.1"'
+    CMAKE_ARGS: '-DWITH_OPTFLOW=TRUE -DWITH_PCL=FALSE -DCMAKE_GENERATOR_PLATFORM=x64 -DNVPIPE_DIR="D:/Build/NvPipe" -DEigen3_DIR="C:/Program Files (x86)/Eigen3/share/eigen3/cmake" -DOpenCV_DIR="D:/Build/opencv-4.1.1" -DCUDA_TOOLKIT_ROOT_DIR="C:/Program Files/NVIDIA GPU Computing Toolkit/CUDA/v10.1"'
     DEPLOY_DIR: 'D:/Shared/AutoDeploy'
   tags:
     - win
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7082c8a8da8c0f696c1ec532f4e083303846914a..b0aecb25a357a5c978db35a80cb20487adb5d08d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -6,16 +6,18 @@ include(CheckLanguage)
 
 project (ftl.utu.fi)
 
-set(CMAKE_CODELITE_USE_TARGETS ON)
 include(GNUInstallDirs)
 include(CTest)
 enable_testing()
 
-option(WITH_PCL "Use PCL if available" ON)
+option(WITH_PCL "Use PCL if available" OFF)
+option(WITH_NVPIPE "Use NvPipe for compression if available" ON)
+option(WITH_OPTFLOW "Use NVIDIA Optical Flow if available" OFF)
 option(WITH_FIXSTARS "Use Fixstars libSGM if available" ON)
 option(BUILD_VISION "Enable the vision component" ON)
 option(BUILD_RECONSTRUCT "Enable the reconstruction component" ON)
 option(BUILD_RENDERER "Enable the renderer component" ON)
+option(BUILD_CALIBRATION "Enable the calibration component" OFF)
 
 set(THREADS_PREFER_PTHREAD_FLAG ON)
 set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/")
@@ -28,6 +30,11 @@ find_package( URIParser REQUIRED )
 find_package( MsgPack REQUIRED )
 find_package( Eigen3 REQUIRED )
 
+if (WITH_OPTFLOW)
+	# TODO check that cudaoptflow.hpp exists (OpenCV built with correct contrib modules)
+	set(HAVE_OPTFLOW true)
+endif()
+
 find_package( LibArchive )
 if (LibArchive_FOUND)
 	set(HAVE_LIBARCHIVE true)
@@ -100,6 +107,28 @@ if (NANOGUI_LIBRARY)
     message(STATUS "Found NanoGUI: ${NANOGUI_LIBRARY}")
 endif()
 
+find_library( NVPIPE_LIBRARY NAMES NvPipe libNvPipe PATHS ${NVPIPE_DIR} PATH_SUFFIXES lib)
+if (NVPIPE_LIBRARY)
+	set(HAVE_NVPIPE TRUE)
+	add_library(nvpipe UNKNOWN IMPORTED)
+	#set_property(TARGET nanogui PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${NANOGUI_EXTRA_INCS})
+	set_property(TARGET nvpipe PROPERTY IMPORTED_LOCATION ${NVPIPE_LIBRARY})
+	message(STATUS "Found NvPipe: ${NVPIPE_LIBRARY}")
+	
+	if(WIN32)
+		# Find include
+		find_path(NVPIPE_INCLUDE_DIRS
+		    NAMES NvPipe.h
+		    PATHS "C:/Program Files/NvPipe" "C:/Program Files (x86)/NvPipe" ${NVPIPE_DIR}
+		    PATH_SUFFIXES include
+		)
+		set_property(TARGET nvpipe PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${NVPIPE_INCLUDE_DIRS})
+	endif()
+else()
+	set(NVPIPE_LIBRARY "")
+	add_library(nvpipe INTERFACE)
+endif()
+
 find_program( NODE_NPM NAMES npm )
 if (NODE_NPM)
 	message(STATUS "Found NPM: ${NODE_NPM}")
@@ -159,7 +188,7 @@ check_include_file_cxx("opencv2/cudastereo.hpp" HAVE_OPENCVCUDA)
 find_program(CPPCHECK_FOUND cppcheck)
 if (CPPCHECK_FOUND)
 	message(STATUS "Found cppcheck: will perform source checks")
-	set(CMAKE_CXX_CPPCHECK "cppcheck" "--enable=warning,performance,portability,style" "--inline-suppr" "--std=c++11" "--suppress=*:*catch.hpp" "--suppress=*:*elas*" "--suppress=*:*nanogui*" "--suppress=*:*json.hpp" "--quiet")
+	set(CMAKE_CXX_CPPCHECK "cppcheck" "-D__align__(A)" "-DCUDARTAPI" "--enable=warning,performance,style" "--inline-suppr" "--std=c++14" "--suppress=*:*catch.hpp" "--suppress=*:*elas*" "--suppress=*:*nanogui*" "--suppress=*:*json.hpp" "--quiet")
 endif()
 
 # include_directories(${PROJECT_SOURCE_DIR}/common/cpp/include)
@@ -185,6 +214,7 @@ SET(CMAKE_USE_RELATIVE_PATHS ON)
 set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
 
 add_subdirectory(components/common/cpp)
+add_subdirectory(components/codecs)
 add_subdirectory(components/net)
 add_subdirectory(components/rgbd-sources)
 add_subdirectory(components/control/cpp)
@@ -200,11 +230,16 @@ if (BUILD_VISION)
 	add_subdirectory(applications/vision)
 endif()
 
+if (BUILD_CALIBRATION)
+	find_package( cvsba REQUIRED )
+	add_subdirectory(applications/calibration-multi)
+endif()
+
 if (HAVE_PCL)
-	add_subdirectory(applications/registration)
+	#add_subdirectory(applications/registration)
 endif()
 
-if (BUILD_RECONSTRUCT AND HAVE_PCL)
+if (BUILD_RECONSTRUCT)
 	add_subdirectory(applications/reconstruct)
 endif()
 
@@ -232,3 +267,7 @@ if ( TARGET Qt5::Core )
 	set( CMAKE_CXX_COMPILE_OPTIONS_PIE "-fPIC" )
 endif()
 
+if (WIN32) # TODO(nick) Should do based upon compiler (VS)
+	set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT ${VS_STARTUP_PROJECT})
+	set_property(TARGET ftl-vision PROPERTY VS_DEBUGGER_WORKING_DIRECTORY ${VS_DEBUG_WORKING_DIRECTORY})
+endif()
\ No newline at end of file
diff --git a/README.md b/README.md
index caddf61ebab500e0d844f7ad07a40c70a98db65f..2269fe68c8b670690a14759aa463f343faaf5d0c 100644
--- a/README.md
+++ b/README.md
@@ -2,15 +2,18 @@
 
 This monorepo contains all elements of the FTL software system.
 
-* Components : Modular components compiled as static libs
-  * net : A p2p messaging library for C++ and JavaScript
-  * rgbd-sources : Abstraction and implementations of different RGB-Depth data sources
+* [Components](components/) : Modular components compiled as static libs
+  * [net](components/net/) : A p2p messaging library for C++ and JavaScript
+  * [rgbd-sources](components/rgbd-sources/) : Abstraction and implementations of different RGB-Depth data sources
   * renderers : A collection of visualisation tools, including for RGB-D and point clouds
   * scene-sources : Abstraction and implementations of 3D scene data sources
-  * common : Utility and configuration tools
-* Applications : Executable apps for the different node machines
-  * vision : Stereo vision node in p2p network, generates an RGB-Depth net stream
-  * reconstruct : Performs scene reconstruction from synchronised RGB-Depth sources
+  * [common](components/common/) : Utility and configuration tools
+* [Applications](applications/) : Executable apps for the different node machines
+  * [ftl-vision](applications/vision/) : Stereo vision node in p2p network, generates an RGB-Depth net stream
+  * [ftl-reconstruct](applications/reconstruct/) : Performs scene reconstruction from synchronised RGB-Depth sources
+  * calibration-multi : All camera intrinsic and extrinsic calibration in one process
+  * [ftl-view](applications/groupview/) : A quick camera viewing app that supports frame and video capture
+  * [ftl-gui](applications/gui/) : Desktop GUI
 * front-end : Client side FTL code, both web and native
 * web-service : A web backend service provider acting as a form of proxy
 * www : FTL Website
diff --git a/applications/calibration-multi/CMakeLists.txt b/applications/calibration-multi/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..ae1f1bb1698fbcbd1f8de3dc1b279123025d27b0
--- /dev/null
+++ b/applications/calibration-multi/CMakeLists.txt
@@ -0,0 +1,12 @@
+set(CALIBMULTI
+	src/main.cpp
+	src/visibility.cpp
+	src/util.cpp
+	src/multicalibrate.cpp
+)
+
+add_executable(ftl-calibrate-multi ${CALIBMULTI})
+
+target_include_directories(ftl-calibrate-multi PRIVATE src)
+
+target_link_libraries(ftl-calibrate-multi ftlcommon ftlnet ftlrgbd Threads::Threads ${OpenCV_LIBS} ${cvsba_LIBS})
diff --git a/applications/calibration-multi/src/main.cpp b/applications/calibration-multi/src/main.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ea770f69b03981164c2b6c97f95aa8841f1d8ae7
--- /dev/null
+++ b/applications/calibration-multi/src/main.cpp
@@ -0,0 +1,590 @@
+#include <loguru.hpp>
+
+#include <ftl/configuration.hpp>
+#include <ftl/net/universe.hpp>
+#include <ftl/rgbd/source.hpp>
+#include <ftl/rgbd/group.hpp>
+
+#include <opencv2/core.hpp>
+#include <opencv2/aruco.hpp>
+#include <opencv2/core/eigen.hpp>
+
+#include <algorithm>
+#include <numeric>
+#include <fstream>
+
+#include "util.hpp"
+#include "multicalibrate.hpp"
+
+using std::string;
+using std::optional;
+
+using std::list;
+using std::vector;
+using std::map;
+using std::pair;
+using std::make_pair;
+
+using cv::Mat;
+using cv::Scalar;
+using cv::Size;
+using cv::Point2f;
+using cv::Point2d;
+using cv::Point3f;
+using cv::Point3d;
+using cv::Vec4f;
+using cv::Vec4d;
+
+using ftl::net::Universe;
+using ftl::rgbd::Source;
+using ftl::rgbd::Channel;
+
+Mat getCameraMatrix(const ftl::rgbd::Camera &parameters) {
+	Mat m = (cv::Mat_<double>(3,3) << parameters.fx, 0.0, -parameters.cx, 0.0, parameters.fy, -parameters.cy, 0.0, 0.0, 1.0);
+	return m;
+}
+
+void to_json(nlohmann::json &json, map<string, Eigen::Matrix4d> &transformations) {
+	for (auto &item : transformations) {
+		auto val = nlohmann::json::array();
+		for(size_t i = 0; i < 16; i++) { val.push_back((float) item.second.data()[i]); }
+		json[item.first] = val;
+	}
+}
+
+// FileStorage allows only alphanumeric keys (code below does not work with URIs)
+
+bool saveRegistration(const string &ofile, const map<string, Mat> &data) {
+	cv::FileStorage fs(ofile, cv::FileStorage::WRITE);
+	if (!fs.isOpened()) return false;
+	for (auto &item : data) { fs << item.first << item.second; }
+	fs.release();
+	return true;
+}
+
+bool saveRegistration(const string &ofile, const map<string, Eigen::Matrix4d> &data) {
+	map<string, Mat> _data;
+	for (auto &item : data) {
+		Mat M;
+		cv::eigen2cv(item.second, M);
+		_data[item.first] = M; 
+	}
+	return saveRegistration(ofile, _data);
+}
+
+bool loadRegistration(const string &ifile, map<string, Mat> &data) {
+	cv::FileStorage fs(ifile, cv::FileStorage::READ);
+	if (!fs.isOpened()) return false;
+	for(cv::FileNodeIterator fit = fs.getFirstTopLevelNode().begin();
+		fit != fs.getFirstTopLevelNode().end();
+		++fit)
+	{
+		data[(*fit).name()] = (*fit).mat();
+	}
+	fs.release();
+	return true; // TODO errors?
+}
+
+bool loadRegistration(const string &ifile, map<string, Eigen::Matrix4d> &data) {
+	map<string, Mat> _data;
+	if (!loadRegistration(ifile, _data)) return false;
+	for (auto &item : _data) {
+		Eigen::Matrix4d M;
+		cv::cv2eigen(item.second, M);
+		data[item.first] = M;
+	}
+	return true;
+}
+
+//
+
+bool saveIntrinsics(const string &ofile, const vector<Mat> &M, const Size &size) {
+	vector<Mat> D;
+	{
+		cv::FileStorage fs(ofile, cv::FileStorage::READ);
+		fs["D"] >> D;
+		fs.release();
+	}
+	{
+		cv::FileStorage fs(ofile, cv::FileStorage::WRITE);
+		if (fs.isOpened()) {
+			fs << "resolution" << size;
+			fs << "K" << M << "D" << D;
+			fs.release();
+			return true;
+		}
+		else {
+			LOG(ERROR) << "Error: can not save the intrinsic parameters to '" << ofile << "'";
+		}
+		return false;
+	}
+}
+
+bool saveExtrinsics(const string &ofile, Mat &R, Mat &T, Mat &R1, Mat &R2, Mat &P1, Mat &P2, Mat &Q) {
+	cv::FileStorage fs;
+	fs.open(ofile, cv::FileStorage::WRITE);
+	if (fs.isOpened()) {
+		fs << "R" << R << "T" << T << "R1" << R1 << "R2" << R2 << "P1"
+			<< P1 << "P2" << P2 << "Q" << Q;
+		fs.release();
+		return true;
+	} else {
+		LOG(ERROR) << "Error: can not save the extrinsic parameters";
+	}
+	return false;
+}
+
+void stack(const vector<Mat> &img, Mat &out, const int rows, const int cols) {
+	Size size = img[0].size();
+	Size size_out = Size(size.width * cols, size.height * rows);
+	if (size_out != out.size() || out.type() != CV_8UC3) {
+		out = Mat(size_out, CV_8UC3, Scalar(0, 0, 0));
+	}
+
+	for (size_t i = 0; i < img.size(); i++) {
+		int row = i % rows;
+		int col = i / rows;
+		auto rect = cv::Rect(size.width * col, size.height * row, size.width, size.height);
+		img[i].copyTo(out(rect));
+	}
+}
+
+void stack(const vector<Mat> &img, Mat &out) {
+	// TODO
+	int rows = 2;
+	int cols = (img.size() + 1) / 2;
+	stack(img, out, rows, cols);
+}
+
+string time_now_string() {
+	char timestamp[18];
+	std::time_t t=std::time(NULL);
+	std::strftime(timestamp, sizeof(timestamp), "%F-%H%M%S", std::localtime(&t));
+	return string(timestamp);
+}
+
+void visualizeCalibration(	MultiCameraCalibrationNew &calib, Mat &out,
+				 			vector<Mat> &rgb, const vector<Mat> &map1,
+							const vector<Mat> &map2, const vector<cv::Rect> &roi)
+{
+	vector<Scalar> colors = {
+		Scalar(64, 64, 255),
+		Scalar(64, 64, 255),
+		Scalar(64, 255, 64),
+		Scalar(64, 255, 64),
+	};
+
+	vector<int> markers = {cv::MARKER_SQUARE, cv::MARKER_DIAMOND};
+
+	for (size_t c = 0; c < rgb.size(); c++) {
+		cv::remap(rgb[c], rgb[c], map1[c], map2[c], cv::INTER_CUBIC);
+		cv::rectangle(rgb[c], roi[c], Scalar(24, 224, 24), 2);
+
+		for (int r = 50; r < rgb[c].rows; r = r+50) {
+			cv::line(rgb[c], cv::Point(0, r), cv::Point(rgb[c].cols-1, r), cv::Scalar(0,0,255), 1);
+		}
+	}
+
+	stack(rgb, out);
+}
+
+struct CalibrationParams {
+	string output_path;
+	string registration_file;
+	vector<size_t> idx_cameras;
+	bool save_extrinsic = true;
+	bool save_intrinsic = false;
+	bool optimize_intrinsic = false;
+	int reference_camera = -1;
+	double alpha = 0.0;
+	Size size;
+};
+
+void calibrate(	MultiCameraCalibrationNew &calib, vector<string> &uri_cameras,
+				const CalibrationParams &params, vector<Mat> &map1, vector<Mat> &map2, vector<cv::Rect> &roi)
+{
+	int reference_camera = -1;
+	if (params.reference_camera < 0) {
+		reference_camera = calib.getOptimalReferenceCamera();
+		reference_camera -= (reference_camera & 1);
+		LOG(INFO) << "optimal camera (automatic): " << reference_camera;
+	}
+	LOG(INFO) << "reference camera: " << reference_camera;
+
+	if (params.optimize_intrinsic) calib.setFixIntrinsic(0);
+
+	calib.calibrateAll(reference_camera);
+	vector<Mat> R, t;
+	calib.getCalibration(R, t);
+
+	size_t n_cameras = calib.getCamerasCount();
+
+	vector<Mat> R_rect(n_cameras), t_rect(n_cameras);
+	vector<Mat> Rt_out(n_cameras);
+	map1.resize(n_cameras);
+	map2.resize(n_cameras);
+	roi.resize(n_cameras);
+
+	for (size_t c = 0; c < n_cameras; c += 2) {
+		Mat K1 = calib.getCameraMat(c);
+		Mat K2 = calib.getCameraMat(c + 1);
+		Mat D1 = calib.getDistCoeffs(c);
+		Mat D2 = calib.getDistCoeffs(c + 1);
+		Mat P1, P2, Q;
+		Mat R1, R2;
+		Mat R_c1c2, T_c1c2;
+
+		calculateTransform(R[c], t[c], R[c + 1], t[c + 1], R_c1c2, T_c1c2);
+		cv::stereoRectify(K1, D1, K2, D2, params.size, R_c1c2, T_c1c2, R1, R2, P1, P2, Q, 0, params.alpha);
+
+		Mat _t = Mat(Size(1, 3), CV_64FC1, Scalar(0.0));
+		Rt_out[c] = getMat4x4(R[c], t[c]) * getMat4x4(R1, _t).inv();
+		Rt_out[c + 1] = getMat4x4(R[c + 1], t[c + 1]) * getMat4x4(R2, _t).inv();
+
+		{
+			string node_name;
+			size_t pos1 = uri_cameras[c/2].find("node");
+			size_t pos2 = uri_cameras[c/2].find("#", pos1);
+			node_name = uri_cameras[c/2].substr(pos1, pos2 - pos1);
+			
+			if (params.save_extrinsic) {
+				// TODO:	only R and T required, rectification performed on vision node,
+				//			consider saving global extrinsic calibration?
+				saveExtrinsics(params.output_path + node_name + "-extrinsic.yml", R_c1c2, T_c1c2, R1, R2, P1, P2, Q);
+				LOG(INFO) << "Saved: " << params.output_path + node_name + "-extrinsic.yml";
+			}
+			if (params.save_intrinsic) {
+				saveIntrinsics(
+					params.output_path + node_name + "-intrinsic.yml",
+					{calib.getCameraMat(c),
+					 calib.getCameraMat(c + 1)},
+					params.size
+
+				);
+				LOG(INFO) << "Saved: " << params.output_path + node_name + "-intrinsic.yml";
+			}
+		}
+
+		// for visualization
+		Size new_size;
+		cv::stereoRectify(K1, D1, K2, D2, params.size, R_c1c2, T_c1c2, R1, R2, P1, P2, Q, 0, 1.0, new_size, &roi[c], &roi[c + 1]);
+		cv::initUndistortRectifyMap(K1, D1, R1, P1, params.size, CV_16SC2, map1[c], map2[c]);
+		cv::initUndistortRectifyMap(K2, D2, R2, P2, params.size, CV_16SC2, map1[c + 1], map2[c + 1]);
+	}
+
+	{
+		map<string, Eigen::Matrix4d> out;
+		for (size_t i = 0; i < n_cameras; i += 2) {
+			Eigen::Matrix4d M_eigen;
+			Mat M_cv = Rt_out[i];
+			cv::cv2eigen(M_cv, M_eigen);
+			out[uri_cameras[i/2]] = M_eigen;
+		}
+		
+		nlohmann::json out_json;
+		to_json(out_json, out);
+		if (params.save_extrinsic) {
+			std::ofstream file_out(params.registration_file);
+			file_out << out_json;
+		}
+		else {
+			LOG(INFO) << "Registration not saved to file";
+			LOG(INFO) << out_json;
+		}
+	}
+}
+
+void calibrateFromPath(	const string &path,
+						const string &filename,
+						CalibrationParams &params,
+						bool visualize=false)
+{
+	size_t reference_camera = 0;
+	auto calib = MultiCameraCalibrationNew(0, reference_camera, Size(0, 0), CalibrationTarget(0.250));
+	
+	vector<string> uri_cameras;
+	cv::FileStorage fs(path + filename, cv::FileStorage::READ);
+	fs["uri"] >> uri_cameras;
+	fs["resolution"] >> params.size;
+
+	//params.idx_cameras = {2, 3};//{0, 1, 4, 5, 6, 7, 8, 9, 10, 11};
+	params.idx_cameras.resize(uri_cameras.size() * 2);
+	std::iota(params.idx_cameras.begin(), params.idx_cameras.end(), 0);
+
+	calib.loadInput(path + filename, params.idx_cameras);
+	
+	vector<Mat> map1, map2;
+	vector<cv::Rect> roi;
+	calibrate(calib, uri_cameras, params, map1, map2, roi);
+
+	if (!visualize) return;
+
+	vector<Scalar> colors = {
+		Scalar(64, 64, 255),
+		Scalar(64, 64, 255),
+		Scalar(64, 255, 64),
+		Scalar(64, 255, 64),
+	};
+	vector<int> markers = {cv::MARKER_SQUARE, cv::MARKER_DIAMOND};
+
+	Mat out;
+	size_t n_cameras = calib.getCamerasCount();
+	vector<Mat> rgb(n_cameras);
+	size_t i = 0;
+	while(ftl::running) {
+		for (size_t c = 0; c < n_cameras; c++) {
+			rgb[c] = cv::imread(path + std::to_string(params.idx_cameras[c]) + "_" + std::to_string(i) + ".jpg");
+			for (size_t j = 0; j < rgb.size(); j++) {
+				vector<Point2d> points;
+				// TODO: indexing incorrect if all cameras used (see also loadInput)
+				calib.projectPointsOptimized(c, i, points); // project BA point to image
+
+				for (Point2d &p : points) {
+					cv::drawMarker(rgb[c], cv::Point2i(p), colors[j % colors.size()], markers[j % markers.size()], 10 + 3 * j, 1);
+				}
+			}
+		}
+		
+		visualizeCalibration(calib, out, rgb, map1, map2, roi);	
+		cv::namedWindow("Calibration", cv::WINDOW_KEEPRATIO | cv::WINDOW_NORMAL);
+		cv::imshow("Calibration", out);
+
+		i = (i + 1) % calib.getViewsCount();
+		
+		if (cv::waitKey(50) != -1) { ftl::running = false; }
+	}
+}
+
+void runCameraCalibration(	ftl::Configurable* root,
+							int n_views, int min_visible,
+							string path, string filename,
+							bool save_input,
+							CalibrationParams &params)
+{
+	Universe *net = ftl::create<Universe>(root, "net");
+	net->start();
+	net->waitConnections();
+
+	vector<Source*> sources = ftl::createArray<Source>(root, "sources", net);
+	
+	const size_t n_sources = sources.size();
+	const size_t n_cameras = n_sources * 2;
+	size_t reference_camera = 0;
+	
+	{
+		auto camera = sources[0]->parameters();
+		params.size = Size(camera.width, camera.height);
+		LOG(INFO) << "Camera resolution: " << params.size;
+	}
+
+	params.idx_cameras.resize(n_cameras);
+	std::iota(params.idx_cameras.begin(), params.idx_cameras.end(), 0);
+
+	// TODO: parameter for calibration target type
+	auto calib = MultiCameraCalibrationNew(	n_cameras, reference_camera,
+											params.size, CalibrationTarget(0.250)
+	);
+
+	for (size_t i = 0; i < n_sources; i++) {
+		auto camera_r = sources[i]->parameters(Channel::Right);
+		auto camera_l = sources[i]->parameters(Channel::Left);
+
+		CHECK(params.size == Size(camera_r.width, camera_r.height));
+		CHECK(params.size == Size(camera_l.width, camera_l.height));
+		
+		Mat K;
+		K = getCameraMatrix(camera_r);
+		LOG(INFO) << "K[" << 2 * i + 1 << "] = \n" << K;
+		calib.setCameraParameters(2 * i + 1, K);
+
+		K = getCameraMatrix(camera_l);
+		LOG(INFO) << "K[" << 2 * i << "] = \n" << K;
+		calib.setCameraParameters(2 * i, K);
+	}
+
+	ftl::rgbd::Group group;
+	for (Source* src : sources) {
+		src->setChannel(Channel::Right);
+		group.addSource(src);
+	}
+
+	std::mutex mutex;
+	std::atomic<bool> new_frames = false;
+	vector<Mat> rgb(n_cameras), rgb_new(n_cameras);
+	
+	ftl::timer::start(false);
+
+	group.sync([&mutex, &new_frames, &rgb_new](ftl::rgbd::FrameSet &frames) {
+		mutex.lock();
+		bool good = true;
+		for (size_t i = 0; i < frames.sources.size(); i ++) {
+			if (frames.frames[i].get<cv::Mat>(Channel::Left).empty()) good = false;
+			if (frames.frames[i].get<cv::Mat>(Channel::Right).empty()) good = false;
+			if (frames.frames[i].get<cv::Mat>(Channel::Left).channels() != 3) good = false; // ASSERT
+			if (frames.frames[i].get<cv::Mat>(Channel::Right).channels() != 3) good = false;
+			if (!good) break;
+			cv::swap(frames.frames[i].get<cv::Mat>(Channel::Left), rgb_new[2 * i]);
+			cv::swap(frames.frames[i].get<cv::Mat>(Channel::Right), rgb_new[2 * i + 1]);
+		}
+
+		new_frames = good;
+		mutex.unlock();
+		return true;
+	});
+	
+	int iter = 0;
+	Mat show;
+
+	vector<int> visible;
+	vector<vector<Point2d>> points(n_cameras);
+
+	while(calib.getMinVisibility() < n_views) {
+		cv::waitKey(10);
+		while (!new_frames) {
+			for (auto src : sources) { src->grab(30); }
+			cv::waitKey(10);
+		}
+
+		mutex.lock();
+		rgb.swap(rgb_new);
+		new_frames = false;
+		mutex.unlock();
+
+		visible.clear();
+		int n_found = findCorrespondingPoints(rgb, points, visible);
+
+		if (n_found >= min_visible) {
+			calib.addPoints(points, visible);
+			
+			if (save_input) {
+				for (size_t i = 0; i < n_cameras; i++) {
+					cv::imwrite(path + std::to_string(i) + "_" + std::to_string(iter) + ".jpg", rgb[i]);
+				}
+			}
+			iter++;
+		}
+
+		for (size_t i = 0; i < n_cameras; i++) {
+			if (visible[i]) {
+				cv::drawMarker(	rgb[i], points[i][0],
+								Scalar(42, 255, 42), cv::MARKER_TILTED_CROSS, 25, 2);
+				cv::drawMarker(	rgb[i], points[i][1],
+								Scalar(42, 42, 255), cv::MARKER_TILTED_CROSS, 25, 2);
+			}
+			cv::putText(rgb[i],
+						"Camera " + std::to_string(i),
+						Point2i(10, 30),
+						cv::FONT_HERSHEY_COMPLEX_SMALL, 1.0, Scalar(64, 64, 255), 1);
+			
+			cv::putText(rgb[i],
+						std::to_string(std::max(0, (int) (n_views - calib.getViewsCount(i)))),
+						Point2i(10, rgb[i].rows-10),
+						cv::FONT_HERSHEY_COMPLEX_SMALL, 1.0, Scalar(64, 64, 255), 1);
+
+		}
+
+		stack(rgb, show);
+		cv::namedWindow("Cameras", cv::WINDOW_KEEPRATIO | cv::WINDOW_NORMAL);
+		cv::imshow("Cameras", show);
+	}
+	cv::destroyWindow("Cameras");
+
+	vector<string> uri;
+	for (size_t i = 0; i < n_sources; i++) {
+		uri.push_back(sources[i]->getURI());
+	}
+
+	if (save_input) {
+		cv::FileStorage fs(path + filename, cv::FileStorage::WRITE);
+		fs << "uri" << uri;
+		calib.saveInput(fs);
+		fs.release();
+	}
+
+	Mat out;
+	vector<Mat> map1, map2;
+	vector<cv::Rect> roi;
+	vector<size_t> idx;
+	calibrate(calib, uri, params, map1, map2, roi);
+
+	// visualize
+	while(ftl::running) {
+		while (!new_frames) {
+			for (auto src : sources) { src->grab(30); }
+			if (cv::waitKey(50) != -1) { ftl::running = false; }
+		}
+
+		mutex.lock();
+		rgb.swap(rgb_new);
+		new_frames = false;
+		mutex.unlock();
+
+		visualizeCalibration(calib, out, rgb, map1, map2, roi);
+		cv::namedWindow("Calibration", cv::WINDOW_KEEPRATIO | cv::WINDOW_NORMAL);
+		cv::imshow("Calibration", out);
+	}
+}
+
+int main(int argc, char **argv) {
+	auto options = ftl::config::read_options(&argv, &argc);
+	auto root = ftl::configure(argc, argv, "registration_default");
+	
+	// run calibration from saved input?
+	const bool load_input = root->value<bool>("load_input", false);
+	// should calibration input be saved
+	const bool save_input = root->value<bool>("save_input", false);
+	// should extrinsic calibration be saved (only used with load_input)
+	const bool save_extrinsic = root->value<bool>("save_extrinsic", true);
+	// should intrinsic calibration be saved
+	const bool save_intrinsic = root->value<bool>("save_intrinsic", false);
+	const bool optimize_intrinsic = root->value<bool>("optimize_intrinsic", false);
+	// directory where calibration data and images are saved, if save_input enabled
+	const string calibration_data_dir = root->value<string>("calibration_data_dir", "./");
+	// file to save calibration input (2d points and visibility)
+	const string calibration_data_file = root->value<string>("calibration_data_file", "data.yml");
+	// in how many cameras should the pattern be visible
+	const int min_visible = root->value<int>("min_visible", 3);
+	// minimum for how many times pattern is seen per camera
+	const int n_views = root->value<int>("n_views", 500);
+	// reference camera, -1 for automatic
+	const int ref_camera = root->value<int>("reference_camera", -1);
+	// registration file path
+	const string registration_file = root->value<string>("registration_file", FTL_LOCAL_CONFIG_ROOT "/registration.json");
+	// location where extrinsic calibration files saved
+	const string output_directory = root->value<string>("output_directory", "./");
+	
+	CalibrationParams params;
+	params.save_extrinsic = save_extrinsic;
+	params.save_intrinsic = save_intrinsic;
+	params.optimize_intrinsic = optimize_intrinsic;
+	params.output_path = output_directory;
+	params.registration_file = registration_file;
+	params.reference_camera = ref_camera;
+	
+	LOG(INFO)	<< "\n"
+				<< "\nIMPORTANT: Remeber to set \"use_intrinsics\" to false for nodes!"
+				<< "\n"
+				<< "\n                save_input: " << (int) save_input
+				<< "\n                load_input: " << (int) load_input
+				<< "\n            save_extrinsic: " << (int) save_extrinsic
+				<< "\n            save_intrinsic: " << (int) save_intrinsic
+				<< "\n        optimize_intrinsic: " << (int) optimize_intrinsic
+				<< "\n      calibration_data_dir: " << calibration_data_dir
+				<< "\n     calibration_data_file: " << calibration_data_file
+				<< "\n               min_visible: " << min_visible
+				<< "\n                   n_views: " << n_views
+				<< "\n          reference_camera: " << ref_camera << (ref_camera != -1 ? "" : " (automatic)")
+				<< "\n         registration_file: " << registration_file
+				<< "\n          output_directory: " << output_directory
+				<< "\n";
+
+	if (load_input) {
+		vector<size_t> idx = {};
+		calibrateFromPath(calibration_data_dir, calibration_data_file, params, true);
+	}
+	else {
+		runCameraCalibration(root, n_views, min_visible, calibration_data_dir, calibration_data_file, save_input, params);
+	}
+
+	return 0;
+}
\ No newline at end of file
diff --git a/applications/calibration-multi/src/multicalibrate.cpp b/applications/calibration-multi/src/multicalibrate.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..9d85d1af7ae938981bb445a852fd85a913c6e26e
--- /dev/null
+++ b/applications/calibration-multi/src/multicalibrate.cpp
@@ -0,0 +1,819 @@
+#include "multicalibrate.hpp"
+
+#include <opencv2/core.hpp>
+#include <opencv2/calib3d.hpp>
+
+#include <cvsba/cvsba.h>
+#include <loguru.hpp>
+
+#include <map>
+
+using cv::Mat;
+using cv::Size;
+using cv::Point2d;
+using cv::Point3d;
+using cv::Vec4d;
+using cv::Scalar;
+
+using std::string;
+using std::vector;
+using std::map;
+using std::pair;
+using std::make_pair;
+
+double CalibrationTarget::estimateScale(vector<Point3d> points) {
+	
+	// 1. calculate statistics 
+	// 2. reject possible outliers 
+	// 3. calculate scale factor
+
+	double f = 0.0;
+	double S = 0.0;
+	double m = 0.0;
+	
+	vector<double> d(points.size() / 2, 0.0);
+
+	for (size_t i = 0; i < points.size(); i += 2) {
+		const Point3d &p1 = points[i];
+		const Point3d &p2 = points[i + 1];
+
+		Point3d p = p1 - p2;
+
+		double x = sqrt(p.x * p.x + p.y * p.y + p.z * p.z);
+		double prev_mean = m;
+		d[i/2] = x;
+
+		f = f + 1.0;
+		m = m + (x - m) / f;
+		S = S + (x - m) * (x - prev_mean);
+
+	}
+
+	double stddev = sqrt(S / f);
+	f = 0.0;
+
+	int outliers = 0;
+	double scale = 0.0;
+
+	for (double l : d) {
+		// TODO:	* Parameterize how large deviation allowed
+		//			* Validate this actually improves quality
+
+		if (abs(l - m) > 3.0 * stddev) {
+			outliers++;
+		}
+		else {
+			f += 1.0;
+			scale += 1.0 / l;
+		}
+		DCHECK(scale != INFINITY);
+	}
+
+	if (outliers != 0) {
+		LOG(WARNING) << "Outliers (large std. deviation in scale): " << outliers;
+	}
+
+	LOG(INFO) << "calibration target std. dev. " <<  stddev << " (" << (int) f << " samples), scale: " << scale * calibration_bar_length_ / f;
+
+	return scale * calibration_bar_length_ / f;
+
+	// TODO:	LM-optimization for scale.
+}
+
+MultiCameraCalibrationNew::MultiCameraCalibrationNew(
+			size_t n_cameras, size_t reference_camera, Size resolution,
+			CalibrationTarget target, int fix_intrinsics) :
+		
+	target_(target),
+	visibility_graph_(n_cameras),
+	is_calibrated_(false),
+	n_cameras_(n_cameras),
+	reference_camera_(reference_camera),
+	min_visible_points_(50),
+	fix_intrinsics_(fix_intrinsics == 1 ? 5 : 0),
+
+	resolution_(resolution),
+	K_(n_cameras),
+	dist_coeffs_(n_cameras),
+	R_(n_cameras),
+	t_(n_cameras),
+
+	points3d_optimized_(n_cameras),
+	points3d_(n_cameras),
+	points2d_(n_cameras),
+	visible_(n_cameras),
+
+	fm_method_(cv::FM_8POINT), // RANSAC/LMEDS results need validation (does not work)
+	fm_ransac_threshold_(0.95),
+	fm_confidence_(0.90)
+{
+	for (auto &K : K_) { K = Mat::eye(Size(3, 3), CV_64FC1); }
+	for (auto &d : dist_coeffs_) { d = Mat(Size(5, 1), CV_64FC1, Scalar(0.0)); }
+}
+
+Mat MultiCameraCalibrationNew::getCameraMat(size_t idx) {
+	DCHECK(idx < n_cameras_);
+	Mat K;
+	K_[idx].copyTo(K);
+	return K;
+}
+
+
+Mat MultiCameraCalibrationNew::getCameraMatNormalized(size_t idx, double scale_x, double scale_y)
+{
+	Mat K = getCameraMat(idx);
+	CHECK((scale_x != 0.0 && scale_y != 0.0) || ((scale_x == 0.0) && scale_y == 0.0));
+
+	scale_x = scale_x / (double) resolution_.width;
+	scale_y = scale_y / (double) resolution_.height;
+
+	Mat scale(Size(3, 3), CV_64F, 0.0);
+	scale.at<double>(0, 0) = scale_x;
+	scale.at<double>(1, 1) = scale_y;
+	scale.at<double>(2, 2) = 1.0;
+	
+	return (scale * K);
+}
+
+Mat MultiCameraCalibrationNew::getDistCoeffs(size_t idx) {
+	DCHECK(idx < n_cameras_);
+	Mat D;
+	dist_coeffs_[idx].copyTo(D);
+	return D;
+}
+
+void MultiCameraCalibrationNew::setCameraParameters(size_t idx, const Mat &K, const Mat &distCoeffs) {
+	DCHECK(idx < n_cameras_);
+	DCHECK(K.size() == Size(3, 3));
+	DCHECK(distCoeffs.size() == Size(5, 1));
+	K.convertTo(K_[idx], CV_64FC1);
+	distCoeffs.convertTo(dist_coeffs_[idx], CV_64FC1);
+}
+
+void MultiCameraCalibrationNew::setCameraParameters(size_t idx, const Mat &K) {
+	DCHECK(idx < n_cameras_);
+	setCameraParameters(idx, K, dist_coeffs_[idx]);
+}
+
+void MultiCameraCalibrationNew::addPoints(vector<vector<Point2d>> points, vector<int> visible) {
+	DCHECK(points.size() == visible.size());
+	DCHECK(visible.size() == n_cameras_);
+
+	for (size_t i = 0; i < n_cameras_; i++) {
+		visible_[i].insert(visible_[i].end(), points[i].size(), visible[i]);
+		points2d_[i].insert(points2d_[i].end(), points[i].begin(), points[i].end());
+	}
+	visibility_graph_.update(visible);
+}
+
+void MultiCameraCalibrationNew::reset() {
+	is_calibrated_ = false;
+	weights_ = vector(n_cameras_, vector(points2d_[0].size(), 0.0));
+	inlier_ = vector(n_cameras_, vector(points2d_[0].size(), 0));
+	points3d_ = vector(n_cameras_, vector(points2d_[0].size(), Point3d()));
+	points3d_optimized_ = vector(points2d_[0].size(), Point3d());
+	R_ = vector<Mat>(n_cameras_, Mat::eye(Size(3, 3), CV_64FC1));
+	t_ = vector<Mat>(n_cameras_, Mat(Size(1, 3), CV_64FC1, Scalar(0.0)));
+}
+
+void MultiCameraCalibrationNew::saveInput(const string &filename) {
+	cv::FileStorage fs(filename, cv::FileStorage::WRITE);
+	saveInput(fs);
+	fs.release();
+}
+
+void MultiCameraCalibrationNew::saveInput(cv::FileStorage &fs) {
+	fs << "resolution" << resolution_;
+	fs << "K" << K_;
+	fs << "points2d" << points2d_;
+	fs << "visible" << visible_;
+}
+
+void MultiCameraCalibrationNew::loadInput(const std::string &filename, const vector<size_t> &cameras_in) {
+	points2d_.clear();
+	points3d_.clear();
+	points3d_optimized_.clear();
+	visible_.clear();
+	inlier_.clear();
+
+	cv::FileStorage fs(filename, cv::FileStorage::READ);
+	vector<Mat> K;
+	vector<vector<Point2d>> points2d;
+	vector<vector<int>> visible;
+	fs["K"] >> K;
+	fs["points2d"] >> points2d;
+	fs["visible"] >> visible;
+	fs["resolution"] >> resolution_;
+	fs.release();
+	
+	vector<size_t> cameras;
+	if (cameras_in.size() == 0) {
+		cameras.resize(K.size());
+		size_t i = 0;
+		for (auto &c : cameras) { c = i++; }
+	} 
+	else {
+		cameras.reserve(cameras_in.size());
+		for (auto &c : cameras_in) { cameras.push_back(c); }
+	}
+	
+	n_cameras_ = cameras.size();
+
+	points2d_.resize(n_cameras_);
+	points3d_.resize(n_cameras_);
+	visible_.resize(n_cameras_);
+
+	for (auto const &c : cameras) {
+		K_.push_back(K[c]);
+	}
+	for (size_t c = 0; c < n_cameras_; c++) {
+		points2d_[c].reserve(visible[0].size());
+		points3d_[c].reserve(visible[0].size());
+		visible_[c].reserve(visible[0].size());
+		points3d_optimized_.reserve(visible[0].size());
+	}
+
+	visibility_graph_ = Visibility(n_cameras_);
+	dist_coeffs_.resize(n_cameras_);
+	for (auto &d : dist_coeffs_ ) { d = Mat(Size(5, 1), CV_64FC1, Scalar(0.0)); }
+
+	vector<vector<Point2d>> points2d_add(n_cameras_, vector<Point2d>());
+	vector<int> visible_add(n_cameras_);
+	for (size_t i = 0; i < visible[0].size(); i += target_.n_points) {
+		int count = 0;
+		for (size_t c = 0; c < n_cameras_; c++) {
+			count += visible[c][i];
+			points2d_add[c].clear();
+			points2d_add[c].insert(
+								points2d_add[c].begin(),
+								points2d[cameras[c]].begin() + i,
+								points2d[cameras[c]].begin() + i + target_.n_points);
+			visible_add[c] = visible[cameras[c]][i];
+		}
+		if (count >= 2) {
+			addPoints(points2d_add, visible_add);
+		}
+	}
+	reset();
+	
+	DCHECK(points2d_.size() == n_cameras_);
+	DCHECK(points2d_.size() == visible_.size());
+	size_t len = visible_[0].size();
+	for (size_t i = 0; i < n_cameras_; i++) {
+		DCHECK(visible_[i].size() == len);
+		DCHECK(points2d_[i].size() == visible_[i].size());
+	}
+}
+
+size_t MultiCameraCalibrationNew::getViewsCount() {
+	return points2d_[0].size() / target_.n_points;
+}
+
+size_t MultiCameraCalibrationNew::getOptimalReferenceCamera() {
+	return (size_t) visibility_graph_.getOptimalCamera();
+}
+
+bool MultiCameraCalibrationNew::isVisible(size_t camera, size_t idx) {
+	return visible_[camera][idx] == 1;
+}
+
+bool MultiCameraCalibrationNew::isValid(size_t camera, size_t idx) {
+	return inlier_[camera][idx] >= 0;
+}
+
+bool MultiCameraCalibrationNew::isValid(size_t idx) {
+	for (auto camera : inlier_) {
+		if (camera[idx] > 0) return true;
+	}
+	return false;
+}
+
+vector<Point2d> MultiCameraCalibrationNew::getPoints(size_t camera, size_t idx) {
+	return vector<Point2d> (points2d_[camera].begin() + idx * (target_.n_points), 
+							points2d_[camera].begin() + idx * (target_.n_points + 1));
+}
+
+
+void MultiCameraCalibrationNew::updatePoints3D(size_t camera, Point3d new_point,
+		size_t idx, const Mat &R, const Mat &t) {
+	
+	int &f = inlier_[camera][idx];
+	Point3d &point = points3d_[camera][idx];
+	new_point = transformPoint(new_point, R, t);
+
+	if (f == -1) return;
+
+	if (f > 0) {
+		// TODO:	remove parameter (10.0 cm - 1.0m); over 0.25m difference
+		//			would most likely suggest very bad triangulation (sync? wrong match?)
+		// 			instead store all triangulations and handle outliers
+		//			(perhaps inverse variance weighted mean?)
+		
+		if (euclideanDistance(point, new_point) > 10.0) {
+			LOG(ERROR) << "bad value (skipping) " << "(" << point << " vs " << new_point << ")";
+			f = -1;
+		}
+		else {
+			point = (point * f + new_point) / (double) (f + 1);
+			f++;
+		}
+	}
+	else {
+		point = new_point;
+		f = 1;
+	}
+}
+
+void MultiCameraCalibrationNew::updatePoints3D(size_t camera, vector<Point3d> points,
+		vector<size_t> idx, const Mat &R, const Mat &t) {
+	
+	for (size_t i = 0; i < idx.size(); i++) {
+		updatePoints3D(camera, points[i], idx[i], R, t);
+	}
+}
+
+void MultiCameraCalibrationNew::getVisiblePoints(
+		vector<size_t> cameras, vector<vector<Point2d>> &points, vector<size_t> &idx) {
+	
+	size_t n_points_total = points2d_[0].size();
+	DCHECK(cameras.size() <= n_cameras_);
+	DCHECK(n_points_total % target_.n_points == 0);
+	
+	idx.clear();
+	idx.reserve(n_points_total);
+	points.clear();
+	points.resize(cameras.size(), {});
+	
+	for (size_t i = 0; i < n_points_total; i += target_.n_points) {
+		bool visible_all = true;
+
+		for (auto c : cameras) {
+			for (size_t j = 0; j < target_.n_points; j++) {
+				visible_all &= isVisible(c, i + j);
+			}
+		}
+		
+		if (!visible_all) { continue; }
+
+		for (size_t j = 0; j < target_.n_points; j++) {
+			idx.push_back(i + j);
+		}
+
+		for (size_t c = 0; c < cameras.size(); c++) {
+			points[c].insert(points[c].end(),
+							 points2d_[cameras[c]].begin() + i,
+							 points2d_[cameras[c]].begin() + i + target_.n_points
+			);
+		}
+	}
+
+	for (auto p : points) {	DCHECK(idx.size() == p.size()); }
+}
+
+double MultiCameraCalibrationNew::calibratePair(size_t camera_from, size_t camera_to, Mat &rmat, Mat &tvec) {
+	
+	vector<size_t> idx;
+	vector<Point2d> points1, points2;
+	{
+		vector<vector<Point2d>> points2d;
+		getVisiblePoints({camera_from, camera_to}, points2d, idx);
+
+		points1 = points2d[0];
+		points2 = points2d[1];
+	}
+	DCHECK(points1.size() % target_.n_points == 0);
+	DCHECK(points1.size() == points2.size());
+
+	// cameras possibly lack line of sight?
+	DCHECK(points1.size() > 8);
+
+	Mat &K1 = K_[camera_from];
+	Mat &K2 = K_[camera_to];
+
+	vector<uchar> inliers;
+	Mat F, E;
+	F = cv::findFundamentalMat(points1, points2, fm_method_, fm_ransac_threshold_, fm_confidence_, inliers);
+
+	if (F.empty())
+	{
+		LOG(ERROR) << "Fundamental matrix estimation failed. Possibly degenerate configuration?";
+		return INFINITY;
+	}
+
+	E = K2.t() * F * K1;
+
+	// Only include inliers
+	if (fm_method_ == cv::FM_LMEDS || fm_method_ == cv::FM_RANSAC) {
+		vector<Point2d> inliers1, inliers2;
+		vector<size_t> inliers_idx;
+
+		inliers1.reserve(points1.size());
+		inliers2.reserve(points1.size());
+		inliers_idx.reserve(points1.size());
+
+		for (size_t i = 0; i < inliers.size(); i += target_.n_points) {
+			bool inlier = true;
+			
+			for (size_t j = 0; j < target_.n_points; j++) {
+				inlier &= inliers[i+j];
+			}
+
+			if (inlier) {
+				inliers1.insert(inliers1.end(), points1.begin() + i, points1.begin() + i + target_.n_points);
+				inliers2.insert(inliers2.end(), points2.begin() + i, points2.begin() + i + target_.n_points);
+				inliers_idx.insert(inliers_idx.end(), idx.begin() + i, idx.begin() + i + target_.n_points);
+			}
+		}
+		
+		LOG(INFO) << "Total points: " << points1.size() << ", inliers: " << inliers1.size();
+		double ratio_good_points = (double) inliers1.size() / (double) points1.size();
+		if (ratio_good_points < 0.66) {
+			// TODO: ... 
+			LOG(WARNING) << "Over 1/3 of points rejected!";
+			if (ratio_good_points < 0.33) { LOG(FATAL) << "Over 2/3 points rejected!"; }
+		}
+		
+		DCHECK(inliers1.size() == inliers_idx.size());
+		DCHECK(inliers2.size() == inliers_idx.size());
+
+		std::swap(inliers1, points1);
+		std::swap(inliers2, points2);
+		std::swap(inliers_idx, idx);
+	}
+	
+	// Estimate initial rotation matrix and translation vector and triangulate
+	// points (in camera 1 coordinate system).
+
+	Mat R1, R2, t1, t2;
+	R1 = Mat::eye(Size(3, 3), CV_64FC1);
+	t1 = Mat(Size(1, 3), CV_64FC1, Scalar(0.0));
+
+	vector<Point3d> points3d;
+	// Convert homogeneous coordinates 
+	{
+		Mat points3dh;
+		recoverPose(E, points1, points2, K1, K2, R2, t2, 1000.0, points3dh);
+		points3d.reserve(points3dh.cols);
+
+		for (int col = 0; col < points3dh.cols; col++) {
+			Point3d p = Point3d(points3dh.at<double>(0, col),
+								points3dh.at<double>(1, col),
+								points3dh.at<double>(2, col))
+								/ points3dh.at<double>(3, col);
+			points3d.push_back(p);
+		}
+	}
+	DCHECK(points3d.size() == points1.size());
+
+	// Estimate and apply scale factor
+	{
+		double scale = target_.estimateScale(points3d);
+		for (auto &p : points3d) { p = p * scale; }
+		t1 = t1 * scale;
+		t2 = t2 * scale;
+	}
+
+	// Reprojection error before BA
+	{
+		// SBA should report squared mean error
+		const double err1 = reprojectionError(points3d, points1, K1, R1, t1);
+		const double err2 = reprojectionError(points3d, points2, K2, R2, t2);
+		
+		if (abs(err1 - err2) > 2.0) {
+			LOG(INFO) << "Initial reprojection error (camera " << camera_from << "): " << err1;
+			LOG(INFO) << "Initial reprojection error (camera " << camera_to << "): " << err2;
+		}
+		LOG(INFO)	<< "Initial reprojection error (" << camera_from << ", " << camera_to << "): "
+					<< sqrt(err1 * err1 + err2 * err2);
+		
+	}
+	
+	// Bundle Adjustment
+	// vector<Point3d> points3d_triangulated;
+	// points3d_triangulated.insert(points3d_triangulated.begin(), points3d.begin(), points3d.end());
+	LOG(INFO) << K1;
+	double err;
+	cvsba::Sba sba;
+	{
+		sba.setParams(cvsba::Sba::Params(cvsba::Sba::TYPE::MOTIONSTRUCTURE, 200, 1.0e-30, 5, 5, false));
+		
+		Mat rvec1, rvec2;
+		cv::Rodrigues(R1, rvec1);
+		cv::Rodrigues(R2, rvec2);
+
+		auto points2d = vector<vector<Point2d>> { points1, points2 };
+		auto K = vector<Mat> { K1, K2 };
+		auto r = vector<Mat> { rvec1, rvec2 };
+		auto t = vector<Mat> { t1, t2 };
+		auto dcoeffs = vector<Mat> { dist_coeffs_[camera_from], dist_coeffs_[camera_to] };
+		
+		sba.run(points3d,
+				vector<vector<Point2d>> { points1, points2 },
+				vector<vector<int>>(2, vector<int>(points1.size(), 1)),
+				K, r, t, dcoeffs
+		);
+		
+		cv::Rodrigues(r[0], R1);
+		cv::Rodrigues(r[1], R2);
+		t1 = t[0];
+		t2 = t[1];
+
+		// intrinsic parameters should only be optimized at final BA
+		//K1 = K[0];
+		//K2 = K[1];
+
+		err = sba.getFinalReprjError();
+		LOG(INFO) << "SBA reprojection error before BA " << sba.getInitialReprjError();
+		LOG(INFO) << "SBA reprojection error after BA " << err;
+	}
+
+	calculateTransform(R2, t2, R1, t1, rmat, tvec);
+
+	// Store and average 3D points for both cameras (skip garbage)
+	if (err < 10.0) {
+		Mat rmat1, tvec1;
+		updatePoints3D(camera_from, points3d, idx, R1, t1);
+		updatePoints3D(camera_to, points3d, idx, R2, t2);
+	}
+	else {
+		LOG(ERROR)	<< "Large RMS error ("
+					<< reprojectionError(points3d, points2, K2, rmat, tvec)
+					<< "), not updating points!";
+	}
+
+	//LOG(INFO) << reprojectionError(points3d, points1, K1, R1, t1);
+	//LOG(INFO) << reprojectionError(points3d, points2, K2, R2, t2);
+
+	return err;
+}
+
+Point3d MultiCameraCalibrationNew::getPoint3D(size_t camera, size_t idx) {
+	return points3d_[camera][idx];
+}
+
+void MultiCameraCalibrationNew::calculateMissingPoints3D() {
+	points3d_optimized_.clear();
+	points3d_optimized_.resize(points3d_[reference_camera_].size());
+
+	for (size_t i = 0; i < points3d_optimized_.size(); i++) {
+		if (inlier_[reference_camera_][i] > 0) {
+			points3d_optimized_[i] = points3d_[reference_camera_][i];
+			continue;
+		}
+
+		if (!isValid(i)) continue;
+
+		double f = 0.0;
+		Point3d point(0.0, 0.0, 0.0);
+		for (size_t c = 0; c < n_cameras_; c++) {
+			if (inlier_[c][i] <= 0) { continue; }
+			point += transformPoint(getPoint3D(c, i), R_[c], t_[c]);
+			f += 1.0;
+		}
+
+		DCHECK(f != 0.0);
+
+		points3d_optimized_[i] = point / f;
+	}
+}
+
+double MultiCameraCalibrationNew::getReprojectionError(size_t c_from, size_t c_to, const Mat &K, const Mat &R, const Mat &t) {
+
+	vector<Point2d> points2d;
+	vector<Point3d> points3d;
+
+	for (size_t i = 0; i < points2d_[c_from].size(); i++) {
+		if (!isValid(i) || !isVisible(c_from, i) || !isVisible(c_to, i)) continue;
+		points2d.push_back(points2d_[c_from][i]);
+		points3d.push_back(points3d_[c_to][i]);
+	}
+
+	return reprojectionError(points3d, points2d, K, R, t);
+}
+
+double MultiCameraCalibrationNew::getReprojectionErrorOptimized(size_t c_from, const Mat &K, const Mat &R, const Mat &t) {
+	
+	vector<Point2d> points2d;
+	vector<Point3d> points3d;
+
+	for (size_t i = 0; i < points2d_[c_from].size(); i++) {
+		if (!isValid(i) || !isVisible(c_from, i)) continue;
+		points2d.push_back(points2d_[c_from][i]);
+		points3d.push_back(points3d_optimized_[i]);
+	}
+
+	return reprojectionError(points3d, points2d, K, R, t);
+}
+
+
+double MultiCameraCalibrationNew::calibrateAll(int reference_camera) {
+	if (reference_camera != -1) {
+		DCHECK(reference_camera >= 0 && reference_camera < n_cameras_);
+		reference_camera_ = reference_camera; 
+	}
+
+	reset(); // remove all old calibration results
+	map<pair<size_t, size_t>, pair<Mat, Mat>> transformations; 
+	
+	// All cameras should be calibrated pairwise; otherwise all possible 3D
+	// points are not necessarily triangulated
+
+	auto paths = visibility_graph_.findShortestPaths(reference_camera_);
+	
+	for (size_t c1 = 0; c1 < n_cameras_; c1++) {
+	for (size_t c2 = c1; c2 < n_cameras_; c2++) {
+		if (c1 == c2) {
+			transformations[make_pair(c1, c2)] = 
+				make_pair(Mat::eye(Size(3, 3), CV_64FC1),
+				Mat(Size(1, 3), CV_64FC1, Scalar(0.0))
+			);
+			continue;
+		}
+
+		size_t n_visible = getVisiblePointsCount({c1, c2});
+
+		if (n_visible < min_visible_points_) {
+			LOG(INFO)	<< "Not enough (" << min_visible_points_ << ") points  between "
+						<< "cameras " << c1 << " and " << c2 << " (" << n_visible << " points), "
+						<< "skipping";
+			continue;
+		}
+		LOG(INFO)	<< "Running pairwise calibration for cameras "
+					<< c1 << " and " << c2 << "(" << n_visible << " points)";
+
+		if (transformations.find(make_pair(c2, c1)) != transformations.end()) {
+			continue;
+		}
+		Mat R, t, R_i, t_i;
+
+		// TODO: threshold parameter, 16.0 possibly too high
+
+		if (calibratePair(c1, c2, R, t) > 16.0) {
+			LOG(ERROR)	<< "Pairwise calibration failed, skipping cameras "
+						<< c1 << " and " << c2;
+			visibility_graph_.deleteEdge(c1, c2);
+			continue;
+		}
+
+		calculateInverse(R, t, R_i, t_i);
+
+		transformations[make_pair(c2, c1)] = make_pair(R, t);
+		transformations[make_pair(c1, c2)] = make_pair(R_i, t_i);
+	}}
+
+	for (size_t c = 0; c < paths.size(); c++) {
+		Mat R_chain = Mat::eye(Size(3, 3), CV_64FC1);
+		Mat t_chain = Mat(Size(1, 3), CV_64FC1, Scalar(0.0));
+		LOG(INFO) << "Chain for camera " << c;
+		for (auto e: paths[c]) {
+			CHECK(transformations.find(e) != transformations.end()) << "chain not calculated; pairwise calibration possibly failed earlier?";
+			LOG(INFO) << e.first << " -> " << e.second;
+			Mat R = transformations[e].first;
+			Mat t = transformations[e].second;
+			R_chain = R * R_chain;
+			t_chain = t + R * t_chain;
+		}
+
+		R_[c] = R_chain;
+		t_[c] = t_chain;
+		/*R_[c] = transformations[make_pair(reference_camera_, c)].first;
+		t_[c] = transformations[make_pair(reference_camera_, c)].second;
+		DCHECK(R_[c].size() == Size(3, 3));
+		DCHECK(t_[c].size() == Size(1, 3));*/
+	}
+	
+	calculateMissingPoints3D();
+	
+	for (size_t c_from = 0; c_from < n_cameras_; c_from++) {
+		if (c_from == reference_camera_) continue;
+		Mat R, t;
+		calculateInverse(R_[c_from], t_[c_from], R, t);
+		LOG(INFO)	<< "Error before BA, cameras " << reference_camera_ << " and " << c_from << ": "
+					<< getReprojectionErrorOptimized(c_from, K_[c_from], R, t);
+	
+	}
+
+	double err;
+	cvsba::Sba sba;
+	{
+		sba.setParams(cvsba::Sba::Params(cvsba::Sba::TYPE::MOTIONSTRUCTURE, 200, 1.0e-24, fix_intrinsics_, 5, false));
+
+		vector<Mat> rvecs(R_.size());
+		vector<vector<int>> visible(R_.size());
+		vector<Point3d> points3d;
+		vector<vector<Point2d>> points2d(R_.size());
+		vector<size_t> idx;
+		idx.reserve(points3d_optimized_.size());
+
+		for (size_t i = 0; i < points3d_optimized_.size(); i++) {
+			
+			auto p = points3d_optimized_[i];
+			DCHECK(!isnanl(p.x) && !isnanl(p.y) && !isnanl(p.z));
+
+			int count = 0;
+			for (size_t c = 0; c < n_cameras_; c++) {
+				if (isVisible(c, i) && isValid(c, i)) { count++; }
+			}
+			
+			if (count < 2) continue;
+
+			points3d.push_back(p);
+			idx.push_back(i);
+
+			for (size_t c = 0; c < n_cameras_; c++) {
+				bool good = isVisible(c, i) && isValid(c, i);
+				visible[c].push_back(good ? 1 : 0);
+				points2d[c].push_back(points2d_[c][i]);
+			}
+		}
+
+		for (size_t i = 0; i < rvecs.size(); i++) {
+			calculateInverse(R_[i], t_[i], R_[i], t_[i]);
+			cv::Rodrigues(R_[i], rvecs[i]);
+		}
+
+		DCHECK(points2d.size() == n_cameras_);
+		DCHECK(visible.size() == n_cameras_);
+		for (size_t c = 0; c < n_cameras_; c++) {
+			DCHECK(points3d.size() == points2d[c].size());
+			DCHECK(points3d.size() == visible[c].size());
+		}
+
+		LOG(INFO) << "number of points used: " << points3d.size();
+		sba.run(points3d, points2d, visible,
+				K_,	rvecs, t_, dist_coeffs_
+		);
+		
+		for (size_t i = 0; i < rvecs.size(); i++) {
+			cv::Rodrigues(rvecs[i], R_[i]);
+			calculateInverse(R_[i], t_[i], R_[i], t_[i]);
+		}
+
+		// save optimized points
+		{
+			size_t l = points3d.size();
+			points3d_optimized_.clear();
+			points3d_optimized_.resize(l, Point3d(NAN, NAN, NAN));
+
+			for (size_t i = 0; i < points3d.size(); i++) {
+				points3d_optimized_[idx[i]] = points3d[i];
+			}
+		}
+
+		err = sba.getFinalReprjError();
+		LOG(INFO) << "SBA reprojection error before final BA " << sba.getInitialReprjError();
+		LOG(INFO) << "SBA reprojection error after final BA " << err;
+	}
+
+	for (size_t c_from = 0; c_from < n_cameras_; c_from++) {
+		if (c_from == reference_camera_) continue;
+		Mat R, t;
+		calculateInverse(R_[c_from], t_[c_from], R, t);
+		LOG(INFO)	<< "Error (RMS) after BA, cameras " << reference_camera_ << " and " << c_from << ": "
+					<< getReprojectionErrorOptimized(c_from, K_[c_from], R, t);
+	
+	}
+
+	is_calibrated_ = true;
+	return err;
+}
+
+void MultiCameraCalibrationNew::projectPointsOriginal(size_t camera_src, size_t camera_dst, size_t idx, vector<Point2d> &points) {
+	
+}
+
+void MultiCameraCalibrationNew::projectPointsOptimized(size_t camera_dst, size_t idx, vector<Point2d> &points) {
+	// TODO:	indexing does not match input (points may be skipped in loadInput())
+
+	points.clear();
+	size_t i = target_.n_points * idx;
+	
+	if (!isValid(i)) return;
+
+	Point3d p1(points3d_optimized_[i]);
+	Point3d p2(points3d_optimized_[i + 1]);
+
+	if (!std::isfinite(p1.x) || !std::isfinite(p2.x)) {
+		// DEBUG: should not happen
+		LOG(ERROR) << "Bad point! (no valid triangulation)";
+		return; 
+	}
+	
+	Mat R, tvec, rvec;
+	calculateTransform(R_[reference_camera_], t_[reference_camera_], R_[camera_dst], t_[camera_dst], R, tvec);
+	
+	cv::Rodrigues(R, rvec);
+	cv::projectPoints(	vector<Point3d> { p1, p2 },
+						rvec, tvec, K_[camera_dst], dist_coeffs_[camera_dst], points);
+}
+
+void MultiCameraCalibrationNew::getCalibration(vector<Mat> &R, vector<Mat> &t) {
+	DCHECK(is_calibrated_);
+	R.resize(n_cameras_);
+	t.resize(n_cameras_);
+
+	for (size_t i = 0; i < n_cameras_; i++) {
+		R_[i].copyTo(R[i]);
+		t_[i].copyTo(t[i]);
+	}
+}
\ No newline at end of file
diff --git a/applications/calibration-multi/src/multicalibrate.hpp b/applications/calibration-multi/src/multicalibrate.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..0168e107d69730e72bcd60790e550c19ca16439a
--- /dev/null
+++ b/applications/calibration-multi/src/multicalibrate.hpp
@@ -0,0 +1,155 @@
+#pragma once
+
+#include <opencv2/core.hpp>
+
+#include "visibility.hpp"
+#include "util.hpp"
+
+using cv::Mat;
+using cv::Size;
+using cv::Point2d;
+using cv::Point3d;
+using cv::Vec4d;
+using cv::Scalar;
+
+using std::vector;
+using std::pair;
+
+class CalibrationTarget {
+public:
+	CalibrationTarget(double length) :
+		n_points(2),
+		calibration_bar_length_(length)
+	{}
+	/* @brief	Estimate scale factor.
+	 * @param	3D points (can pass n views)
+	 */
+	double estimateScale(vector<Point3d> points3d);
+	size_t n_points;
+
+private:
+	double calibration_bar_length_;
+};
+
+class MultiCameraCalibrationNew {
+public:
+	MultiCameraCalibrationNew(	size_t n_cameras, size_t reference_camera,
+								Size resolution, CalibrationTarget target,
+								int fix_intrinsics=1);
+	
+	void setCameraParameters(size_t idx, const Mat &K, const Mat &distCoeffs);
+	void setCameraParameters(size_t idx, const Mat &K);
+
+	void addPoints(vector<vector<Point2d>> points2d, vector<int> visibility);
+
+	size_t getViewsCount();
+	size_t getCamerasCount() { return n_cameras_; }
+	size_t getOptimalReferenceCamera();
+
+	size_t getMinVisibility() { return visibility_graph_.getMinVisibility(); }
+	size_t getViewsCount(size_t camera) { return visibility_graph_.getViewsCount(camera); }
+
+	void setFixIntrinsic(int value) { fix_intrinsics_ = (value == 1 ? 5 : 0); }
+
+	void loadInput(const std::string &filename, const vector<size_t> &cameras = {});
+
+	void saveInput(cv::FileStorage &fs);
+	void saveInput(const std::string &filename);
+
+	Mat getCameraMat(size_t idx);
+	Mat getCameraMatNormalized(size_t idx, double scale_x = 1.0, double scale_y = 1.0);
+
+	Mat getDistCoeffs(size_t idx);
+
+	double calibrateAll(int reference_camera = -1);
+	double getReprojectionError();
+	void getCalibration(vector<Mat> &R, vector<Mat> &t);
+
+	void projectPointsOriginal(size_t camera_src, size_t camera_dst, size_t idx, vector<Point2d> &points);
+	void projectPointsOptimized(size_t camera_dst, size_t idx, vector<Point2d> &points);
+
+protected:
+	bool isVisible(size_t camera, size_t idx);
+	bool isValid(size_t camera, size_t idx);
+	bool isValid(size_t idx);
+
+	Point3d getPoint3D(size_t camera, size_t i);
+
+	vector<Point2d> getPoints(size_t camera, size_t idx);
+	vector<vector<Point2d>> getAllPoints(size_t camera, vector<size_t> idx);
+
+	void getVisiblePoints(	vector<size_t> cameras,
+							vector<vector<Point2d>> &points,
+							vector<size_t> &idx);
+
+	size_t getVisiblePointsCount(vector<size_t> cameras) {
+		// TODO: for pairs can use visibility graph adjacency matrix
+		vector<vector<Point2d>> points2d;
+		vector<size_t> idx;
+		getVisiblePoints(cameras, points2d, idx);
+		return idx.size();
+	}
+
+	size_t getTotalPointsCount() {
+		return points2d_[0].size();
+	}
+
+	vector<Point3d> getPoints3D(size_t idx);
+
+	/* @brief	Find points which are visible on all cameras. Returns
+	 * 			corresponding indices in idx vector.
+	 */
+	void getVisiblePoints3D(vector<size_t> cameras,
+							vector<vector<Point3d>> &points,
+							vector<size_t> &idx);
+
+	/* @brief	Update 3D points with new values. If no earlier data, new data
+	 *			is used as is, otherwise calculates average.
+	 */
+	void updatePoints3D(size_t camera, Point3d new_point, size_t idx, const Mat &R, const Mat &t);
+	void updatePoints3D(size_t camera, vector<Point3d> new_points, vector<size_t> idx, const Mat &R, const Mat &t);
+
+	/* @brief	Calculates 3D points that are not visible in reference camera
+	 *			from transformations in visible cameras.
+	 */
+	void calculateMissingPoints3D();
+
+	void getTransformation(size_t camera_from, size_t camera_to, Mat &R, Mat &T);
+	double calibratePair(size_t camera_from, size_t camera_to, Mat &R, Mat &T);
+
+	/* @brief	Calculate reprojection error of visible points (triangulation) */
+	double getReprojectionError(size_t c_from, size_t c_to, const Mat &K, const Mat &R, const Mat &T);
+
+	/* @brief	Calculate reprojection error of visible points (optimized/averaged points) */
+	double getReprojectionErrorOptimized(size_t c_from, const Mat &K, const Mat &R, const Mat &T);
+
+	/* @brief	Remove old calibration data calculated by calibrateAll */
+	void reset();
+
+private:
+	CalibrationTarget target_;
+	Visibility visibility_graph_; 
+
+	bool is_calibrated_;
+	size_t n_cameras_;
+	size_t reference_camera_;
+	size_t min_visible_points_;
+	int fix_intrinsics_;
+
+	Size resolution_;
+	vector<Mat> K_;
+	vector<Mat> dist_coeffs_;
+	vector<Mat> R_;
+	vector<Mat> t_;
+
+	vector<Point3d> points3d_optimized_;
+	vector<vector<Point3d>> points3d_;
+	vector<vector<Point2d>> points2d_;
+	vector<vector<int>> visible_;
+	vector<vector<int>> inlier_; // "inlier"
+	vector<vector<double>> weights_;
+
+	int fm_method_;
+	double fm_ransac_threshold_;
+	double fm_confidence_;
+};
diff --git a/applications/calibration-multi/src/util.cpp b/applications/calibration-multi/src/util.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..0019516e4027472d2be733899bde75a50a94dd0f
--- /dev/null
+++ b/applications/calibration-multi/src/util.cpp
@@ -0,0 +1,174 @@
+#include "util.hpp"
+
+#include <loguru.hpp>
+
+#include <opencv2/core.hpp>
+#include <opencv2/calib3d.hpp>
+#include <opencv2/imgproc.hpp>
+#include <opencv2/aruco.hpp>
+
+using std::vector;
+
+using cv::Mat;
+using cv::Point2i;
+using cv::Point2d;
+using cv::Point3d;
+using cv::Size;
+using cv::Scalar;
+
+/* @brief	Visualize epipolar lines for given points in the other image.
+ * @param	Points in image
+ * @param	Corresponding image where to draw the lines
+ * @param	Fundamental matrix
+ * @param	Line color
+ * @param	Which image (1 or 2), see OpenCV's computeCorrespondEpilines()
+ */
+void drawEpipolarLines(vector<Point2d> const &points, Mat &img, Mat const &F, Scalar color, int image) {
+	Mat lines;
+	cv::computeCorrespondEpilines(points, image, F, lines);
+
+	for (int i = 0; i < lines.rows; i++) {
+		cv::Vec3f l = lines.at<cv::Vec3f>(i);
+		float a = l[0];
+		float b = l[1];
+		float c = l[2];
+		float x0, y0, x1, y1;
+		x0 = 0;
+		y0 = (-c -a * x0) / b;
+		x1 = img.cols;
+		y1 = (-c -a * x1) / b;
+		cv::line(img, cv::Point(x0, y0), cv::Point(x1,y1), color, 1);
+	}
+}
+
+/* @breif	Find calibration points. AruCo markers, two per image.
+ *			visible parameter input/ouput
+ */
+int findCorrespondingPoints(vector<Mat> imgs, vector<vector<Point2d>> &points,
+							vector<int> &visible) {
+	using namespace cv;
+	int count = 0;
+
+	visible.resize(imgs.size(), 1);
+
+	points.clear();
+	points.resize(imgs.size(), vector<Point2d>(2, Point2d(0.0, 0.0)));
+
+	auto dictionary = aruco::getPredefinedDictionary(aruco::DICT_5X5_50);
+	vector<vector<Point2f>> corners;
+	vector<int> ids;
+	
+	for (size_t i = 0; i < imgs.size(); i++) {
+		if (visible[i] == 0) continue;
+
+		aruco::detectMarkers(imgs[i], dictionary, corners, ids);
+		if (corners.size() == 2) {
+			Point2d center0((corners[0][0] + corners[0][1] + corners[0][2] + corners[0][3]) / 4.0);
+			Point2d center1((corners[1][0] + corners[1][1] + corners[1][2] + corners[1][3]) / 4.0);
+			if (ids[0] != 0) { std::swap(center0, center1); }
+
+			points[i][0] = center0; points[i][1] = center1;
+			visible[i] = 1;
+
+			count++;
+		}
+		else {
+			visible[i] = 0;
+		}
+	}
+
+	return count;
+}
+
+/* @brief	Find AruCo marker centers.
+ * @param	(input) image
+ * @param	(output) found centers
+ * @param	(output) marker IDs
+ */
+void findMarkerCenters(Mat &img, vector<Point2d> &points, vector<int> &ids, int dict) {
+	using namespace cv;
+
+	points.clear();
+
+	auto dictionary = aruco::getPredefinedDictionary(dict);
+	vector<vector<Point2f>> corners;
+
+	aruco::detectMarkers(img, dictionary, corners, ids);
+	for (size_t j = 0; j < corners.size(); j++) {
+		Point2f center((corners[j][0] + corners[j][1] + corners[j][2] + corners[j][3]) / 4.0);
+		points.push_back(center);
+	}
+}
+
+/* OpenCV's recoverPose() expects both cameras to have identical intrinsic
+ * parameters.
+ */
+int recoverPose(Mat &E, vector<Point2d> &_points1, vector<Point2d> &_points2,
+				Mat &_cameraMatrix1, Mat &_cameraMatrix2,
+				Mat &_R, Mat &_t, double distanceThresh,
+				Mat &triangulatedPoints) {
+
+	Mat points1, points2, cameraMatrix1, cameraMatrix2, cameraMatrix;
+	
+	Mat(_points1.size(), 2, CV_64FC1, _points1.data()).convertTo(points1, CV_64F);
+	Mat(_points2.size(), 2, CV_64FC1, _points2.data()).convertTo(points2, CV_64F);
+	_cameraMatrix1.convertTo(cameraMatrix1, CV_64F);
+	_cameraMatrix2.convertTo(cameraMatrix2, CV_64F);
+	cameraMatrix = Mat::eye(Size(3, 3), CV_64FC1);
+
+	double fx1 = cameraMatrix1.at<double>(0,0);
+	double fy1 = cameraMatrix1.at<double>(1,1);
+	double cx1 = cameraMatrix1.at<double>(0,2);
+	double cy1 = cameraMatrix1.at<double>(1,2);
+
+	double fx2 = cameraMatrix2.at<double>(0,0);
+	double fy2 = cameraMatrix2.at<double>(1,1);
+	double cx2 = cameraMatrix2.at<double>(0,2);
+	double cy2 = cameraMatrix2.at<double>(1,2);
+
+	points1.col(0) = (points1.col(0) - cx1) / fx1;
+	points1.col(1) = (points1.col(1) - cy1) / fy1;
+
+	points2.col(0) = (points2.col(0) - cx2) / fx2;
+	points2.col(1) = (points2.col(1) - cy2) / fy2;
+
+	// TODO mask
+	// cameraMatrix = I (for details, see OpenCV's recoverPose() source code)
+	// modules/calib3d/src/five-point.cpp (461)
+	//
+	// https://github.com/opencv/opencv/blob/371bba8f54560b374fbcd47e7e02f015ac4969ad/modules/calib3d/src/five-point.cpp#L461
+
+	return cv::recoverPose(E, points1, points2, cameraMatrix, _R, _t, distanceThresh, cv::noArray(), triangulatedPoints);
+}
+
+/* @brief	Calculate RMS reprojection error
+ * @param	3D points
+ * @param	Expected 2D points
+ * @param	Camera matrix
+ * @param	Rotation matrix/vector
+ * @param	Translation vector
+ */
+double reprojectionError(	const vector<Point3d> &points3d, const vector<Point2d> &points2d,
+							const Mat &K, const Mat &rvec, const Mat &tvec) {
+	
+	DCHECK(points3d.size() == points2d.size());
+	
+	Mat _rvec;
+	if (rvec.size() == Size(3, 3)) { cv::Rodrigues(rvec, _rvec); }
+	else { _rvec = rvec; }
+
+	DCHECK(_rvec.size() == Size(1, 3) || _rvec.size() == Size(3, 1));
+
+	vector<Point2d> points_reprojected;
+	cv::projectPoints(points3d, _rvec, tvec, K, cv::noArray(), points_reprojected);
+	
+	int n_points = points2d.size();
+	double err = 0.0;
+
+	for (int i = 0; i < n_points; i++) {
+		Point2d a = points2d[i] - points_reprojected[i];
+		err += a.x * a.x + a.y * a.y;
+	}
+
+	return sqrt(err / n_points);
+}
\ No newline at end of file
diff --git a/applications/calibration-multi/src/util.hpp b/applications/calibration-multi/src/util.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..7c2b5702feb1b518fd4662560f349c2aa4de9fed
--- /dev/null
+++ b/applications/calibration-multi/src/util.hpp
@@ -0,0 +1,110 @@
+#pragma once
+
+#include <loguru.hpp>
+
+#include <opencv2/core.hpp>
+#include <opencv2/aruco.hpp>
+
+using std::vector;
+
+using cv::Mat;
+using cv::Point2i;
+using cv::Point2d;
+using cv::Point3d;
+using cv::Size;
+using cv::Scalar;
+
+/* @brief	Visualize epipolar lines for given points in the other image.
+ * @param	Points in image
+ * @param	Corresponding image where to draw the lines
+ * @param	Fundamental matrix
+ * @param	Line color
+ * @param	Which image (1 or 2), see OpenCV's computeCorrespondEpilines()
+ */
+void drawEpipolarLines(vector<Point2d> const &points, Mat &img, Mat const &F, Scalar color, int image=1);
+
+
+/* @breif	Find calibration points. AruCo markers, two per image.
+ */
+int findCorrespondingPoints(vector<Mat> imgs, vector<vector<Point2d>> &points,
+							vector<int> &visible);
+
+/* @brief	Find AruCo marker centers.
+ * @param	(input) image
+ * @param	(output) found centers
+ * @param	(output) marker IDs
+ */
+void findMarkerCenters(Mat &img, vector<Point2d> &points, vector<int> &ids, int dict=cv::aruco::DICT_4X4_50);
+
+/* OpenCV's recoverPose() expects both cameras to have identical intrinsic
+ * parameters.
+ * 
+ * https://github.com/opencv/opencv/blob/371bba8f54560b374fbcd47e7e02f015ac4969ad/modules/calib3d/src/five-point.cpp#L461
+ */
+int recoverPose(Mat &E, vector<Point2d> &_points1, vector<Point2d> &_points2,
+				Mat &_cameraMatrix1, Mat &_cameraMatrix2,
+				Mat &_R, Mat &_t, double distanceThresh,
+				Mat &triangulatedPoints);
+
+/* @brief	Calculate RMS reprojection error
+ * @param	3D points
+ * @param	Expected 2D points
+ * @param	Camera matrix
+ * @param	Rotation matrix/vector
+ * @param	Translation vector
+ */
+double reprojectionError(	const vector<Point3d> &points3d, const vector<Point2d> &points2d,
+							const Mat &K, const Mat &rvec, const Mat &tvec);
+
+inline double euclideanDistance(Point3d a, Point3d b) {
+	Point3d c = a - b;
+	return sqrt(c.x*c.x + c.y*c.y + c.z*c.z);
+}
+
+inline Point3d transformPoint(Point3d p, Mat R, Mat t) {
+	DCHECK(R.size() == Size(3, 3));
+	DCHECK(t.size() == Size(1, 3));
+	return Point3d(Mat(R * Mat(p) + t));
+}
+
+inline Point3d inverseTransformPoint(Point3d p, Mat R, Mat t) {
+	DCHECK(R.size() == Size(3, 3));
+	DCHECK(t.size() == Size(1, 3));
+	return Point3d(Mat(R.t() * (Mat(p) - t)));
+}
+
+inline Mat getMat4x4(const Mat &R, const Mat &t) {
+	DCHECK(R.size() == Size(3, 3));
+	DCHECK(t.size() == Size(1, 3));
+	Mat M = Mat::eye(Size(4, 4), CV_64FC1);
+	R.copyTo(M(cv::Rect(0, 0, 3, 3)));
+	t.copyTo(M(cv::Rect(3, 0, 1, 3)));
+	return M;
+}
+
+inline void getRT(const Mat RT, Mat &R, Mat &t) {
+	R = RT(cv::Rect(0, 0, 3, 3));
+	t = RT(cv::Rect(3, 0, 1, 3));
+}
+
+// calculate transforms from (R1, t1) to (R2, t2), where parameters
+// (R1, t1) and (R2, t2) map to same (target) coordinate system
+
+inline void calculateTransform(const Mat &R1, const Mat &T1, const Mat &R2, const Mat &T2, Mat &R, Mat &tvec, Mat &M) {
+	Mat M_src = getMat4x4(R1, T1);
+	Mat M_dst = getMat4x4(R2, T2);
+	M = M_dst.inv() * M_src;	
+	R = M(cv::Rect(0, 0, 3, 3));
+	tvec = M(cv::Rect(3, 0, 1, 3));
+}
+
+inline void calculateTransform(const Mat &R1, const Mat &T1, const Mat &R2, const Mat &T2,Mat &R, Mat &tvec) {
+	Mat M;
+	calculateTransform(R1, T1, R2, T2, R, tvec, M);
+}
+
+inline void calculateInverse(const Mat &R2, const Mat &T2, Mat &R, Mat &T) {
+	Mat R1 = Mat::eye(Size(3, 3), CV_64FC1);
+	Mat T1(Size(1, 3), CV_64FC1, Scalar(0.0));
+	calculateTransform(R1, T1, R2, T2, R, T);
+}
\ No newline at end of file
diff --git a/applications/calibration-multi/src/visibility.cpp b/applications/calibration-multi/src/visibility.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a4c16165b47decadfa9ca18ccf641a32b1cb16c1
--- /dev/null
+++ b/applications/calibration-multi/src/visibility.cpp
@@ -0,0 +1,153 @@
+#include <numeric>
+#include <loguru.hpp>
+#include <queue>
+
+#include "visibility.hpp"
+
+using cv::Mat;
+using cv::Scalar;
+using cv::Size;
+using std::vector;
+using std::pair;
+using std::make_pair;
+
+Visibility::Visibility(int n_cameras) : n_cameras_(n_cameras) {
+	visibility_ = Mat(Size(n_cameras, n_cameras), CV_32SC1, Scalar(0));
+	count_ = vector(n_cameras, 0);
+}
+
+void Visibility::update(vector<int> &visible) {
+	DCHECK(visible.size() == (size_t) n_cameras_);
+
+	for (int i = 0; i < n_cameras_; i++) {
+		if (visible[i] == 0) continue;
+		count_[i]++;
+
+		for (int j = 0; j < n_cameras_; j++) {
+			if (i == j) continue;
+			if (visible[j] == 1) visibility_.at<int>(i, j)++;
+		}
+	}
+}
+
+int Visibility::getOptimalCamera() {
+	// most visible on average
+	int best_i;
+	double best_score = -INFINITY;
+	for (int i = 0; i < visibility_.rows; i++) {
+		double score = 0.0;
+		for (int x = 0; x < visibility_.cols; x++) {
+			score += visibility_.at<int>(i, x);
+		}
+		score = score / (double) visibility_.cols;
+		if (score > best_score) {
+			best_i = i;
+			best_score = score;
+		}
+	}
+	
+	return best_i;
+}
+
+void Visibility::deleteEdge(int camera1, int camera2)
+{
+	visibility_.at<int>(camera1, camera2) = 0;
+	visibility_.at<int>(camera2, camera1) = 0;
+}
+
+int Visibility::getMinVisibility() {
+	int min_i;
+	int min_count = INT_MAX;
+
+	for (int i = 0; i < n_cameras_; i++) {
+		if (count_[i] < min_count) {
+			min_i = i;
+			min_count = count_[i];
+		}
+	}
+	
+	return min_count;
+}
+
+int Visibility::getViewsCount(int camera) {
+	return count_[camera];
+}
+
+vector<vector<pair<int, int>>> Visibility::findShortestPaths(int reference) {
+	DCHECK(reference < n_cameras_);
+
+	vector<vector<pair<int, int>>> res(n_cameras_);
+	for (int i = 0; i < n_cameras_; i++) {
+		res[i] = findShortestPath(i, reference);
+	}
+	
+	return res;
+}
+
+vector<pair<int, int>> Visibility::findShortestPath(int from, int to) {
+	if (from == to) return vector<pair<int, int>>();
+
+	vector<bool> visited(n_cameras_, false);
+	vector<double> distances(n_cameras_, INFINITY);
+	vector<int> previous(n_cameras_, -1);
+	
+	distances[from] = 0.0;
+
+	auto cmp = [](pair<int, double> u, pair<int, double> v) { return u.second > v.second; };
+	std::priority_queue<pair<int, double>, vector<pair<int, double>>, decltype(cmp)> pq(cmp);
+
+	pq.push(make_pair(from, distances[from]));
+
+	while(!pq.empty()) {
+		pair<int, double> current = pq.top();
+		pq.pop();
+
+		int current_id = current.first;
+		double current_distance = distances[current_id];
+
+		visited[current_id] = true;
+
+		for (int i = 0; i < n_cameras_; i++) {
+			int count = visibility_.at<int>(current_id, i);
+			if (count == 0) continue; // not connected
+
+			double distance = 1.0 / (double) count;
+			double new_distance = current_distance + distance;
+			
+			if (distances[i] > new_distance) {
+				distances[i] = new_distance;
+				previous[i] = current_id;
+
+				pq.push(make_pair(i, distances[i]));
+			}
+		}
+	}
+
+	vector<pair<int, int>> res;
+	int prev = previous[to];
+	int current = to;
+
+	do {
+		res.push_back(make_pair(current, prev));
+		current = prev;
+		prev = previous[prev];
+	}
+	while(prev != -1);
+
+	std::reverse(res.begin(), res.end());
+	return res;
+}
+
+vector<int> Visibility::getClosestCameras(int c) {
+
+	// initialize original index locations
+	vector<int> idx(n_cameras_);
+	iota(idx.begin(), idx.end(), 0);
+	int* views = visibility_.ptr<int>(c);
+	
+	// sort indexes based on comparing values in v
+	sort(idx.begin(), idx.end(),
+		[views](size_t i1, size_t i2) {return views[i1] < views[i2];});
+
+	return idx;
+}
\ No newline at end of file
diff --git a/applications/calibration-multi/src/visibility.hpp b/applications/calibration-multi/src/visibility.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..3521435dd62e2cd6eeea417f926b02f8049eb560
--- /dev/null
+++ b/applications/calibration-multi/src/visibility.hpp
@@ -0,0 +1,46 @@
+#pragma once
+
+#include <opencv2/core.hpp>
+
+using cv::Mat;
+using std::vector;
+using std::pair;
+
+class Visibility {
+public:
+	Visibility(int n_cameras);
+
+	/* @breif	Update visibility graph.
+	 * @param	Which cameras see the feature(s) in this iteration
+	 */
+	void update(vector<int> &visible);
+
+	/* @brief	For all cameras, find shortest (optimal) paths to reference
+	 * 			camera
+	 * @param	Id of reference camera
+	 * 
+	 * Calculates shortest path in weighted graph using Dijstra's
+	 * algorithm. Weights are inverse of views between cameras (nodes)
+	 * 
+	 * @todo	Add constant weight for each edge (prefer less edges)
+	 */
+	vector<vector<pair<int, int>>> findShortestPaths(int reference);
+
+	vector<int> getClosestCameras(int c);
+	void deleteEdge(int camera1, int camera2);
+	int getOptimalCamera();
+	int getMinVisibility();
+	int getViewsCount(int camera);
+
+protected:
+	/* @brief	Find shortest path between nodes
+	 * @param	Source node id
+	 * @param	Destination node id
+	 */
+	vector<pair<int, int>> findShortestPath(int from, int to);
+
+private:
+	int n_cameras_;		// @brief number of cameras
+	Mat visibility_;	// @brief adjacency matrix
+	vector<int> count_;
+};
diff --git a/applications/calibration/src/common.cpp b/applications/calibration/src/common.cpp
index 49967754dc0dd4f6665db7dae9bd9c5e965e35d3..8a678e4b9ba808dccea146c2ce4c8c1c6942a7f0 100644
--- a/applications/calibration/src/common.cpp
+++ b/applications/calibration/src/common.cpp
@@ -62,20 +62,24 @@ bool saveExtrinsics(const string &ofile, Mat &R, Mat &T, Mat &R1, Mat &R2, Mat &
 	return false;
 }
 
-bool saveIntrinsics(const string &ofile, const vector<Mat> &M, const vector<Mat>& D) {
+bool saveIntrinsics(const string &ofile, const vector<Mat> &M, const vector<Mat>& D, const Size &size)
+{
 	cv::FileStorage fs(ofile, cv::FileStorage::WRITE);
-	if (fs.isOpened()) {
+	if (fs.isOpened())
+	{
+		fs << "resolution" << size;
 		fs << "K" << M << "D" << D;
 		fs.release();
 		return true;
 	}
-	else {
+	else
+	{
 		LOG(ERROR) << "Error: can not save the intrinsic parameters to '" << ofile << "'";
 	}
 	return false;
 }
 
-bool loadIntrinsics(const string &ifile, vector<Mat> &K1, vector<Mat> &D1) {
+bool loadIntrinsics(const string &ifile, vector<Mat> &K1, vector<Mat> &D1, Size &size) {
 	using namespace cv;
 
 	FileStorage fs;
@@ -89,9 +93,10 @@ bool loadIntrinsics(const string &ifile, vector<Mat> &K1, vector<Mat> &D1) {
 	
 	LOG(INFO) << "Intrinsics from: " << ifile;
 
-	fs["M"] >> K1;
+	fs["resolution"] >> size;
+	fs["K"] >> K1;
 	fs["D"] >> D1;
-
+	
 	return true;
 }
 
@@ -211,6 +216,23 @@ bool CalibrationChessboard::findPoints(Mat &img, vector<Vec2f> &points) {
 	return cv::findChessboardCornersSB(img, pattern_size_, points, chessboard_flags_);
 }
 
+
+void CalibrationChessboard::drawCorners(Mat &img, const vector<Vec2f> &points) {
+	using cv::Point2i;
+	vector<Point2i> corners(4);
+	corners[1] = Point2i(points[0]);
+	corners[0] = Point2i(points[pattern_size_.width - 1]);
+	corners[2] = Point2i(points[pattern_size_.width * (pattern_size_.height - 1)]);
+	corners[3] = Point2i(points.back());
+	
+	cv::Scalar color = cv::Scalar(200, 200, 200);
+	
+	for (int i = 0; i <= 4; i++)
+	{
+		cv::line(img, corners[i % 4], corners[(i + 1) % 4], color, 2);
+	}
+}
+
 void CalibrationChessboard::drawPoints(Mat &img, const vector<Vec2f> &points) {
 	cv::drawChessboardCorners(img, pattern_size_, points, true);
 }
diff --git a/applications/calibration/src/common.hpp b/applications/calibration/src/common.hpp
index 274698b32b51ae9b741b94e25b492ab059637e37..c84f25d249b49eee094e3e898090ffb9ff129f03 100644
--- a/applications/calibration/src/common.hpp
+++ b/applications/calibration/src/common.hpp
@@ -15,8 +15,8 @@ int getOptionInt(const std::map<std::string, std::string> &options, const std::s
 double getOptionDouble(const std::map<std::string, std::string> &options, const std::string &opt, double default_value);
 std::string getOptionString(const std::map<std::string, std::string> &options, const std::string &opt, std::string default_value);
 
-bool loadIntrinsics(const std::string &ifile, std::vector<cv::Mat> &K, std::vector<cv::Mat> &D);
-bool saveIntrinsics(const std::string &ofile, const std::vector<cv::Mat> &K, const std::vector<cv::Mat> &D);
+bool loadIntrinsics(const std::string &ifile, std::vector<cv::Mat> &K, std::vector<cv::Mat> &D, cv::Size &size);
+bool saveIntrinsics(const std::string &ofile, const std::vector<cv::Mat> &K, const std::vector<cv::Mat> &D, const cv::Size &size);
 
 // TODO loadExtrinsics()
 bool saveExtrinsics(const std::string &ofile, cv::Mat &R, cv::Mat &T, cv::Mat &R1, cv::Mat &R2, cv::Mat &P1, cv::Mat &P2, cv::Mat &Q);
@@ -90,10 +90,11 @@ public:
  */
 class CalibrationChessboard : Calibration {
 public:
-	CalibrationChessboard(const std::map<std::string, std::string> &opt);
+	explicit CalibrationChessboard(const std::map<std::string, std::string> &opt);
 	void objectPoints(std::vector<cv::Vec3f> &out);
 	bool findPoints(cv::Mat &in, std::vector<cv::Vec2f> &out);
 	void drawPoints(cv::Mat &img, const std::vector<cv::Vec2f> &points);
+	void drawCorners(cv::Mat &img, const std::vector<cv::Vec2f> &points);
 
 private:
 	int chessboard_flags_ = 0;
diff --git a/applications/calibration/src/lens.cpp b/applications/calibration/src/lens.cpp
index 924787a740ce46e0d4caf31c6630f38d15a02244..b69b26cc6e4cfec9dab04d337d75b21721d2fbae 100644
--- a/applications/calibration/src/lens.cpp
+++ b/applications/calibration/src/lens.cpp
@@ -14,6 +14,8 @@
 #include <opencv2/highgui.hpp>
 
 #include <vector>
+#include <atomic>
+#include <thread>
 
 using std::map;
 using std::string;
@@ -30,34 +32,73 @@ void ftl::calibration::intrinsic(map<string, string> &opt) {
 	LOG(INFO) << "Begin intrinsic calibration";
 
 	// TODO PARAMETERS TO CONFIG FILE
-	const Size image_size = Size(	getOptionInt(opt, "width", 1280),
-							getOptionInt(opt, "height", 720));
+	const Size image_size = Size(	getOptionInt(opt, "width", 1920),
+							getOptionInt(opt, "height", 1080));
 	const int n_cameras = getOptionInt(opt, "n_cameras", 2);
-	const int iter = getOptionInt(opt, "iter", 60);
-	const int delay = getOptionInt(opt, "delay", 750);
+	const int iter = getOptionInt(opt, "iter", 40);
+	const int delay = getOptionInt(opt, "delay", 1000);
 	const double aperture_width = getOptionDouble(opt, "aperture_width", 6.2);
 	const double aperture_height = getOptionDouble(opt, "aperture_height", 4.6);
 	const string filename_intrinsics = getOptionString(opt, "profile", FTL_LOCAL_CONFIG_ROOT "/intrinsics.yml");
 	CalibrationChessboard calib(opt);
+	bool use_guess = getOptionInt(opt, "use_guess", 1);
+	//bool use_guess_distortion = getOptionInt(opt, "use_guess_distortion", 0);
 
 	LOG(INFO) << "Intrinsic calibration parameters";
-	LOG(INFO) << "         profile: " << filename_intrinsics;
-	LOG(INFO) << "       n_cameras: " << n_cameras;
-	LOG(INFO) << "           width: " << image_size.width;
-	LOG(INFO) << "          height: " << image_size.height;
-	LOG(INFO) << "            iter: " << iter;
-	LOG(INFO) << "           delay: " << delay;
-	LOG(INFO) << "  aperture_width: " << aperture_width;
-	LOG(INFO) << " aperture_height: " << aperture_height;
+	LOG(INFO) << "               profile: " << filename_intrinsics;
+	LOG(INFO) << "             n_cameras: " << n_cameras;
+	LOG(INFO) << "                 width: " << image_size.width;
+	LOG(INFO) << "                height: " << image_size.height;
+	LOG(INFO) << "                  iter: " << iter;
+	LOG(INFO) << "                 delay: " << delay;
+	LOG(INFO) << "        aperture_width: " << aperture_width;
+	LOG(INFO) << "       aperture_height: " << aperture_height;
+	LOG(INFO) << "             use_guess: " << use_guess;
+	//LOG(INFO) << "  use_guess_distortion: " << use_guess_distortion;
+
 	LOG(INFO) << "-----------------------------------";
 
-	int calibrate_flags = cv::CALIB_ZERO_TANGENT_DIST | cv::CALIB_FIX_ASPECT_RATIO;
+	int calibrate_flags =	cv::CALIB_ZERO_TANGENT_DIST | cv::CALIB_FIX_ASPECT_RATIO;
+	if (use_guess) { calibrate_flags |= cv::CALIB_USE_INTRINSIC_GUESS; }
+	//						cv::CALIB_FIX_PRINCIPAL_POINT;
 	// PARAMETERS
 
+
+	vector<Mat> camera_matrix(n_cameras), dist_coeffs(n_cameras);
+
+	for (Mat &d : dist_coeffs)
+	{
+		d = Mat(Size(5, 1), CV_64FC1, cv::Scalar(0.0));
+	}
+
+	if (use_guess)
+	{
+		camera_matrix.clear();
+		vector<Mat> tmp;
+		Size tmp_size;
+		
+		loadIntrinsics(filename_intrinsics, camera_matrix, tmp, tmp_size);
+		CHECK(camera_matrix.size() == n_cameras); // (camera_matrix.size() == dist_coeffs.size())
+		if ((tmp_size != image_size) && (!tmp_size.empty()))
+		{
+			Mat scale = Mat::eye(Size(3, 3), CV_64FC1);
+			scale.at<double>(0, 0) = ((double) image_size.width) / ((double) tmp_size.width);
+			scale.at<double>(1, 1) = ((double) image_size.height) / ((double) tmp_size.height);
+			for (Mat &K : camera_matrix) { K = scale * K; }
+		}
+
+		if (tmp_size.empty())
+		{
+			use_guess = false;
+			LOG(FATAL) << "No valid calibration found.";
+		}
+	}
+
 	vector<cv::VideoCapture> cameras;
 	cameras.reserve(n_cameras);
 	for (int c = 0; c < n_cameras; c++) { cameras.emplace_back(c); }
-	for (auto &camera : cameras) {
+	for (auto &camera : cameras)
+	{
 		if (!camera.isOpened()) {
 			LOG(ERROR) << "Could not open camera device";
 			return;
@@ -68,43 +109,102 @@ void ftl::calibration::intrinsic(map<string, string> &opt) {
 
 	vector<vector<vector<Vec2f>>> image_points(n_cameras);
 	vector<vector<vector<Vec3f>>> object_points(n_cameras);
+	
 	vector<Mat> img(n_cameras);
+	vector<Mat> img_display(n_cameras);
 	vector<int> count(n_cameras, 0);
+	Mat display(Size(image_size.width * n_cameras, image_size.height), CV_8UC3);
 
-	while (iter > *std::min_element(count.begin(), count.end())) {
+	for (int c = 0; c < n_cameras; c++)
+	{
+		img_display[c] = Mat(display, cv::Rect(c * image_size.width, 0, image_size.width, image_size.height));
+	}
 
-		for (auto &camera : cameras) { camera.grab(); }
+	std::mutex m;
+	std::atomic<bool> ready = false;
+	auto capture = std::thread([n_cameras, delay, &m, &ready, &count, &calib, &img, &image_points, &object_points]()
+	{
+		vector<Mat> tmp(n_cameras);
+		while(true)
+		{
+			if (!ready)
+			{
+				std::this_thread::sleep_for(std::chrono::milliseconds(delay));
+				continue;
+			}
 
-		for (int c = 0; c < n_cameras; c++) {
-			vector<Vec2f> points;
-			cameras[c].retrieve(img[c]);
-		
-			if (calib.findPoints(img[c], points)) {
-				calib.drawPoints(img[c], points);
-				count[c]++;
+			m.lock();
+			ready = false;
+			for (int c = 0; c < n_cameras; c++)
+			{
+				img[c].copyTo(tmp[c]);
+			}
+			m.unlock();
+			
+			for (int c = 0; c < n_cameras; c++)
+			{
+				vector<Vec2f> points;
+				if (calib.findPoints(tmp[c], points))
+				{
+					count[c]++;
+				}
+				else { continue; }
+
+				vector<Vec3f> points_ref;
+				calib.objectPoints(points_ref);
+				Mat camera_matrix, dist_coeffs;
+				image_points[c].push_back(points);
+				object_points[c].push_back(points_ref);
 			}
-			else { continue; }
-		
-			vector<Vec3f> points_ref;
-			calib.objectPoints(points_ref);
 
-			Mat camera_matrix, dist_coeffs;
-			vector<Mat> rvecs, tvecs;
-		
-			image_points[c].push_back(points);
-			object_points[c].push_back(points_ref);
+			std::this_thread::sleep_for(std::chrono::milliseconds(delay));
+		}
+	});
+
+	while (iter > *std::min_element(count.begin(), count.end()))
+	{
+		if (m.try_lock())
+		{
+			for (auto &camera : cameras) { camera.grab(); }
+
+			for (int c = 0; c < n_cameras; c++)
+			{
+				cameras[c].retrieve(img[c]);
+			}
+
+			ready = true;
+			m.unlock();
 		}
 		
-		for (int c = 0; c < n_cameras; c++) {
-			cv::imshow("Camera " + std::to_string(c), img[c]);
+		for (int c = 0; c < n_cameras; c++)
+		{
+			img[c].copyTo(img_display[c]);
+			m.lock();
+
+			if (image_points[c].size() > 0)
+			{
+				
+				for (auto &points : image_points[c])
+				{
+					calib.drawCorners(img_display[c], points);
+				}
+
+				calib.drawPoints(img_display[c], image_points[c].back());
+			}
+
+			m.unlock();
 		}
 
-		cv::waitKey(delay);
+		cv::namedWindow("Cameras", cv::WINDOW_KEEPRATIO | cv::WINDOW_NORMAL);
+		cv::imshow("Cameras", display);
+
+		cv::waitKey(10);
 	}
-	
-	vector<Mat> camera_matrix(n_cameras), dist_coeffs(n_cameras);
 
-	for (int c = 0; c < n_cameras; c++) {
+	cv::destroyAllWindows();
+
+	for (int c = 0; c < n_cameras; c++)
+	{
 		LOG(INFO) << "Calculating intrinsic paramters for camera " << std::to_string(c);
 		vector<Mat> rvecs, tvecs;
 		
@@ -135,7 +235,7 @@ void ftl::calibration::intrinsic(map<string, string> &opt) {
 		LOG(INFO) << "";
 	}
 
-	saveIntrinsics(filename_intrinsics, camera_matrix, dist_coeffs);
+	saveIntrinsics(filename_intrinsics, camera_matrix, dist_coeffs, image_size);
 	LOG(INFO) << "intrinsic paramaters saved to: " << filename_intrinsics;
 	
 	vector<Mat> map1(n_cameras), map2(n_cameras);
diff --git a/applications/calibration/src/stereo.cpp b/applications/calibration/src/stereo.cpp
index c009d6dd0311c27bc952e7daba18afc76e30288f..964cf815300971cf31130e78fae94e1c21a172ad 100644
--- a/applications/calibration/src/stereo.cpp
+++ b/applications/calibration/src/stereo.cpp
@@ -68,7 +68,7 @@ void ftl::calibration::stereo(map<string, string> &opt) {
 
 	int stereocalibrate_flags =
 		cv::CALIB_FIX_INTRINSIC | cv::CALIB_FIX_PRINCIPAL_POINT | cv::CALIB_FIX_ASPECT_RATIO |
-		cv::CALIB_ZERO_TANGENT_DIST | cv::CALIB_SAME_FOCAL_LENGTH | cv::CALIB_RATIONAL_MODEL |
+		cv::CALIB_ZERO_TANGENT_DIST | cv::CALIB_SAME_FOCAL_LENGTH | 
 		cv::CALIB_FIX_K3 | cv::CALIB_FIX_K4 | cv::CALIB_FIX_K5;
 
 	vector<cv::VideoCapture> cameras { cv::VideoCapture(0), cv::VideoCapture(1) };
@@ -93,11 +93,17 @@ void ftl::calibration::stereo(map<string, string> &opt) {
 	
 	vector<Mat> dist_coeffs(2);
 	vector<Mat> camera_matrices(2);
-
-	if (!loadIntrinsics(filename_intrinsics, camera_matrices, dist_coeffs)) {
+	Size intrinsic_resolution;
+	if (!loadIntrinsics(filename_intrinsics, camera_matrices, dist_coeffs, intrinsic_resolution))
+	{
 		LOG(FATAL) << "Failed to load intrinsic camera parameters from file.";
 	}
 	
+	if (intrinsic_resolution != image_size)
+	{
+		LOG(FATAL) << "Intrinsic resolution is not same as input resolution (TODO)";
+	}
+
 	Mat R, T, E, F, per_view_errors;
 	
 	// capture calibration patterns
@@ -152,11 +158,11 @@ void ftl::calibration::stereo(map<string, string> &opt) {
 		vector<Vec3f> points_ref;
 		calib.objectPoints(points_ref);
 		
+		/* doesn't seem to be very helpful (error almost always low enough)
 		// calculate reprojection error with single pair of images
 		// reject it if RMS reprojection error too high
 		int flags = stereocalibrate_flags;
-
-		// TODO move to "findPoints"-thread
+		
 		double rms_iter = stereoCalibrate(
 					vector<vector<Vec3f>> { points_ref }, 
 					vector<vector<Vec2f>> { new_points[0] },
@@ -170,7 +176,7 @@ void ftl::calibration::stereo(map<string, string> &opt) {
 		if (rms_iter > max_error) {
 			LOG(WARNING) << "RMS reprojection error too high, maximum allowed error: " << max_error;
 			continue;
-		}
+		}*/
 		
 		if (use_grid) {
 			// store results in result grid
@@ -225,14 +231,11 @@ void ftl::calibration::stereo(map<string, string> &opt) {
 	Mat R1, R2, P1, P2, Q;
 	cv::Rect validRoi[2];
 
-	// calculate extrinsic parameters
-	// NOTE: 	Other code assumes CALIB_ZERO_DISPARITY is used (for Cy == Cx). 
-	//			Depth map map calculation disparityToDepth() could be incorrect otherwise.
 	stereoRectify(
 		camera_matrices[0], dist_coeffs[0],
 		camera_matrices[1], dist_coeffs[1],
 		image_size, R, T, R1, R2, P1, P2, Q,
-		cv::CALIB_ZERO_DISPARITY, alpha, image_size,
+		0, alpha, image_size,
 		&validRoi[0], &validRoi[1]
 	);
 
@@ -257,21 +260,33 @@ void ftl::calibration::stereo(map<string, string> &opt) {
 			camera.grab();
 			camera.retrieve(in[i]);
 	
+			auto p = cv::Point2i(camera_matrices[i].at<double>(0, 2), camera_matrices[i].at<double>(1, 2));
+			cv::drawMarker(in[i], p, cv::Scalar(51, 204, 51), cv::MARKER_CROSS, 40, 1);
+			cv::drawMarker(in[i], p, cv::Scalar(51, 204, 51), cv::MARKER_SQUARE, 25);
+
 			cv::remap(in[i], out[i], map1[i], map2[i], cv::INTER_CUBIC);
-			// cv::cvtColor(out[i], out_gray[i], cv::COLOR_BGR2GRAY);
 
 			// draw lines
 			for (int r = 50; r < image_size.height; r = r+50) {
 				cv::line(out[i], cv::Point(0, r), cv::Point(image_size.width-1, r), cv::Scalar(0,0,255), 1);
 			}
 
+			if (i == 0) { // left camera
+				auto p_r = cv::Point2i(-Q.at<double>(0, 3), -Q.at<double>(1, 3));
+				cv::drawMarker(out[i], p_r, cv::Scalar(0, 0, 204), cv::MARKER_CROSS, 30);
+				cv::drawMarker(out[i], p_r, cv::Scalar(0, 0, 204), cv::MARKER_SQUARE);
+			}
+			
+			cv::imshow("Camera " + std::to_string(i) + " (unrectified)", in[i]);
 			cv::imshow("Camera " + std::to_string(i) + " (rectified)", out[i]);
 		}
-
+		
 		/* not useful
 		cv::absdiff(out_gray[0], out_gray[1], diff);
 		cv::applyColorMap(diff, diff_color, cv::COLORMAP_JET);
 		cv::imshow("Difference", diff_color);
 		*/
 	}
+
+	cv::destroyAllWindows();
 }
diff --git a/applications/groupview/src/main.cpp b/applications/groupview/src/main.cpp
index f57337183e2a0528a5f85cbfc42b49ff32b2ebba..6b32221ef1a13ad05f9e5bb915c1186eca0aa52c 100644
--- a/applications/groupview/src/main.cpp
+++ b/applications/groupview/src/main.cpp
@@ -13,7 +13,12 @@
 using Eigen::Matrix4d;
 using std::map;
 using std::string;
+using std::vector;
+using cv::Size;
+using cv::Mat;
+using ftl::rgbd::Channel;
 
+// TODO: remove code duplication (function from reconstruction)
 static void from_json(nlohmann::json &json, map<string, Matrix4d> &transformations) {
 	for (auto it = json.begin(); it != json.end(); ++it) {
 		Eigen::Matrix4d m;
@@ -23,6 +28,7 @@ static void from_json(nlohmann::json &json, map<string, Matrix4d> &transformatio
 	}
 }
 
+// TODO: remove code duplication (function from reconstruction)
 static bool loadTransformations(const string &path, map<string, Matrix4d> &data) {
 	std::ifstream file(path);
 	if (!file.is_open()) {
@@ -36,8 +42,108 @@ static bool loadTransformations(const string &path, map<string, Matrix4d> &data)
 	return true;
 }
 
-int main(int argc, char **argv) {
-	auto root = ftl::configure(argc, argv, "viewer_default");
+// TODO: remove code duplication (function from calibrate-multi)
+void stack(const vector<Mat> &img, Mat &out, const int rows, const int cols) {
+	Size size = img[0].size();
+	Size size_out = Size(size.width * cols, size.height * rows);
+	if (size_out != out.size() || out.type() != CV_8UC3) {
+		out = Mat(size_out, CV_8UC3, cv::Scalar(0, 0, 0));
+	}
+
+	for (size_t i = 0; i < img.size(); i++) {
+		int row = i % rows;
+		int col = i / rows;
+		auto rect = cv::Rect(size.width * col, size.height * row, size.width, size.height);
+		img[i].copyTo(out(rect));
+	}
+}
+
+// TODO: remove code duplication (function from calibrate-multi)
+void stack(const vector<Mat> &img, Mat &out) {
+	// TODO
+	int rows = 2;
+	int cols = (img.size() + 1) / 2;
+	stack(img, out, rows, cols);
+}
+
+void modeLeftRight(ftl::Configurable *root) {
+	ftl::net::Universe *net = ftl::create<ftl::net::Universe>(root, "net");
+
+	net->start();
+	net->waitConnections();
+
+	auto sources = ftl::createArray<ftl::rgbd::Source>(root, "sources", net);
+	const string path = root->value<string>("save_to", "./");
+	const string file_type = root->value<string>("file_type", "jpg");
+
+	const size_t n_cameras = sources.size() * 2;
+	ftl::rgbd::Group group;
+
+	for (auto* src : sources) {
+		src->setChannel(Channel::Right);
+		group.addSource(src);
+	}
+
+	std::mutex mutex;
+	std::atomic<bool> new_frames = false;
+	vector<Mat> rgb(n_cameras), rgb_new(n_cameras);
+	
+	group.sync([&mutex, &new_frames, &rgb_new](ftl::rgbd::FrameSet &frames) {
+		mutex.lock();
+		bool good = true;
+		for (size_t i = 0; i < frames.frames.size(); i ++) {
+			auto &chan1 = frames.frames[i].get<cv::Mat>(Channel::Colour);
+			auto &chan2 = frames.frames[i].get<cv::Mat>(frames.sources[i]->getChannel());
+			if (chan1.empty()) good = false;
+			if (chan2.empty()) good = false;
+			if (chan1.channels() != 3) good = false; // ASSERT
+			if (chan2.channels() != 3) good = false;
+			if (!good) break;
+			
+			chan1.copyTo(rgb_new[2 * i]);
+			chan2.copyTo(rgb_new[2 * i + 1]);
+		}
+
+		new_frames = good;
+		mutex.unlock();
+		return true;
+	});
+	
+	int idx = 0;
+
+	Mat show;
+	
+	while (ftl::running) {
+		int key;
+		
+		while (!new_frames) {
+			for (auto src : sources) { src->grab(30); }
+			key = cv::waitKey(10);
+		}
+
+		mutex.lock();
+		rgb.swap(rgb_new);
+		new_frames = false;
+		mutex.unlock();
+		
+		stack(rgb, show);
+		cv::namedWindow("Cameras", cv::WINDOW_KEEPRATIO | cv::WINDOW_NORMAL);
+		cv::imshow("Cameras", show);
+
+		key = cv::waitKey(100);
+		// TODO: fix
+		if (key == 's') {
+			 for (size_t c = 0; c < n_cameras; c++ ) {
+			 	cv::imwrite(path + "camera" + std::to_string(c) + "_" + std::to_string(idx) + "." + file_type, rgb[c]);
+			 }
+			 LOG(INFO) << "Saved (" << idx << ")";
+			 idx++;
+		}
+		if (key == 27) break;
+	}
+}
+
+void modeFrame(ftl::Configurable *root, int frames=1) {
 	ftl::net::Universe *net = ftl::create<ftl::net::Universe>(root, "net");
 
 	net->start();
@@ -62,47 +168,161 @@ int main(int argc, char **argv) {
 		} else {
 			s->setPose(T->second);
 		}
-		s->setChannel(ftl::rgbd::kChanDepth);
+		s->setChannel(Channel::Depth);
 		group.addSource(s);
 	}
 
-	bool grab = false;
+	std::atomic<bool> grab = false;
+	std::atomic<bool> video = false;
 
-	group.sync([&grab](const ftl::rgbd::FrameSet &fs) {
-		LOG(INFO) << "Complete set: " << fs.timestamp;
-		if (grab) {
-			grab = false;
+	vector<cv::Mat> rgb(sources.size());
+	vector<cv::Mat> depth(sources.size());
+	MUTEX mtx;
+
+	group.sync([&grab,&rgb,&depth,&mtx](ftl::rgbd::FrameSet &fs) {
+		UNIQUE_LOCK(mtx, lk);
+		//LOG(INFO) << "Complete set: " << fs.timestamp;
+		if (!ftl::running) { return false; }
+		
+		std::vector<cv::Mat> frames;
+
+		for (size_t i=0; i<fs.sources.size(); ++i) {
+			auto &chan1 = fs.frames[i].get<cv::Mat>(Channel::Colour);
+			auto &chan2 = fs.frames[i].get<cv::Mat>(fs.sources[i]->getChannel());
+			if (chan1.empty() || chan2.empty()) return true;
+
+			frames.push_back(chan1);
+		}
+
+		cv::Mat show;
+
+		stack(frames, show);
+
+		cv::resize(show, show, cv::Size(1280,720));
+		cv::namedWindow("Cameras", cv::WINDOW_KEEPRATIO | cv::WINDOW_NORMAL);
+		cv::imshow("Cameras", show);
+
+		auto key = cv::waitKey(1);
+		if (key == 27) ftl::running = false;
+		if (key == 'g') grab = true;
 
 #ifdef HAVE_LIBARCHIVE
+		if (grab) {
+			grab = false;
 			char timestamp[18];
 			std::time_t t=std::time(NULL);
 			std::strftime(timestamp, sizeof(timestamp), "%F-%H%M%S", std::localtime(&t));
 			auto writer = ftl::rgbd::SnapshotWriter(std::string(timestamp) + ".tar.gz");
 
 			for (size_t i=0; i<fs.sources.size(); ++i) {
-				writer.addCameraParams(std::string("camera")+std::to_string(i), fs.sources[i]->getPose(), fs.sources[i]->parameters());
-				LOG(INFO) << "SAVE: " << fs.channel1[i].cols << ", " << fs.channel2[i].type();
-				writer.addCameraRGBD(std::string("camera")+std::to_string(i), fs.channel1[i], fs.channel2[i]);
+				auto &chan1 = fs.frames[i].get<cv::Mat>(Channel::Colour);
+				auto &chan2 = fs.frames[i].get<cv::Mat>(fs.sources[i]->getChannel());
+
+				writer.addSource(fs.sources[i]->getURI(), fs.sources[i]->parameters(), fs.sources[i]->getPose());
+				//LOG(INFO) << "SAVE: " << fs.channel1[i].cols << ", " << fs.channel2[i].type();
+				writer.addRGBD(i, chan1, chan2);
 			}
-#endif  // HAVE_LIBARCHIVE
 		}
+#endif  // HAVE_LIBARCHIVE
 		return true;
 	});
 
-	int current = 0;
+	/*cv::Mat show;
 
 	while (ftl::running) {
-		//std::this_thread::sleep_for(std::chrono::milliseconds(20));
 		for (auto s : sources) s->grab(30);
-		cv::Mat rgb,depth;
-		sources[current%sources.size()]->getFrames(rgb, depth);
-		if (!rgb.empty()) cv::imshow("View", rgb);
-		auto key = cv::waitKey(20);
+		for (size_t i = 0; i < sources.size(); i++) {
+			//do { sources[i]->getFrames(rgb[i], depth[i]); }
+			while(rgb[i].empty());
+		}
+
+		{
+			UNIQUE_LOCK(mtx, lk);
+			stack(rgb, show);
+		}
+		cv::resize(show, show, cv::Size(1280,720));
+		cv::namedWindow("Cameras", cv::WINDOW_KEEPRATIO | cv::WINDOW_NORMAL);
+		cv::imshow("Cameras", show);
 
+		auto key = cv::waitKey(20);
 		if (key == 27) break;
-		if (key == 'n') current++;
 		if (key == 'g') grab = true;
+	}*/
+}
+
+void modeVideo(ftl::Configurable *root) {
+
+	ftl::net::Universe *net = ftl::create<ftl::net::Universe>(root, "net");
+
+	net->start();
+	net->waitConnections();
+
+	auto sources = ftl::createArray<ftl::rgbd::Source>(root, "sources", net);
+	const string path = root->value<string>("save_to", "./");
+
+	for (auto* src : sources) { src->setChannel(Channel::Depth); }
+
+	cv::Mat show;
+	vector<cv::Mat> rgb(sources.size());
+	vector<cv::Mat> depth(sources.size());
+
+#ifdef HAVE_LIBARCHIVE
+	char timestamp[18];
+	std::time_t t=std::time(NULL);
+	std::strftime(timestamp, sizeof(timestamp), "%F-%H%M%S", std::localtime(&t));
+	ftl::rgbd::SnapshotWriter writer = ftl::rgbd::SnapshotWriter(std::string(timestamp) + ".tar.gz");
+
+	for (size_t i = 0; i < sources.size(); i++) {
+		writer.addSource(sources[i]->getURI(), sources[i]->parameters(), sources[i]->getPose());
+	}
+#endif // HAVE_LIBARCHIVE
+
+	bool save = false;
+
+	while (ftl::running) {
+		for (auto s : sources) s->grab(30);
+		for (size_t i = 0; i < sources.size(); i++) {
+			do { sources[i]->getFrames(rgb[i], depth[i]); }
+			while(rgb[i].empty() || depth[i].empty());
+		}
+
+#ifdef HAVE_LIBARCHIVE
+		if (save) {
+			for (size_t i = 0; i < sources.size(); i++) {
+				writer.addRGBD(i, rgb[i], depth[i]);
+			}
+		}
+#endif // HAVE_LIBARCHIVE
+
+		stack(rgb, show);
+		cv::namedWindow("Cameras", cv::WINDOW_KEEPRATIO | cv::WINDOW_NORMAL);
+		cv::imshow("Cameras", show);
+
+		auto key = cv::waitKey(20);
+		if (key == 'r') {
+			save = true;
+		}
+		if (key == 's') {
+			save = false;
+		}
+		if (key == 27) break;
+	}
+}
+
+int main(int argc, char **argv) {
+	auto root = ftl::configure(argc, argv, "viewer_default");
+
+	if (root->value("stereo", false)) {
+		LOG(INFO) << "Stereo images mode";
+		modeLeftRight(root);
+	} else if (root->value("video", false)) {
+		LOG(INFO) << "Video mode";
+		modeVideo(root);
+	} else {
+		//modeVideo(root);
+		modeFrame(root);
 	}
 
+	ftl::running = false;
 	return 0;
 }
diff --git a/applications/gui/CMakeLists.txt b/applications/gui/CMakeLists.txt
index b8970d7b91c47aa780c4ee9692b92779b1bc2fd6..fbed0680dde997c0f0a76519ac272fb3e5c1c64f 100644
--- a/applications/gui/CMakeLists.txt
+++ b/applications/gui/CMakeLists.txt
@@ -27,6 +27,6 @@ target_include_directories(ftl-gui PUBLIC
 #endif()
 
 #target_include_directories(cv-node PUBLIC ${PROJECT_SOURCE_DIR}/include)
-target_link_libraries(ftl-gui ftlcommon ftlctrl ftlrgbd Threads::Threads ${OpenCV_LIBS} ${OPENVR_LIBRARIES} glog::glog ftlnet ftlrender nanogui GL)
+target_link_libraries(ftl-gui ftlcommon ftlctrl ftlrgbd Threads::Threads ${OpenCV_LIBS} ${OPENVR_LIBRARIES} glog::glog ftlnet nanogui GL)
 
 
diff --git a/applications/gui/src/camera.cpp b/applications/gui/src/camera.cpp
index 0f2d8c1fa962dd7b9021ed34ebbf8e9e92ee84e8..32d78a4a0ff8bbccfed60b13293b1277ab599792 100644
--- a/applications/gui/src/camera.cpp
+++ b/applications/gui/src/camera.cpp
@@ -7,6 +7,8 @@
 using ftl::rgbd::isValidDepth;
 using ftl::gui::GLTexture;
 using ftl::gui::PoseWindow;
+using ftl::rgbd::Channel;
+using ftl::rgbd::Channels;
 
 // TODO(Nick) MOVE
 class StatisticsImage {
@@ -16,7 +18,7 @@ private:
 	float n_;		// total number of samples
 
 public:
-	StatisticsImage(cv::Size size);
+	explicit StatisticsImage(cv::Size size);
 	StatisticsImage(cv::Size size, float max_f);
 
 	/* @brief reset all statistics to 0
@@ -129,19 +131,29 @@ ftl::gui::Camera::Camera(ftl::gui::Screen *screen, ftl::rgbd::Source *src) : scr
 	rotmat_.setIdentity();
 	//up_ = Eigen::Vector3f(0,1.0f,0);
 	lerpSpeed_ = 0.999f;
-	depth_ = false;
+	sdepth_ = false;
 	ftime_ = (float)glfwGetTime();
 	pause_ = false;
 
-	channel_ = ftl::rgbd::kChanLeft;
+	channel_ = Channel::Left;
 
-	channels_.push_back(ftl::rgbd::kChanLeft);
-	channels_.push_back(ftl::rgbd::kChanDepth);
+	channels_ += Channel::Left;
+	channels_ += Channel::Depth;
 
 	// Create pose window...
 	posewin_ = new PoseWindow(screen, src_->getURI());
 	posewin_->setTheme(screen->windowtheme);
 	posewin_->setVisible(false);
+
+	src->setCallback([this](int64_t ts, cv::Mat &rgb, cv::Mat &depth) {
+		UNIQUE_LOCK(mutex_, lk);
+		rgb_.create(rgb.size(), rgb.type());
+		depth_.create(depth.size(), depth.type());
+		cv::swap(rgb_,rgb);
+		cv::swap(depth_, depth);
+		cv::flip(rgb_,rgb_,0);
+		cv::flip(depth_,depth_,0);
+	});
 }
 
 ftl::gui::Camera::~Camera() {
@@ -219,26 +231,27 @@ void ftl::gui::Camera::showSettings() {
 
 }
 
-void ftl::gui::Camera::setChannel(ftl::rgbd::channel_t c) {
+void ftl::gui::Camera::setChannel(Channel c) {
 	channel_ = c;
 	switch (c) {
-	case ftl::rgbd::kChanFlow:
-	case ftl::rgbd::kChanConfidence:
-	case ftl::rgbd::kChanNormals:
-	case ftl::rgbd::kChanRight:
+	case Channel::Energy:
+	case Channel::Flow:
+	case Channel::Confidence:
+	case Channel::Normals:
+	case Channel::Right:
 		src_->setChannel(c);
 		break;
 
-	case ftl::rgbd::kChanDeviation:
+	case Channel::Deviation:
 		if (stats_) { stats_->reset(); }
-		src_->setChannel(ftl::rgbd::kChanDepth);
+		src_->setChannel(Channel::Depth);
 		break;
 	
-	case ftl::rgbd::kChanDepth:
+	case Channel::Depth:
 		src_->setChannel(c);
 		break;
 	
-	default: src_->setChannel(ftl::rgbd::kChanNone);
+	default: src_->setChannel(Channel::None);
 	}
 }
 
@@ -253,17 +266,63 @@ static Eigen::Matrix4d ConvertSteamVRMatrixToMatrix4( const vr::HmdMatrix34_t &m
 	return matrixObj;
 }
 
+static void visualizeDepthMap(	const cv::Mat &depth, cv::Mat &out,
+								const float max_depth)
+{
+	DCHECK(max_depth > 0.0);
+
+	depth.convertTo(out, CV_8U, 255.0f / max_depth);
+	out = 255 - out;
+	cv::Mat mask = (depth >= 39.0f); // TODO (mask for invalid pixels)
+	
+	applyColorMap(out, out, cv::COLORMAP_JET);
+	out.setTo(cv::Scalar(255, 255, 255), mask);
+}
+
+static void visualizeEnergy(	const cv::Mat &depth, cv::Mat &out,
+								const float max_depth)
+{
+	DCHECK(max_depth > 0.0);
+
+	depth.convertTo(out, CV_8U, 255.0f / max_depth);
+	//out = 255 - out;
+	cv::Mat mask = (depth >= 39.0f); // TODO (mask for invalid pixels)
+	
+	applyColorMap(out, out, cv::COLORMAP_JET);
+	out.setTo(cv::Scalar(255, 255, 255), mask);
+}
+
+static void drawEdges(	const cv::Mat &in, cv::Mat &out,
+						const int ksize = 3, double weight = -1.0, const int threshold = 32,
+						const int threshold_type = cv::THRESH_TOZERO)
+{
+	cv::Mat edges;
+	cv::Laplacian(in, edges, 8, ksize);
+	cv::threshold(edges, edges, threshold, 255, threshold_type);
+
+	cv::Mat edges_color(in.size(), CV_8UC3);
+	cv::addWeighted(edges, weight, out, 1.0, 0.0, out, CV_8UC3);
+}
+
+bool ftl::gui::Camera::thumbnail(cv::Mat &thumb) {
+	UNIQUE_LOCK(mutex_, lk);
+	src_->grab(1,9);
+	if (rgb_.empty()) return false;
+	cv::resize(rgb_, thumb, cv::Size(320,180));
+	return true;
+}
+
 const GLTexture &ftl::gui::Camera::captureFrame() {
 	float now = (float)glfwGetTime();
 	delta_ = now - ftime_;
 	ftime_ = now;
 
 	if (src_ && src_->isReady()) {
-		cv::Mat rgb, depth;
+		UNIQUE_LOCK(mutex_, lk);
 
 		if (screen_->hasVR()) {
 			#ifdef HAVE_OPENVR
-			src_->setChannel(ftl::rgbd::kChanRight);
+			src_->setChannel(Channel::Right);
 
 			vr::VRCompositor()->WaitGetPoses(rTrackedDevicePose_, vr::k_unMaxTrackedDeviceCount, NULL, 0 );
 
@@ -300,37 +359,37 @@ const GLTexture &ftl::gui::Camera::captureFrame() {
 		}
 
 		src_->grab();
-		src_->getFrames(rgb, depth);
-
-		cv::flip(rgb,rgb,0);
-		cv::flip(depth,depth,0);
+		//src_->getFrames(rgb, depth);
 
 		// When switching from right to depth, client may still receive
 		// right images from previous batch (depth.channels() == 1 check)
-		if (channel_ == ftl::rgbd::kChanDeviation &&
-			depth.rows > 0 && depth.channels() == 1)
+		if (channel_ == Channel::Deviation &&
+			depth_.rows > 0 && depth_.channels() == 1)
 		{
 			if (!stats_) {
-				stats_ = new StatisticsImage(depth.size());
+				stats_ = new StatisticsImage(depth_.size());
 			}
 			
-			stats_->update(depth);
+			stats_->update(depth_);
 		}
 
 		cv::Mat tmp;
 
 		switch(channel_) {
-			case ftl::rgbd::kChanDepth:
-				if (depth.rows == 0) { break; }
-				//imageSize = Vector2f(depth.cols,depth.rows);
-				depth.convertTo(tmp, CV_8U, 255.0f / 5.0f);
-				tmp = 255 - tmp;
-				applyColorMap(tmp, tmp, cv::COLORMAP_JET);
+			case Channel::Energy:
+				if (depth_.rows == 0) { break; }
+				visualizeEnergy(depth_, tmp, 10.0);
+				texture_.update(tmp);
+				break;
+			case Channel::Depth:
+				if (depth_.rows == 0) { break; }
+				visualizeDepthMap(depth_, tmp, 7.0);
+				if (screen_->root()->value("showEdgesInDepth", false)) drawEdges(rgb_, tmp);
 				texture_.update(tmp);
 				break;
 			
-			case ftl::rgbd::kChanDeviation:
-				if (depth.rows == 0) { break; }
+			case Channel::Deviation:
+				if (depth_.rows == 0) { break; }
 				//imageSize = Vector2f(depth.cols, depth.rows);
 				stats_->getStdDev(tmp);
 				tmp.convertTo(tmp, CV_8U, 1000.0);
@@ -338,23 +397,23 @@ const GLTexture &ftl::gui::Camera::captureFrame() {
 				texture_.update(tmp);
 				break;
 
-		case ftl::rgbd::kChanFlow:
-		case ftl::rgbd::kChanConfidence:
-		case ftl::rgbd::kChanNormals:
-			case ftl::rgbd::kChanRight:
-				if (depth.rows == 0 || depth.type() != CV_8UC3) { break; }
-				texture_.update(depth);
+		case Channel::Flow:
+		case Channel::Confidence:
+		case Channel::Normals:
+		case Channel::Right:
+				if (depth_.rows == 0 || depth_.type() != CV_8UC3) { break; }
+				texture_.update(depth_);
 				break;
 
 			default:
-				if (rgb.rows == 0) { break; }
+				if (rgb_.rows == 0) { break; }
 				//imageSize = Vector2f(rgb.cols,rgb.rows);
-				texture_.update(rgb);
+				texture_.update(rgb_);
 
 				#ifdef HAVE_OPENVR
-				if (screen_->hasVR() && depth.channels() >= 3) {
+				if (screen_->hasVR() && depth_.channels() >= 3) {
 					LOG(INFO) << "DRAW RIGHT";
-					textureRight_.update(depth);
+					textureRight_.update(depth_);
 				}
 				#endif
 		}
diff --git a/applications/gui/src/camera.hpp b/applications/gui/src/camera.hpp
index cdd243ab3cb35e1cad7fddf9dfa9b3eae339c774..55042fe0d7b3baf5f24a26e390b1348084bcf320 100644
--- a/applications/gui/src/camera.hpp
+++ b/applications/gui/src/camera.hpp
@@ -23,6 +23,8 @@ class Camera {
 	Camera(ftl::gui::Screen *screen, ftl::rgbd::Source *src);
 	~Camera();
 
+	Camera(const Camera &)=delete;
+
 	ftl::rgbd::Source *source();
 
 	int width() { return (src_) ? src_->parameters().width : 0; }
@@ -36,16 +38,18 @@ class Camera {
 	void showPoseWindow();
 	void showSettings();
 
-	void setChannel(ftl::rgbd::channel_t c);
+	void setChannel(ftl::rgbd::Channel c);
 
 	void togglePause();
 	void isPaused();
-	const std::vector<ftl::rgbd::channel_t> &availableChannels();
+	const ftl::rgbd::Channels &availableChannels();
 
 	const GLTexture &captureFrame();
 	const GLTexture &getLeft() const { return texture_; }
 	const GLTexture &getRight() const { return textureRight_; }
 
+	bool thumbnail(cv::Mat &thumb);
+
 	nlohmann::json getMetaData();
 
 	StatisticsImage *stats_ = nullptr;
@@ -65,10 +69,13 @@ class Camera {
 	float ftime_;
 	float delta_;
 	float lerpSpeed_;
-	bool depth_;
+	bool sdepth_;
 	bool pause_;
-	ftl::rgbd::channel_t channel_;
-	std::vector<ftl::rgbd::channel_t> channels_;
+	ftl::rgbd::Channel channel_;
+	ftl::rgbd::Channels channels_;
+	cv::Mat rgb_;
+	cv::Mat depth_;
+	MUTEX mutex_;
 
 	#ifdef HAVE_OPENVR
 	vr::TrackedDevicePose_t rTrackedDevicePose_[ vr::k_unMaxTrackedDeviceCount ];
diff --git a/applications/gui/src/media_panel.cpp b/applications/gui/src/media_panel.cpp
index 6fb6d5085cc02efae38fcdfce0819ee0d2ae7110..cb44400bb1c850669332376e0134f138b9c460f3 100644
--- a/applications/gui/src/media_panel.cpp
+++ b/applications/gui/src/media_panel.cpp
@@ -12,91 +12,89 @@
 #endif
 
 using ftl::gui::MediaPanel;
+using ftl::rgbd::Channel;
 
 MediaPanel::MediaPanel(ftl::gui::Screen *screen) : nanogui::Window(screen, ""), screen_(screen) {
-    using namespace nanogui;
+	using namespace nanogui;
 
-    paused_ = false;
-    writer_ = nullptr;
+	paused_ = false;
+	writer_ = nullptr;
 
-    setLayout(new BoxLayout(Orientation::Horizontal,
+	setLayout(new BoxLayout(Orientation::Horizontal,
 									Alignment::Middle, 5, 10));
 
-    auto size = Vector2i(400, 60);
-    //setFixedSize(size);
-    setPosition(Vector2i(screen->width() / 2 - size[0]/2, screen->height() - 30 - size[1]));
+	auto size = Vector2i(400, 60);
+	//setFixedSize(size);
+	setPosition(Vector2i(screen->width() / 2 - size[0]/2, screen->height() - 30 - size[1]));
 
-    auto button = new Button(this, "", ENTYPO_ICON_EDIT);
+	auto button = new Button(this, "", ENTYPO_ICON_EDIT);
 	button->setTooltip("Edit camera properties");
-    button->setCallback([this]() {
-        auto *cam = screen_->activeCamera();
-        if (cam) cam->showPoseWindow();
-    });
+	button->setCallback([this]() {
+		auto *cam = screen_->activeCamera();
+		if (cam) cam->showPoseWindow();
+	});
 
-    button = new Button(this, "", ENTYPO_ICON_CONTROLLER_RECORD);
-    button->setFlags(Button::ToggleButton);
-    button->setChangeCallback([this,button](bool state) {
-        if (state){
-            auto *cam = screen_->activeCamera();
+	button = new Button(this, "", ENTYPO_ICON_CONTROLLER_RECORD);
+	button->setFlags(Button::ToggleButton);
+	button->setChangeCallback([this,button](bool state) {
+		if (state){
+			auto *cam = screen_->activeCamera();
 
-            button->setTextColor(nanogui::Color(1.0f,0.1f,0.1f,1.0f));
-            char timestamp[18];
+			button->setTextColor(nanogui::Color(1.0f,0.1f,0.1f,1.0f));
+			char timestamp[18];
 			std::time_t t=std::time(NULL);
 			std::strftime(timestamp, sizeof(timestamp), "%F-%H%M%S", std::localtime(&t));
 			writer_ = new ftl::rgbd::SnapshotStreamWriter(std::string(timestamp) + ".tar.gz", 1000 / 25);
-            writer_->addSource(cam->source());
-            writer_->start();
-        } else {
-            button->setTextColor(nanogui::Color(1.0f,1.0f,1.0f,1.0f));
-            if (writer_) {
-                writer_->stop();
-                delete writer_;
-                writer_ = nullptr;
-            }
-        }
-        //if (state) ... start
-        //else ... stop
-    });
+			writer_->addSource(cam->source());
+			writer_->start();
+		} else {
+			button->setTextColor(nanogui::Color(1.0f,1.0f,1.0f,1.0f));
+			if (writer_) {
+				writer_->stop();
+				delete writer_;
+				writer_ = nullptr;
+			}
+		}
+		//if (state) ... start
+		//else ... stop
+	});
 
 	button = new Button(this, "", ENTYPO_ICON_CONTROLLER_STOP);
-    button->setCallback([this]() {
-        screen_->setActiveCamera(nullptr);
-    });
+	button->setCallback([this]() {
+		screen_->setActiveCamera(nullptr);
+	});
 
-    button = new Button(this, "", ENTYPO_ICON_CONTROLLER_PAUS);
-    button->setCallback([this,button]() {
-        paused_ = !paused_;
-        screen_->control()->pause();
-        if (paused_) {
-            button->setIcon(ENTYPO_ICON_CONTROLLER_PLAY);
-        } else {
-            button->setIcon(ENTYPO_ICON_CONTROLLER_PAUS);
-        }
-    });
+	button = new Button(this, "", ENTYPO_ICON_CONTROLLER_PAUS);
+	button->setCallback([this,button]() {
+		paused_ = !paused_;
+		screen_->control()->pause();
+		if (paused_) {
+			button->setIcon(ENTYPO_ICON_CONTROLLER_PLAY);
+		} else {
+			button->setIcon(ENTYPO_ICON_CONTROLLER_PAUS);
+		}
+	});
 
-    //button = new Button(this, "", ENTYPO_ICON_CONTROLLER_RECORD);
+	//button = new Button(this, "", ENTYPO_ICON_CONTROLLER_RECORD);
 
  #ifdef HAVE_LIBARCHIVE
 	auto button_snapshot = new Button(this, "", ENTYPO_ICON_IMAGES);
-    button_snapshot->setTooltip("Screen capture");
+	button_snapshot->setTooltip("Screen capture");
 	button_snapshot->setCallback([this] {
-        ftl::gui::Camera *cam = screen_->activeCamera();
-        if (!cam) return;
-    
-		try {
-			char timestamp[18];
+	ftl::gui::Camera *cam = screen_->activeCamera();
+	if (!cam) return;
+
+	try {
+		char timestamp[18];
 			std::time_t t=std::time(NULL);
 			std::strftime(timestamp, sizeof(timestamp), "%F-%H%M%S", std::localtime(&t));
 			auto writer = ftl::rgbd::SnapshotWriter(std::string(timestamp) + ".tar.gz");
 			cv::Mat rgb, depth;
 			cam->source()->getFrames(rgb, depth);
-			if (!writer.addCameraParams("0", cam->source()->getPose(), cam->source()->parameters()) || !writer.addCameraRGBD(
-					"0", // TODO
-					rgb,
-					depth
-				)) {
-				LOG(ERROR) << "Snapshot failed";
-			}
+			writer.addSource(	cam->source()->getURI(),
+								cam->source()->parameters(),
+								cam->source()->getPose());
+			writer.addRGBD(0, rgb, depth);
 		}
 		catch(std::runtime_error) {
 			LOG(ERROR) << "Snapshot failed (file error)";
@@ -118,7 +116,7 @@ MediaPanel::MediaPanel(ftl::gui::Screen *screen) : nanogui::Window(screen, ""),
     button->setCallback([this]() {
         ftl::gui::Camera *cam = screen_->activeCamera();
         if (cam) {
-            cam->setChannel(ftl::rgbd::kChanLeft);
+            cam->setChannel(Channel::Left);
         }
     });
 
@@ -127,7 +125,7 @@ MediaPanel::MediaPanel(ftl::gui::Screen *screen) : nanogui::Window(screen, ""),
     right_button_->setCallback([this]() {
         ftl::gui::Camera *cam = screen_->activeCamera();
         if (cam) {
-            cam->setChannel(ftl::rgbd::kChanRight);
+            cam->setChannel(Channel::Right);
         }
     });
 
@@ -136,7 +134,7 @@ MediaPanel::MediaPanel(ftl::gui::Screen *screen) : nanogui::Window(screen, ""),
     depth_button_->setCallback([this]() {
         ftl::gui::Camera *cam = screen_->activeCamera();
         if (cam) {
-            cam->setChannel(ftl::rgbd::kChanDepth);
+            cam->setChannel(Channel::Depth);
         }
     });
 
@@ -153,7 +151,7 @@ MediaPanel::MediaPanel(ftl::gui::Screen *screen) : nanogui::Window(screen, ""),
     button->setCallback([this]() {
         ftl::gui::Camera *cam = screen_->activeCamera();
         if (cam) {
-            cam->setChannel(ftl::rgbd::kChanDeviation);
+            cam->setChannel(Channel::Deviation);
         }
     });
 
@@ -162,7 +160,7 @@ MediaPanel::MediaPanel(ftl::gui::Screen *screen) : nanogui::Window(screen, ""),
     button->setCallback([this]() {
         ftl::gui::Camera *cam = screen_->activeCamera();
         if (cam) {
-            cam->setChannel(ftl::rgbd::kChanNormals);
+            cam->setChannel(Channel::Normals);
         }
     });
 
@@ -171,7 +169,7 @@ MediaPanel::MediaPanel(ftl::gui::Screen *screen) : nanogui::Window(screen, ""),
     button->setCallback([this]() {
         ftl::gui::Camera *cam = screen_->activeCamera();
         if (cam) {
-            cam->setChannel(ftl::rgbd::kChanFlow);
+            cam->setChannel(Channel::Flow);
         }
     });
 
@@ -180,7 +178,16 @@ MediaPanel::MediaPanel(ftl::gui::Screen *screen) : nanogui::Window(screen, ""),
     button->setCallback([this]() {
         ftl::gui::Camera *cam = screen_->activeCamera();
         if (cam) {
-            cam->setChannel(ftl::rgbd::kChanConfidence);
+            cam->setChannel(Channel::Confidence);
+        }
+    });
+
+    button = new Button(popup, "Energy");
+    button->setFlags(Button::RadioButton);
+    button->setCallback([this]() {
+        ftl::gui::Camera *cam = screen_->activeCamera();
+        if (cam) {
+            cam->setChannel(Channel::Energy);
         }
     });
 
diff --git a/applications/gui/src/media_panel.hpp b/applications/gui/src/media_panel.hpp
index d7f9aa9938ee51418629ee42781e738b535c38d0..9e9154d860483a6bf6c88b8da856a4a89862de2f 100644
--- a/applications/gui/src/media_panel.hpp
+++ b/applications/gui/src/media_panel.hpp
@@ -15,7 +15,7 @@ class Screen;
 
 class MediaPanel : public nanogui::Window {
     public:
-    MediaPanel(ftl::gui::Screen *);
+    explicit MediaPanel(ftl::gui::Screen *);
     ~MediaPanel();
 
     void cameraChanged();
diff --git a/applications/gui/src/screen.cpp b/applications/gui/src/screen.cpp
index 6fcd4c12489f8b97a4d20c9be772520cce151cfa..03d1847112fa86468d9661d5a9966b71a3d46b09 100644
--- a/applications/gui/src/screen.cpp
+++ b/applications/gui/src/screen.cpp
@@ -51,15 +51,15 @@ namespace {
         })";
 }
 
-ftl::gui::Screen::Screen(ftl::Configurable *proot, ftl::net::Universe *pnet, ftl::ctrl::Master *controller) : nanogui::Screen(Eigen::Vector2i(1024, 768), "FT-Lab Remote Presence") {
+ftl::gui::Screen::Screen(ftl::Configurable *proot, ftl::net::Universe *pnet, ftl::ctrl::Master *controller) :
+		nanogui::Screen(Eigen::Vector2i(1024, 768), "FT-Lab Remote Presence"),
+		status_("FT-Lab Remote Presence System") {
 	using namespace nanogui;
 	net_ = pnet;
 	ctrl_ = controller;
 	root_ = proot;
 	camera_ = nullptr;
 
-	status_ = "FT-Lab Remote Presence System";
-
 	setSize(Vector2i(1280,720));
 
 	toolbuttheme = new Theme(*theme());
diff --git a/applications/gui/src/src_window.cpp b/applications/gui/src/src_window.cpp
index e98ec081b1d23f34cfe541756cbd8925e4290dbc..e7ffc4b1e0e44caccd67cce5d60d34b481ec58d7 100644
--- a/applications/gui/src/src_window.cpp
+++ b/applications/gui/src/src_window.cpp
@@ -116,7 +116,7 @@ void SourceWindow::draw(NVGcontext *ctx) {
 			cv::Mat t;
 			auto *cam = cameras_[available_[i]];
 			if (cam) {
-				if (cam->source()->thumbnail(t)) {
+				if (cam->thumbnail(t)) {
 					thumbs_[i].update(t);
 				} else {
 					refresh_thumbs_ = true;
diff --git a/applications/gui/src/src_window.hpp b/applications/gui/src/src_window.hpp
index 7f28279c7fa3cd83bdb62a074e55d4d549799f73..b2fe8a9e0957f3345a719a0c37bac46bf473089c 100644
--- a/applications/gui/src/src_window.hpp
+++ b/applications/gui/src/src_window.hpp
@@ -22,7 +22,7 @@ class Camera;
 
 class SourceWindow : public nanogui::Window {
 	public:
-	SourceWindow(ftl::gui::Screen *screen);
+	explicit SourceWindow(ftl::gui::Screen *screen);
 	~SourceWindow();
 
 	const std::vector<ftl::gui::Camera*> &getCameras();
diff --git a/applications/reconstruct/CMakeLists.txt b/applications/reconstruct/CMakeLists.txt
index ae06aab6d142ba8a2ad3a1e0970905e3a70468e9..dcee7afa0147378ffaabd91263e200f8fe284c98 100644
--- a/applications/reconstruct/CMakeLists.txt
+++ b/applications/reconstruct/CMakeLists.txt
@@ -4,23 +4,19 @@
 
 set(REPSRC
 	src/main.cpp
-	src/voxel_scene.cpp
-	src/scene_rep_hash_sdf.cu
-	src/compactors.cu
-	src/garbage.cu
-	src/integrators.cu
+	#src/voxel_scene.cpp
 	#src/ray_cast_sdf.cu
-	src/splat_render.cu
 	src/camera_util.cu
-	src/voxel_hash.cu
-	src/voxel_hash.cpp
 	#src/ray_cast_sdf.cpp
 	src/registration.cpp
 	#src/virtual_source.cpp
-	src/splat_render.cpp
-	src/dibr.cu
-	src/depth_camera.cu
-	src/depth_camera.cpp
+	#src/splat_render.cpp
+	#src/dibr.cu
+	#src/mls.cu
+	#src/depth_camera.cu
+	#src/depth_camera.cpp
+	src/ilw.cpp
+	src/ilw.cu
 )
 
 add_executable(ftl-reconstruct ${REPSRC})
diff --git a/applications/reconstruct/include/ftl/depth_camera.hpp b/applications/reconstruct/include/ftl/depth_camera.hpp
index cff8ff71f8872a965b2f05071e96f26a4b0a604a..39e037abd90f64e6730577578e09a1f69998b43c 100644
--- a/applications/reconstruct/include/ftl/depth_camera.hpp
+++ b/applications/reconstruct/include/ftl/depth_camera.hpp
@@ -4,8 +4,8 @@
 
 //#include <cutil_inline.h>
 //#include <cutil_math.h>
-#include <vector_types.h>
-#include <cuda_runtime.h>
+//#include <vector_types.h>
+//#include <cuda_runtime.h>
 
 #include <ftl/cuda_matrix_util.hpp>
 #include <ftl/cuda_common.hpp>
diff --git a/applications/reconstruct/include/ftl/depth_camera_params.hpp b/applications/reconstruct/include/ftl/depth_camera_params.hpp
index 4864fccbdc9fa52647687e885f955a12132b0c04..c1c3b8e615577e2d4006ad15cfa975a27c5797fe 100644
--- a/applications/reconstruct/include/ftl/depth_camera_params.hpp
+++ b/applications/reconstruct/include/ftl/depth_camera_params.hpp
@@ -4,12 +4,13 @@
 
 //#include <cutil_inline.h>
 //#include <cutil_math.h>
-#include <vector_types.h>
-#include <cuda_runtime.h>
+//#include <cuda_runtime.h>
 
 #include <ftl/cuda_matrix_util.hpp>
 #include <ftl/rgbd/camera.hpp>
 
+//#include <vector_types.h>
+
 struct __align__(16) DepthCameraParams {
 	float fx;
 	float fy;
diff --git a/applications/reconstruct/include/ftl/registration.hpp b/applications/reconstruct/include/ftl/registration.hpp
index 20f8eac6ab9ad751bc4021d6f5469d6480affb2d..1af4de8a5e3cbc8b64b291e4e115165e197cf28a 100644
--- a/applications/reconstruct/include/ftl/registration.hpp
+++ b/applications/reconstruct/include/ftl/registration.hpp
@@ -6,10 +6,6 @@
 #include <ftl/rgbd.hpp>
 #include <opencv2/opencv.hpp>
 
-#ifdef HAVE_PCL
-
-#include <pcl/common/common_headers.h>
-#include <pcl/point_cloud.h>
 
 namespace ftl {
 namespace registration {
@@ -20,187 +16,7 @@ void from_json(nlohmann::json &json, std::map<std::string, Eigen::Matrix4d> &tra
 bool loadTransformations(const std::string &path, std::map<std::string, Eigen::Matrix4d> &data);
 bool saveTransformations(const std::string &path, std::map<std::string, Eigen::Matrix4d> &data);
 
-/** @brief	Find transformation matrix for transforming clouds_source to clouds_target.
- *			Assumes that corresponding points in clouds_source[i] and clouds_target[i] have same indices.
- */
-Eigen::Matrix4f findTransformation(	std::vector<pcl::PointCloud<pcl::PointXYZ>::Ptr> clouds_source,
-									std::vector<pcl::PointCloud<pcl::PointXYZ>::Ptr> clouds_target);
-
-
-/** @brief Convert chessboard corners found with OpenCV's findChessboardCorners to PCL point cloud. */
-pcl::PointCloud<pcl::PointXYZ>::Ptr cornersToPointCloud(const std::vector<cv::Point2f> &corners, const cv::Mat &disp, const ftl::rgbd::Camera &p);
-
-/** @brief 	Find chessboard corners from image and store them in PCL PointCloud.
- * 	@note	Corners will be drawn in rgb.
- */
-bool findChessboardCorners(cv::Mat &rgb, const cv::Mat &depth, const ftl::rgbd::Camera &p, const cv::Size pattern_size, pcl::PointCloud<pcl::PointXYZ>::Ptr &out, float error_threshold);
-
-/**
- * @brief	Abstract class for registration
- * 
- * Registration procedure 
- * 
- * @todo	Support for multiple features (possibly necessary for other algorithms).
- */
-class Registration : public ftl::Configurable {
-public:
-	explicit Registration(nlohmann::json &config);
-	void addSource(ftl::rgbd::Source* source);
-	size_t getSourcesCount() { return sources_.size(); }
-
-	/**
-	 * @brief	Run registration loop. 
-	 * 
-	 * Loop terminates when processData() returns false.
-	 */
-	virtual void run();
-
-	/**
-	 * @brief	Find registration transformations. run() must be called before
-	 * 			findTransformations(). Transformations are in same order as
-	 * 			sources were added with addSource().
-	 * 
-	 * Transformations are calculated to targetsource if configured. If
-	 * targetsource is not configured or is not found in inputs, which target
-	 * coordinate system is used depends on sub-classes' implementation.
-	 * 
-	 * @param	Output parameter for transformations.
-	 * @return	True if transformations found, otherwise false.
-	 */
-	virtual bool findTransformations(std::vector<Eigen::Matrix4f> &data)=0;
-	
-	/**
-	 * @brief	Overload of findTransformations(). Map keys are source URIs.
-	 */
-	virtual bool findTransformations(std::map<std::string, Eigen::Matrix4f> &data);
-
-protected:
-	ftl::rgbd::Source* getSource(size_t idx);
-
-	bool isTargetSourceSet();
-	bool isTargetSourceFound();
-	bool isTargetSource(ftl::rgbd::Source* source); 
-	bool isTargetSource(size_t idx); 
-	
-	/**
-	 * @brief	Get index for target source. If target source not defined
-	 * 			returns 0. If target source is not found, returns 0.
-	 */
-	size_t getTargetSourceIdx();
-	
-	/**
-	 * @brief	Resets visibility matrix.
-	 */
-	void resetVisibility();
-
-	/**
-	 * @brief	Check if there is enough data to cover all the cameras;
-	 * 			that is visibility is a connected graph. Implemented with BFS.
-	 * 			Implementations of processData() in sub-classes may use this
-	 * 			method (but it is not required).
-	 * 
-	 * @todo	Add support for checking visibility for each different features
-	 * 			found in all images. Also see findFeatures().
-	 */
-	bool connectedVisibility();
-
-	/**
-	 * @brief	Check if there are enough data for all the sources to calculate
-	 * 			transformations. Method may also implement additional processing
-	 * 			for the data. Called once after iteration round. run() stops
-	 * 			when processData() returns false.
-	 */
-	virtual bool processData()=0;
-
-	/**
-	 * @brief	Find features in source, return 
-	 * 
-	 * Called iteratively n times for every input (n * inputs.size()). Exact
-	 * details depend on implementation of processData(). Implementations may
-	 * choose not to set/use visibility information.
-	 * 
-	 * @param	Input source
-	 * @param	Unique index for source provided by run(). Same source will
-	 * 			always have same idx. Implementations may choose to ignore it.
-	 * @return	True/false if feature was found. Used to build adjacency matrix.
-	 */
-	virtual bool findFeatures(ftl::rgbd::Source* source, size_t idx)=0;
-
-	std::vector<std::vector<bool>> visibility_; /*< Adjacency matrix for sources (feature visibility). */
-
-private:
-	std::optional<std::string> target_source_; /*< Reference coordinate system for transformations. */
-	std::vector<ftl::rgbd::Source*> sources_;
-};
-
-/**
- * @brief	Registration using chessboard calibration pattern
- * 
- * Parameters from configuration:
- * 
- * 		patternsize: required
- * 			Chessboard pattern size, inner corners.
- * 
- * 		maxerror: default +inf
- * 			Maximum allowed error value for pattern detection. MSE error between
- * 			estimated plane and points captured from input.
- * 
- * 		delay:	default 500
- * 			Milliseconds between captured images.
- * 
- * 		chain: default false
- * 			Enabling allows camera chaining. In chain mode, pattern is not
- * 			required to be visible in every source. In default (chain: false)
- * 			mode, pattern visibility is required for every source.
- * 
- * 		iter: default 10
- * 			Number of iterations for capturing calibration samples. In
- * 			non-chaining mode, each iteration consists of images where patterns
- * 			were detected on every input. In chaining mode each iteration only
- * 			requires camera visibility to be connected.
- */
-class ChessboardRegistration : public Registration {
-public:
-	explicit ChessboardRegistration(nlohmann::json &config);
-	/** 
-	 * @brief	Creates new ChessboardRegistration or ChessboardRegistrationChain
-	 * 			object depending on chain option in config. User of the method
-	 * 			needs to free the memory.
-	 */
-	static ChessboardRegistration* create(nlohmann::json &config);
-
-	void run() override;
-	bool findTransformations(std::vector<Eigen::Matrix4f> &data) override;
-
-protected:
-	bool findFeatures(ftl::rgbd::Source* source, size_t idx) override;
-	bool processData() override;
-	cv::Size pattern_size_;
-	std::vector<std::vector<std::optional<pcl::PointCloud<pcl::PointXYZ>::Ptr>>> data_;
-	std::vector<Eigen::Matrix4f> T_;
-	float error_threshold_;
-	uint delay_;
-	uint iter_;
-	uint iter_remaining_;
-};
-
-/**
- * @brief Chain registration. Finds visibility and then runs registration.
- */
-class ChessboardRegistrationChain : public ChessboardRegistration {
-public:
-	explicit ChessboardRegistrationChain(nlohmann::json &config);
-	
-	bool findTransformations(std::vector<Eigen::Matrix4f> &data) override;
-
-protected:
-	bool processData() override;
-	std::vector<Eigen::Matrix4f> T_;
-	std::vector<std::vector<std::pair<size_t, size_t>>> edges_;
-};
-
 }
 }
 
-#endif  // HAVE_PCL
 #endif  // _FTL_RECONSTRUCT_REGISTRATION_HPP_
\ No newline at end of file
diff --git a/applications/reconstruct/include/ftl/voxel_hash.hpp b/applications/reconstruct/include/ftl/voxel_hash.hpp
deleted file mode 100644
index 98c2eca90530c309b5d2e46848493634aaaa053c..0000000000000000000000000000000000000000
--- a/applications/reconstruct/include/ftl/voxel_hash.hpp
+++ /dev/null
@@ -1,428 +0,0 @@
-// From: https://github.com/niessner/VoxelHashing/blob/master/DepthSensingCUDA/Source/VoxelUtilHashSDF.h
-
-#pragma once
-
-#ifndef sint
-typedef signed int sint;
-#endif
-
-#ifndef uint
-typedef unsigned int uint;
-#endif 
-
-#ifndef slong 
-typedef signed long slong;
-#endif
-
-#ifndef ulong
-typedef unsigned long ulong;
-#endif
-
-#ifndef uchar
-typedef unsigned char uchar;
-#endif
-
-#ifndef schar
-typedef signed char schar;
-#endif
-
-
-
-
-#include <ftl/cuda_util.hpp>
-
-#include <ftl/cuda_matrix_util.hpp>
-#include <ftl/voxel_hash_params.hpp>
-
-#include <ftl/depth_camera.hpp>
-
-#define SDF_BLOCK_SIZE 8
-#define SDF_BLOCK_SIZE_OLAP 8
-
-#ifndef MINF
-#define MINF __int_as_float(0xff800000)
-#endif
-
-#ifndef PINF
-#define PINF __int_as_float(0x7f800000)
-#endif
-
-extern  __constant__ ftl::voxhash::HashParams c_hashParams;
-extern "C" void updateConstantHashParams(const ftl::voxhash::HashParams& hashParams);
-
-namespace ftl {
-namespace voxhash {
-
-//status flags for hash entries
-static const int LOCK_ENTRY = -1;
-static const int FREE_ENTRY = -2147483648;
-static const int NO_OFFSET = 0;
-
-static const uint kFlagSurface = 0x00000001;
-
-struct __align__(16) HashEntryHead {
-	union {
-	short4 posXYZ;		// hash position (lower left corner of SDFBlock))
-	uint64_t pos;
-	};
-	int offset;	// offset for collisions
-	uint flags;
-};
-
-struct __align__(16) HashEntry 
-{
-	HashEntryHead head;
-	uint voxels[16];  // 512 bits, 1 bit per voxel
-	//uint validity[16];  // Is the voxel valid, 512 bit
-	
-	/*__device__ void operator=(const struct HashEntry& e) {
-		((long long*)this)[0] = ((const long long*)&e)[0];
-		((long long*)this)[1] = ((const long long*)&e)[1];
-		//((int*)this)[4] = ((const int*)&e)[4];
-		((long long*)this)[2] = ((const long long*)&e)[2];
-		((long long*)this)[2] = ((const long long*)&e)[3];
-		((long long*)this)[2] = ((const long long*)&e)[4];
-		((long long*)this)[2] = ((const long long*)&e)[5];
-		((long long*)this)[2] = ((const long long*)&e)[6];
-		((long long*)this)[2] = ((const long long*)&e)[7];
-		((long long*)this)[2] = ((const long long*)&e)[8];
-		((long long*)this)[2] = ((const long long*)&e)[9];
-		((long long*)this)[2] = ((const long long*)&e)[10];
-	}*/
-};
-
-struct __align__(8) Voxel {
-	float	sdf;		//signed distance function
-	uchar3	color;		//color 
-	uchar	weight;		//accumulated sdf weight
-
-	__device__ void operator=(const struct Voxel& v) {
-		((long long*)this)[0] = ((const long long*)&v)[0];
-	}
-
-};
- 
-/**
- * Voxel Hash Table structure and operations. Works on both CPU and GPU with
- * host <-> device transfer included.
- */
-struct HashData {
-
-	///////////////
-	// Host part //
-	///////////////
-
-	__device__ __host__
-	HashData() {
-		//d_heap = NULL;
-		//d_heapCounter = NULL;
-		d_hash = NULL;
-		d_hashDecision = NULL;
-		d_hashDecisionPrefix = NULL;
-		d_hashCompactified = NULL;
-		d_hashCompactifiedCounter = NULL;
-		//d_SDFBlocks = NULL;
-		d_hashBucketMutex = NULL;
-		m_bIsOnGPU = false;
-	}
-
-	/**
-	 * Create all the data structures, either on GPU or system memory.
-	 */
-	__host__ void allocate(const HashParams& params, bool dataOnGPU = true);
-
-	__host__ void updateParams(const HashParams& params);
-
-	__host__ void free();
-
-	/**
-	 * Download entire hash table from GPU to CPU memory.
-	 */
-	__host__ HashData download() const;
-
-	/**
-	 * Upload entire hash table from CPU to GPU memory.
-	 */
-	__host__ HashData upload() const;
-
-	__host__ size_t getAllocatedBlocks() const;
-
-	__host__ size_t getFreeBlocks() const;
-
-	__host__ size_t getCollisionCount() const;
-
-
-
-	/////////////////
-	// Device part //
-	/////////////////
-//#define __CUDACC__
-#ifdef __CUDACC__
-
-	__device__
-	const HashParams& params() const {
-		return c_hashParams;
-	}
-
-	//! see teschner et al. (but with correct prime values)
-	__device__ 
-	uint computeHashPos(const int3& virtualVoxelPos) const { 
-		const int p0 = 73856093;
-		const int p1 = 19349669;
-		const int p2 = 83492791;
-
-		int res = ((virtualVoxelPos.x * p0) ^ (virtualVoxelPos.y * p1) ^ (virtualVoxelPos.z * p2)) % params().m_hashNumBuckets;
-		if (res < 0) res += params().m_hashNumBuckets;
-		return (uint)res;
-	}
-
-	//merges two voxels (v0 the currently stored voxel, v1 is the input voxel)
-	__device__ 
-	void combineVoxel(const Voxel &v0, const Voxel& v1, Voxel &out) const 	{
-
-		//v.color = (10*v0.weight * v0.color + v1.weight * v1.color)/(10*v0.weight + v1.weight);	//give the currently observed color more weight
-		//v.color = (v0.weight * v0.color + v1.weight * v1.color)/(v0.weight + v1.weight);
-		//out.color = 0.5f * (v0.color + v1.color);	//exponential running average 
-		
-
-		float3 c0 = make_float3(v0.color.x, v0.color.y, v0.color.z);
-		float3 c1 = make_float3(v1.color.x, v1.color.y, v1.color.z);
-
-		//float3 res = (c0.x+c0.y+c0.z == 0) ? c1 : 0.5f*c0 + 0.5f*c1;
-		//float3 res = (c0+c1)/2;
-		float3 res = (c0 * (float)v0.weight + c1 * (float)v1.weight) / ((float)v0.weight + (float)v1.weight);
-		//float3 res = c1;
-
-		out.color.x = (uchar)(res.x+0.5f);	out.color.y = (uchar)(res.y+0.5f); out.color.z = (uchar)(res.z+0.5f);
-		
-		// Nick: reduces colour flicker but not ideal..
-		//out.color = v1.color;
-
-		// Option 3 (Nick): Use colour with minimum SDF since it should be closest to surface.
-		// Results in stable but pixelated output
-		//out.color = (v0.weight > 0 && (fabs(v0.sdf) < fabs(v1.sdf))) ? v0.color : v1.color;
-
-		// Option 4 (Nick): Merge colours based upon relative closeness
-		/*float3 c0 = make_float3(v0.color.x, v0.color.y, v0.color.z);
-		float3 c1 = make_float3(v1.color.x, v1.color.y, v1.color.z);
-		float factor = fabs(v0.sdf - v1.sdf) / 0.05f / 2.0f;
-		if (factor > 0.5f) factor = 0.5f;
-		float factor0 = (fabs(v0.sdf) < fabs(v1.sdf)) ? 1.0f - factor : factor;
-		float factor1 = 1.0f - factor0;
-		out.color.x = (v0.weight > 0) ? (uchar)(c0.x * factor0 + c1.x * factor1) : c1.x;
-		out.color.y = (v0.weight > 0) ? (uchar)(c0.y * factor0 + c1.y * factor1) : c1.y;
-		out.color.z = (v0.weight > 0) ? (uchar)(c0.z * factor0 + c1.z * factor1) : c1.z;*/
-
-		out.sdf = (v0.sdf * (float)v0.weight + v1.sdf * (float)v1.weight) / ((float)v0.weight + (float)v1.weight);
-		out.weight = min(params().m_integrationWeightMax, (unsigned int)v0.weight + (unsigned int)v1.weight);
-	}
-
-
-	//! returns the truncation of the SDF for a given distance value
-	__device__ 
-	float getTruncation(float z) const {
-		return params().m_truncation + params().m_truncScale * z;
-	}
-
-
-	__device__ 
-	float3 worldToVirtualVoxelPosFloat(const float3& pos) const	{
-		return pos / params().m_virtualVoxelSize;
-	}
-
-	__device__ 
-	int3 worldToVirtualVoxelPos(const float3& pos) const {
-		//const float3 p = pos*g_VirtualVoxelResolutionScalar;
-		const float3 p = pos / params().m_virtualVoxelSize;
-		return make_int3(p+make_float3(sign(p))*0.5f);
-	}
-
-	__device__ 
-	int3 virtualVoxelPosToSDFBlock(int3 virtualVoxelPos) const {
-		if (virtualVoxelPos.x < 0) virtualVoxelPos.x -= SDF_BLOCK_SIZE_OLAP-1;
-		if (virtualVoxelPos.y < 0) virtualVoxelPos.y -= SDF_BLOCK_SIZE_OLAP-1;
-		if (virtualVoxelPos.z < 0) virtualVoxelPos.z -= SDF_BLOCK_SIZE_OLAP-1;
-
-		return make_int3(
-			virtualVoxelPos.x/SDF_BLOCK_SIZE_OLAP,
-			virtualVoxelPos.y/SDF_BLOCK_SIZE_OLAP,
-			virtualVoxelPos.z/SDF_BLOCK_SIZE_OLAP);
-	}
-
-	// Computes virtual voxel position of corner sample position
-	__device__ 
-	int3 SDFBlockToVirtualVoxelPos(const int3& sdfBlock) const	{
-		return sdfBlock*SDF_BLOCK_SIZE_OLAP;
-	}
-
-	__device__ 
-	float3 virtualVoxelPosToWorld(const int3& pos) const	{
-		return make_float3(pos)*params().m_virtualVoxelSize;
-	}
-
-	__device__ 
-	float3 SDFBlockToWorld(const int3& sdfBlock) const	{
-		return virtualVoxelPosToWorld(SDFBlockToVirtualVoxelPos(sdfBlock));
-	}
-
-	__device__ 
-	int3 worldToSDFBlock(const float3& worldPos) const	{
-		return virtualVoxelPosToSDFBlock(worldToVirtualVoxelPos(worldPos));
-	}
-
-	__device__
-	bool isInBoundingBox(const HashParams &hashParams, const int3& sdfBlock) {
-		// NOTE (Nick): Changed, just assume all voxels are potentially in frustrum
-		//float3 posWorld = virtualVoxelPosToWorld(SDFBlockToVirtualVoxelPos(sdfBlock)) + hashParams.m_virtualVoxelSize * 0.5f * (SDF_BLOCK_SIZE - 1.0f);
-		//return camera.isInCameraFrustumApprox(hashParams.m_rigidTransformInverse, posWorld);
-		return !(hashParams.m_flags & ftl::voxhash::kFlagClipping) || sdfBlock.x > hashParams.m_minBounds.x && sdfBlock.x < hashParams.m_maxBounds.x &&
-			sdfBlock.y > hashParams.m_minBounds.y && sdfBlock.y < hashParams.m_maxBounds.y &&
-			sdfBlock.z > hashParams.m_minBounds.z && sdfBlock.z < hashParams.m_maxBounds.z;
-	}
-
-	//! computes the (local) virtual voxel pos of an index; idx in [0;511]
-	__device__ 
-	uint3 delinearizeVoxelIndex(uint idx) const	{
-		uint x = idx % SDF_BLOCK_SIZE;
-		uint y = (idx % (SDF_BLOCK_SIZE * SDF_BLOCK_SIZE)) / SDF_BLOCK_SIZE;
-		uint z = idx / (SDF_BLOCK_SIZE * SDF_BLOCK_SIZE);	
-		return make_uint3(x,y,z);
-	}
-
-	//! computes the linearized index of a local virtual voxel pos; pos in [0;7]^3
-	__device__ 
-	uint linearizeVoxelPos(const int3& virtualVoxelPos)	const {
-		return  
-			virtualVoxelPos.z * SDF_BLOCK_SIZE * SDF_BLOCK_SIZE +
-			virtualVoxelPos.y * SDF_BLOCK_SIZE +
-			virtualVoxelPos.x;
-	}
-
-	__device__ 
-	int virtualVoxelPosToLocalSDFBlockIndex(const int3& virtualVoxelPos) const	{
-		int3 localVoxelPos = make_int3(
-			virtualVoxelPos.x % SDF_BLOCK_SIZE,
-			virtualVoxelPos.y % SDF_BLOCK_SIZE,
-			virtualVoxelPos.z % SDF_BLOCK_SIZE);
-
-		if (localVoxelPos.x < 0) localVoxelPos.x += SDF_BLOCK_SIZE;
-		if (localVoxelPos.y < 0) localVoxelPos.y += SDF_BLOCK_SIZE;
-		if (localVoxelPos.z < 0) localVoxelPos.z += SDF_BLOCK_SIZE;
-
-		return linearizeVoxelPos(localVoxelPos);
-	}
-
-	__device__ 
-	int worldToLocalSDFBlockIndex(const float3& world) const	{
-		int3 virtualVoxelPos = worldToVirtualVoxelPos(world);
-		return virtualVoxelPosToLocalSDFBlockIndex(virtualVoxelPos);
-	}
-
-
-		//! returns the hash entry for a given worldPos; if there was no hash entry the returned entry will have a ptr with FREE_ENTRY set
-	__device__ 
-	int getHashEntry(const float3& worldPos) const	{
-		//int3 blockID = worldToSDFVirtualVoxelPos(worldPos)/SDF_BLOCK_SIZE;	//position of sdf block
-		int3 blockID = worldToSDFBlock(worldPos);
-		return getHashEntryForSDFBlockPos(blockID);
-	}
-
-
-	__device__ 
-		void deleteHashEntry(uint id) {
-			deleteHashEntry(d_hash[id]);
-	}
-
-	__device__ 
-		void deleteHashEntry(HashEntry& hashEntry) {
-			hashEntry.head.pos = 0;
-			hashEntry.head.offset = FREE_ENTRY;
-			for (int i=0; i<16; ++i) hashEntry.voxels[i] = 0;
-	}
-
-	__device__ 
-		bool voxelExists(const float3& worldPos) const	{
-			int hashEntry = getHashEntry(worldPos);
-			return (hashEntry != -1);
-	}
-
-	__device__  
-	void deleteVoxel(Voxel& v) const {
-		v.color = make_uchar3(0,0,0);
-		v.weight = 0;
-		v.sdf = 0.0f;
-	}
-
-
-	__device__ 
-	bool getVoxel(const float3& worldPos) const	{
-		int hashEntry = getHashEntry(worldPos);
-		if (hashEntry == -1) {
-			return false;		
-		} else {
-			int3 virtualVoxelPos = worldToVirtualVoxelPos(worldPos);
-			int ix = virtualVoxelPosToLocalSDFBlockIndex(virtualVoxelPos);
-			return d_hash[hashEntry].voxels[ix/32] & (0x1 << (ix % 32));
-		}
-	}
-
-	__device__ 
-	bool getVoxel(const int3& virtualVoxelPos) const	{
-		int hashEntry = getHashEntryForSDFBlockPos(virtualVoxelPosToSDFBlock(virtualVoxelPos));
-		if (hashEntry == -1) {
-			return false;		
-		} else {
-			int ix = virtualVoxelPosToLocalSDFBlockIndex(virtualVoxelPos);
-			return d_hash[hashEntry].voxels[ix >> 5] & (0x1 << (ix & 0x1F));
-		}
-	}
-	
-	/*__device__ 
-	void setVoxel(const int3& virtualVoxelPos, bool voxelInput) const {
-		int hashEntry = getHashEntryForSDFBlockPos(virtualVoxelPosToSDFBlock(virtualVoxelPos));
-		if (hashEntry == -1) {
-			d_SDFBlocks[hashEntry.ptr + virtualVoxelPosToLocalSDFBlockIndex(virtualVoxelPos)] = voxelInput;
-			int ix = virtualVoxelPosToLocalSDFBlockIndex(virtualVoxelPos);
-			d_hash[hashEntry].voxels[ix >> 5] |= (0x1 << (ix & 0x1F));
-		}
-	}*/
-
-	//! returns the hash entry for a given sdf block id; if there was no hash entry the returned entry will have a ptr with FREE_ENTRY set
-	__device__ 
-	int getHashEntryForSDFBlockPos(const int3& sdfBlock) const;
-
-	//for histogram (no collision traversal)
-	__device__ 
-	unsigned int getNumHashEntriesPerBucket(unsigned int bucketID);
-
-	//for histogram (collisions traversal only)
-	__device__ 
-	unsigned int getNumHashLinkedList(unsigned int bucketID);
-
-
-	//pos in SDF block coordinates
-	__device__
-	void allocBlock(const int3& pos);
-
-	//!inserts a hash entry without allocating any memory: used by streaming: TODO MATTHIAS check the atomics in this function
-	__device__
-	bool insertHashEntry(HashEntry entry);
-
-	//! deletes a hash entry position for a given sdfBlock index (returns true uppon successful deletion; otherwise returns false)
-	__device__
-	bool deleteHashEntryElement(const int3& sdfBlock);
-
-#endif	//CUDACC
-
-	int*		d_hashDecision;				//
-	int*		d_hashDecisionPrefix;		//
-	HashEntry*	d_hash;						//hash that stores pointers to sdf blocks
-	HashEntry**	d_hashCompactified;			//same as before except that only valid pointers are there
-	int*		d_hashCompactifiedCounter;	//atomic counter to add compactified entries atomically 
-	int*		d_hashBucketMutex;			//binary flag per hash bucket; used for allocation to atomically lock a bucket
-
-	bool		m_bIsOnGPU;					//the class be be used on both cpu and gpu
-};
-
-}  // namespace voxhash
-}  // namespace ftl
diff --git a/applications/reconstruct/include/ftl/voxel_hash_params.hpp b/applications/reconstruct/include/ftl/voxel_hash_params.hpp
index 480e16d478a7a3c82d046f6de464d7bb20c04f64..ac5fcaa726febaff0b56426b6d481d48bae3e421 100644
--- a/applications/reconstruct/include/ftl/voxel_hash_params.hpp
+++ b/applications/reconstruct/include/ftl/voxel_hash_params.hpp
@@ -4,8 +4,8 @@
 
 //#include <cutil_inline.h>
 //#include <cutil_math.h>
-#include <vector_types.h>
-#include <cuda_runtime.h>
+//#include <vector_types.h>
+//#include <cuda_runtime.h>
 
 #include <ftl/cuda_matrix_util.hpp>
 
@@ -28,8 +28,8 @@ struct __align__(16) HashParams {
 	unsigned int	m_integrationWeightSample;
 	unsigned int	m_integrationWeightMax;
 
-	int3 m_minBounds;
-	int3 m_maxBounds;
+	float3 m_minBounds;
+	float3 m_maxBounds;
 	float m_spatialSmoothing;
 	float m_colourSmoothing;
 	float m_confidenceThresh;
diff --git a/applications/reconstruct/include/ftl/voxel_scene.hpp b/applications/reconstruct/include/ftl/voxel_scene.hpp
index d594d479f1f35ffc9cece087b867be2f4b781ee0..4ea2d5eb3d98b0a3baac018f2c9ace9b5ad7a6f0 100644
--- a/applications/reconstruct/include/ftl/voxel_scene.hpp
+++ b/applications/reconstruct/include/ftl/voxel_scene.hpp
@@ -2,7 +2,7 @@
 
 #pragma once
 
-#include <cuda_runtime.h>
+//#include <cuda_runtime.h>
 
 #include <ftl/cuda_common.hpp>
 #include <ftl/rgbd/source.hpp>
@@ -10,6 +10,7 @@
 #include <ftl/matrix_conversion.hpp>
 #include <ftl/voxel_hash.hpp>
 #include <ftl/depth_camera.hpp>
+#include <ftl/rgbd/group.hpp>
 #include <unordered_set>
 
 namespace ftl {
@@ -34,6 +35,8 @@ class SceneRep : public ftl::Configurable {
 	 */
 	int upload();
 
+	int upload(ftl::rgbd::FrameSet &);
+
 	/**
 	 * Merge all camera frames into the voxel hash datastructure.
 	 */
diff --git a/applications/reconstruct/src/compactors.cu b/applications/reconstruct/src/compactors.cu
deleted file mode 100644
index b7cdd5028f0f5ec78de47d8bf6f9099c5448a494..0000000000000000000000000000000000000000
--- a/applications/reconstruct/src/compactors.cu
+++ /dev/null
@@ -1,236 +0,0 @@
-#include "compactors.hpp"
-
-using ftl::voxhash::HashData;
-using ftl::voxhash::HashParams;
-using ftl::voxhash::Voxel;
-using ftl::voxhash::HashEntry;
-using ftl::voxhash::FREE_ENTRY;
-
-#define COMPACTIFY_HASH_THREADS_PER_BLOCK 256
-//#define COMPACTIFY_HASH_SIMPLE
-
-
-/*__global__ void fillDecisionArrayKernel(HashData hashData, DepthCameraData depthCameraData) 
-{
-	const HashParams& hashParams = c_hashParams;
-	const unsigned int idx = blockIdx.x*blockDim.x + threadIdx.x;
-
-	if (idx < hashParams.m_hashNumBuckets * HASH_BUCKET_SIZE) {
-		hashData.d_hashDecision[idx] = 0;
-		if (hashData.d_hash[idx].ptr != FREE_ENTRY) {
-			if (hashData.isSDFBlockInCameraFrustumApprox(hashData.d_hash[idx].pos)) {
-				hashData.d_hashDecision[idx] = 1;	//yes
-			}
-		}
-	}
-}*/
-
-/*extern "C" void fillDecisionArrayCUDA(HashData& hashData, const HashParams& hashParams, const DepthCameraData& depthCameraData)
-{
-	const dim3 gridSize((HASH_BUCKET_SIZE * hashParams.m_hashNumBuckets + (T_PER_BLOCK*T_PER_BLOCK) - 1)/(T_PER_BLOCK*T_PER_BLOCK), 1);
-	const dim3 blockSize((T_PER_BLOCK*T_PER_BLOCK), 1);
-
-	fillDecisionArrayKernel<<<gridSize, blockSize>>>(hashData, depthCameraData);
-
-#ifdef _DEBUG
-	cudaSafeCall(cudaDeviceSynchronize());
-	//cutilCheckMsg(__FUNCTION__);
-#endif
-
-}*/
-
-/*__global__ void compactifyHashKernel(HashData hashData) 
-{
-	const HashParams& hashParams = c_hashParams;
-	const unsigned int idx = blockIdx.x*blockDim.x + threadIdx.x;
-	if (idx < hashParams.m_hashNumBuckets * HASH_BUCKET_SIZE) {
-		if (hashData.d_hashDecision[idx] == 1) {
-			hashData.d_hashCompactified[hashData.d_hashDecisionPrefix[idx]-1] = hashData.d_hash[idx];
-		}
-	}
-}*/
-
-/*extern "C" void compactifyHashCUDA(HashData& hashData, const HashParams& hashParams) 
-{
-	const dim3 gridSize((HASH_BUCKET_SIZE * hashParams.m_hashNumBuckets + (T_PER_BLOCK*T_PER_BLOCK) - 1)/(T_PER_BLOCK*T_PER_BLOCK), 1);
-	const dim3 blockSize((T_PER_BLOCK*T_PER_BLOCK), 1);
-
-	compactifyHashKernel<<<gridSize, blockSize>>>(hashData);
-
-#ifdef _DEBUG
-	cudaSafeCall(cudaDeviceSynchronize());
-	//cutilCheckMsg(__FUNCTION__);
-#endif
-}*/
-
-/*__global__ void compactifyVisibleKernel(HashData hashData, HashParams hashParams, DepthCameraParams camera)
-{
-	//const HashParams& hashParams = c_hashParams;
-	const unsigned int idx = blockIdx.x*blockDim.x + threadIdx.x;
-#ifdef COMPACTIFY_HASH_SIMPLE
-	if (idx < hashParams.m_hashNumBuckets) {
-		if (hashData.d_hash[idx].ptr != FREE_ENTRY) {
-			if (hashData.isSDFBlockInCameraFrustumApprox(hashParams, camera, hashData.d_hash[idx].pos))
-			{
-				int addr = atomicAdd(hashData.d_hashCompactifiedCounter, 1);
-				hashData.d_hashCompactified[addr] = hashData.d_hash[idx];
-			}
-		}
-	}
-#else	
-	__shared__ int localCounter;
-	if (threadIdx.x == 0) localCounter = 0;
-	__syncthreads();
-
-	int addrLocal = -1;
-	if (idx < hashParams.m_hashNumBuckets) {
-		if (hashData.d_hash[idx].ptr != FREE_ENTRY) {
-			if (hashData.isSDFBlockInCameraFrustumApprox(hashParams, camera, hashData.d_hash[idx].pos))
-			{
-				addrLocal = atomicAdd(&localCounter, 1);
-			}
-		}
-	}
-
-	__syncthreads();
-
-	__shared__ int addrGlobal;
-	if (threadIdx.x == 0 && localCounter > 0) {
-		addrGlobal = atomicAdd(hashData.d_hashCompactifiedCounter, localCounter);
-	}
-	__syncthreads();
-
-	if (addrLocal != -1) {
-		const unsigned int addr = addrGlobal + addrLocal;
-		hashData.d_hashCompactified[addr] = hashData.d_hash[idx];
-	}
-#endif
-}
-
-void ftl::cuda::compactifyVisible(HashData& hashData, const HashParams& hashParams, const DepthCameraParams &camera, cudaStream_t stream) {
-	const unsigned int threadsPerBlock = COMPACTIFY_HASH_THREADS_PER_BLOCK;
-	const dim3 gridSize((hashParams.m_hashNumBuckets + threadsPerBlock - 1) / threadsPerBlock, 1);
-	const dim3 blockSize(threadsPerBlock, 1);
-
-	cudaSafeCall(cudaMemsetAsync(hashData.d_hashCompactifiedCounter, 0, sizeof(int),stream));
-	compactifyVisibleKernel << <gridSize, blockSize, 0, stream >> >(hashData, hashParams, camera);
-	//unsigned int res = 0;
-	//cudaSafeCall(cudaMemcpyAsync(&res, hashData.d_hashCompactifiedCounter, sizeof(unsigned int), cudaMemcpyDeviceToHost, stream));
-
-#ifdef _DEBUG
-	cudaSafeCall(cudaDeviceSynchronize());
-	//cutilCheckMsg(__FUNCTION__);
-#endif
-	//return res;
-}*/
-
-__global__ void compactifyAllocatedKernel(HashData hashData)
-{
-	const HashParams& hashParams = c_hashParams;
-	const unsigned int idx = blockIdx.x*blockDim.x + threadIdx.x;
-#ifdef COMPACTIFY_HASH_SIMPLE
-	if (idx < hashParams.m_hashNumBuckets) {
-		if (hashData.d_hash[idx].head.offset != FREE_ENTRY) {
-			int addr = atomicAdd(hashData.d_hashCompactifiedCounter, 1);
-			hashData.d_hashCompactified[addr] = &hashData.d_hash[idx];
-		}
-	}
-#else	
-	__shared__ int localCounter;
-	if (threadIdx.x == 0) localCounter = 0;
-	__syncthreads();
-
-	int addrLocal = -1;
-	if (idx < hashParams.m_hashNumBuckets) {
-		if (hashData.d_hash[idx].head.offset != FREE_ENTRY) {
-			addrLocal = atomicAdd(&localCounter, 1);
-		}
-	}
-
-	__syncthreads();
-
-	__shared__ int addrGlobal;
-	if (threadIdx.x == 0 && localCounter > 0) {
-		addrGlobal = atomicAdd(hashData.d_hashCompactifiedCounter, localCounter);
-	}
-	__syncthreads();
-
-	if (addrLocal != -1) {
-		const unsigned int addr = addrGlobal + addrLocal;
-		hashData.d_hashCompactified[addr] = &hashData.d_hash[idx];
-	}
-#endif
-}
-
-void ftl::cuda::compactifyAllocated(HashData& hashData, const HashParams& hashParams, cudaStream_t stream) {
-	const unsigned int threadsPerBlock = COMPACTIFY_HASH_THREADS_PER_BLOCK;
-	const dim3 gridSize((hashParams.m_hashNumBuckets + threadsPerBlock - 1) / threadsPerBlock, 1);
-	const dim3 blockSize(threadsPerBlock, 1);
-
-	cudaSafeCall(cudaMemsetAsync(hashData.d_hashCompactifiedCounter, 0, sizeof(int), stream));
-	compactifyAllocatedKernel << <gridSize, blockSize, 0, stream >> >(hashData);
-	//unsigned int res = 0;
-	//cudaSafeCall(cudaMemcpyAsync(&res, hashData.d_hashCompactifiedCounter, sizeof(unsigned int), cudaMemcpyDeviceToHost, stream));
-
-#ifdef _DEBUG
-	cudaSafeCall(cudaDeviceSynchronize());
-	//cutilCheckMsg(__FUNCTION__);
-#endif
-	//return res;
-}
-
-
-__global__ void compactifyOccupiedKernel(HashData hashData)
-{
-	const HashParams& hashParams = c_hashParams;
-	const unsigned int idx = blockIdx.x*blockDim.x + threadIdx.x;
-#ifdef COMPACTIFY_HASH_SIMPLE
-	if (idx < hashParams.m_hashNumBuckets) {
-		if (hashData.d_hash[idx].head.offset != FREE_ENTRY && hashData.d_hash[idx].head.flags & ftl::voxhash::kFlagSurface) {
-			int addr = atomicAdd(hashData.d_hashCompactifiedCounter, 1);
-			hashData.d_hashCompactified[addr] = &hashData.d_hash[idx];
-		}
-	}
-#else	
-	__shared__ int localCounter;
-	if (threadIdx.x == 0) localCounter = 0;
-	__syncthreads();
-
-	int addrLocal = -1;
-	if (idx < hashParams.m_hashNumBuckets) {
-		if (hashData.d_hash[idx].head.offset != FREE_ENTRY && (hashData.d_hash[idx].head.flags & ftl::voxhash::kFlagSurface)) {  // TODO:(Nick) Check voxels for all 0 or all 1
-			addrLocal = atomicAdd(&localCounter, 1);
-		}
-	}
-
-	__syncthreads();
-
-	__shared__ int addrGlobal;
-	if (threadIdx.x == 0 && localCounter > 0) {
-		addrGlobal = atomicAdd(hashData.d_hashCompactifiedCounter, localCounter);
-	}
-	__syncthreads();
-
-	if (addrLocal != -1) {
-		const unsigned int addr = addrGlobal + addrLocal;
-		hashData.d_hashCompactified[addr] = &hashData.d_hash[idx];
-	}
-#endif
-}
-
-void ftl::cuda::compactifyOccupied(HashData& hashData, const HashParams& hashParams, cudaStream_t stream) {
-	const unsigned int threadsPerBlock = COMPACTIFY_HASH_THREADS_PER_BLOCK;
-	const dim3 gridSize((hashParams.m_hashNumBuckets + threadsPerBlock - 1) / threadsPerBlock, 1);
-	const dim3 blockSize(threadsPerBlock, 1);
-
-	cudaSafeCall(cudaMemsetAsync(hashData.d_hashCompactifiedCounter, 0, sizeof(int), stream));
-	compactifyAllocatedKernel << <gridSize, blockSize, 0, stream >> >(hashData);
-	//unsigned int res = 0;
-	//cudaSafeCall(cudaMemcpyAsync(&res, hashData.d_hashCompactifiedCounter, sizeof(unsigned int), cudaMemcpyDeviceToHost, stream));
-
-#ifdef _DEBUG
-	cudaSafeCall(cudaDeviceSynchronize());
-	//cutilCheckMsg(__FUNCTION__);
-#endif
-	//return res;
-}
diff --git a/applications/reconstruct/src/compactors.hpp b/applications/reconstruct/src/compactors.hpp
deleted file mode 100644
index 6c61985eea8448a078b8abe3e821992519c9425f..0000000000000000000000000000000000000000
--- a/applications/reconstruct/src/compactors.hpp
+++ /dev/null
@@ -1,21 +0,0 @@
-#ifndef _FTL_RECONSTRUCT_COMPACTORS_HPP_
-#define _FTL_RECONSTRUCT_COMPACTORS_HPP_
-
-#include <ftl/voxel_hash.hpp>
-
-namespace ftl {
-namespace cuda {
-
-// Compact visible
-//void compactifyVisible(ftl::voxhash::HashData& hashData, const ftl::voxhash::HashParams& hashParams, const DepthCameraParams &camera, cudaStream_t);
-
-// Compact allocated
-void compactifyAllocated(ftl::voxhash::HashData& hashData, const ftl::voxhash::HashParams& hashParams, cudaStream_t);
-
-// Compact visible surfaces
-void compactifyOccupied(ftl::voxhash::HashData& hashData, const ftl::voxhash::HashParams& hashParams, cudaStream_t stream);
-
-}
-}
-
-#endif  // _FTL_RECONSTRUCT_COMPACTORS_HPP_
diff --git a/applications/reconstruct/src/depth_camera.cu b/applications/reconstruct/src/depth_camera.cu
index 9a36ea452225c1a174d0ba6ced0baf98471688eb..7a322eaf733230772e72c50722e849335bf7e891 100644
--- a/applications/reconstruct/src/depth_camera.cu
+++ b/applications/reconstruct/src/depth_camera.cu
@@ -47,6 +47,21 @@ void ftl::cuda::clear_depth(const ftl::cuda::TextureObject<int> &depth, cudaStre
 	clear_depth_kernel<<<clear_gridSize, clear_blockSize, 0, stream>>>(depth);
 }
 
+__global__ void clear_to_zero_kernel(ftl::cuda::TextureObject<float> depth) {
+	const unsigned int x = blockIdx.x*blockDim.x + threadIdx.x;
+	const unsigned int y = blockIdx.y*blockDim.y + threadIdx.y;
+
+	if (x < depth.width() && y < depth.height()) {
+		depth(x,y) = 0.0f; //PINF;
+	}
+}
+
+void ftl::cuda::clear_to_zero(const ftl::cuda::TextureObject<float> &depth, cudaStream_t stream) {
+	const dim3 clear_gridSize((depth.width() + T_PER_BLOCK - 1)/T_PER_BLOCK, (depth.height() + T_PER_BLOCK - 1)/T_PER_BLOCK);
+	const dim3 clear_blockSize(T_PER_BLOCK, T_PER_BLOCK);
+	clear_to_zero_kernel<<<clear_gridSize, clear_blockSize, 0, stream>>>(depth);
+}
+
 __global__ void clear_points_kernel(ftl::cuda::TextureObject<float4> depth) {
 	const unsigned int x = blockIdx.x*blockDim.x + threadIdx.x;
 	const unsigned int y = blockIdx.y*blockDim.y + threadIdx.y;
@@ -79,6 +94,21 @@ void ftl::cuda::clear_colour(const ftl::cuda::TextureObject<uchar4> &depth, cuda
 	clear_colour_kernel<<<clear_gridSize, clear_blockSize, 0, stream>>>(depth);
 }
 
+__global__ void clear_colour_kernel(ftl::cuda::TextureObject<float4> depth) {
+	const unsigned int x = blockIdx.x*blockDim.x + threadIdx.x;
+	const unsigned int y = blockIdx.y*blockDim.y + threadIdx.y;
+
+	if (x < depth.width() && y < depth.height()) {
+		depth(x,y) = make_float4(0.0f);
+	}
+}
+
+void ftl::cuda::clear_colour(const ftl::cuda::TextureObject<float4> &depth, cudaStream_t stream) {
+	const dim3 clear_gridSize((depth.width() + T_PER_BLOCK - 1)/T_PER_BLOCK, (depth.height() + T_PER_BLOCK - 1)/T_PER_BLOCK);
+	const dim3 clear_blockSize(T_PER_BLOCK, T_PER_BLOCK);
+	clear_colour_kernel<<<clear_gridSize, clear_blockSize, 0, stream>>>(depth);
+}
+
 // ===== Type convert =====
 
 template <typename A, typename B>
@@ -103,331 +133,6 @@ void ftl::cuda::int_to_float(const ftl::cuda::TextureObject<int> &in, ftl::cuda:
 	convert_kernel<int,float><<<gridSize, blockSize, 0, stream>>>(in, out, scale);
 }
 
-/// ===== MLS Smooth
-
-// TODO:(Nick) Put this in a common location (used in integrators.cu)
-extern __device__ float spatialWeighting(float r);
-extern __device__ float spatialWeighting(float r, float h);
-
-/*
- * Kim, K., Chalidabhongse, T. H., Harwood, D., & Davis, L. (2005).
- * Real-time foreground-background segmentation using codebook model.
- * Real-Time Imaging. https://doi.org/10.1016/j.rti.2004.12.004
- */
- __device__ float colordiffFloat(const uchar4 &pa, const uchar4 &pb) {
-	const float x_2 = pb.x * pb.x + pb.y * pb.y + pb.z * pb.z;
-	const float v_2 = pa.x * pa.x + pa.y * pa.y + pa.z * pa.z;
-	const float xv_2 = pow(pb.x * pa.x + pb.y * pa.y + pb.z * pa.z, 2);
-	const float p_2 = xv_2 / v_2;
-	return sqrt(x_2 - p_2);
-}
-
-__device__ float colordiffFloat2(const uchar4 &pa, const uchar4 &pb) {
-	float3 delta = make_float3((float)pa.x - (float)pb.x, (float)pa.y - (float)pb.y, (float)pa.z - (float)pb.z);
-	return length(delta);
-}
-
-/*
- * Colour weighting as suggested in:
- * C. Kuster et al. Spatio-Temporal Geometry Fusion for Multiple Hybrid Cameras using Moving Least Squares Surfaces. 2014.
- * c = colour distance
- */
- __device__ float colourWeighting(float c) {
-	const float h = c_hashParams.m_colourSmoothing;
-	if (c >= h) return 0.0f;
-	float ch = c / h;
-	ch = 1.0f - ch*ch;
-	return ch*ch*ch*ch;
-}
-
-#define WINDOW_RADIUS 5
-
-__device__ float mlsCamera(int cam, const float3 &mPos, uchar4 c1, float3 &wpos) {
-	const ftl::voxhash::DepthCameraCUDA &camera = c_cameras[cam];
-
-	const float3 pf = camera.poseInverse * mPos;
-	float3 pos = make_float3(0.0f, 0.0f, 0.0f);
-	const uint2 screenPos = make_uint2(camera.params.cameraToKinectScreenInt(pf));
-	float weights = 0.0f;
-
-	//#pragma unroll
-	for (int v=-WINDOW_RADIUS; v<=WINDOW_RADIUS; ++v) {
-		for (int u=-WINDOW_RADIUS; u<=WINDOW_RADIUS; ++u) {
-			//if (screenPos.x+u < width && screenPos.y+v < height) {	//on screen
-				float depth = tex2D<float>(camera.depth, screenPos.x+u, screenPos.y+v);
-				const float3 camPos = camera.params.kinectDepthToSkeleton(screenPos.x+u, screenPos.y+v, depth);
-				float weight = spatialWeighting(length(pf - camPos));
-
-				if (weight > 0.0f) {
-					uchar4 c2 = tex2D<uchar4>(camera.colour, screenPos.x+u, screenPos.y+v);
-					weight *= colourWeighting(colordiffFloat2(c1,c2));
-
-					if (weight > 0.0f) {
-						wpos += weight* (camera.pose * camPos);
-						weights += weight;
-					}
-				}			
-			//}
-		}
-	}
-
-	//wpos += (camera.pose * pos);
-
-	return weights;
-}
-
-__device__ float mlsCameraNoColour(int cam, const float3 &mPos, uchar4 c1, const float4 &norm, float3 &wpos, float h) {
-	const ftl::voxhash::DepthCameraCUDA &camera = c_cameras[cam];
-
-	const float3 pf = camera.poseInverse * mPos;
-	float3 pos = make_float3(0.0f, 0.0f, 0.0f);
-	const uint2 screenPos = make_uint2(camera.params.cameraToKinectScreenInt(pf));
-	float weights = 0.0f;
-
-	//#pragma unroll
-	for (int v=-WINDOW_RADIUS; v<=WINDOW_RADIUS; ++v) {
-		for (int u=-WINDOW_RADIUS; u<=WINDOW_RADIUS; ++u) {
-			//if (screenPos.x+u < width && screenPos.y+v < height) {	//on creen
-				float depth = tex2D<float>(camera.depth, screenPos.x+u, screenPos.y+v);
-				const float3 camPos = camera.params.kinectDepthToSkeleton(screenPos.x+u, screenPos.y+v, depth);
-
-				// TODO:(Nick) dot product of normals < 0 means the point
-				// should be ignored with a weight of 0 since it is facing the wrong direction
-				// May be good to simply weight using the dot product to give
-				// a stronger weight to those whose normals are closer
-
-				float weight = spatialWeighting(length(pf - camPos), h);
-
-				if (weight > 0.0f) {
-					float4 n2 = tex2D<float4>(camera.normal, screenPos.x+u, screenPos.y+v);
-					if (dot(make_float3(norm), make_float3(n2)) > 0.0f) {
-
-						uchar4 c2 = tex2D<uchar4>(camera.colour, screenPos.x+u, screenPos.y+v);
-
-						if (colourWeighting(colordiffFloat2(c1,c2)) > 0.0f) {
-							pos += weight*camPos; // (camera.pose * camPos);
-							weights += weight;
-						}
-					}
-				}			
-			//}
-		}
-	}
-
-	if (weights > 0.0f) wpos += (camera.pose * (pos / weights)) * weights;
-
-	return weights;
-}
-
-__device__ float mlsCameraBest(int cam, const float3 &mPos, uchar4 c1, float3 &wpos) {
-	const ftl::voxhash::DepthCameraCUDA &camera = c_cameras[cam];
-
-	const float3 pf = camera.poseInverse * mPos;
-	float3 pos = make_float3(0.0f, 0.0f, 0.0f);
-	const uint2 screenPos = make_uint2(camera.params.cameraToKinectScreenInt(pf));
-	float weights = 0.0f;
-
-	//#pragma unroll
-	for (int v=-WINDOW_RADIUS; v<=WINDOW_RADIUS; ++v) {
-		for (int u=-WINDOW_RADIUS; u<=WINDOW_RADIUS; ++u) {
-			//if (screenPos.x+u < width && screenPos.y+v < height) {	//on screen
-				float depth = tex2D<float>(camera.depth, screenPos.x+u, screenPos.y+v);
-				const float3 camPos = camera.params.kinectDepthToSkeleton(screenPos.x+u, screenPos.y+v, depth);
-				float weight = spatialWeighting(length(pf - camPos));
-
-				if (weight > 0.0f) {
-					uchar4 c2 = tex2D<uchar4>(camera.colour, screenPos.x+u, screenPos.y+v);
-					weight *= colourWeighting(colordiffFloat2(c1,c2));
-
-					if (weight > weights) {
-						pos = weight* (camera.pose * camPos);
-						weights = weight;
-					}
-				}			
-			//}
-		}
-	}
-
-	wpos += pos;
-	//wpos += (camera.pose * pos);
-
-	return weights;
-}
-
-__device__ float mlsCameraPoint(int cam, const float3 &mPos, uchar4 c1, float3 &wpos) {
-	const ftl::voxhash::DepthCameraCUDA &camera = c_cameras[cam];
-
-	const float3 pf = camera.poseInverse * mPos;
-	float3 pos = make_float3(0.0f, 0.0f, 0.0f);
-	const uint2 screenPos = make_uint2(camera.params.cameraToKinectScreenInt(pf));
-	float weights = 0.0f;
-
-
-	//float depth = tex2D<float>(camera.depth, screenPos.x, screenPos.y);
-	const float3 worldPos = make_float3(tex2D<float4>(camera.points, screenPos.x, screenPos.y));
-	if (worldPos.z == MINF) return 0.0f;
-
-	float weight = spatialWeighting(length(mPos - worldPos));
-
-	if (weight > 0.0f) {
-		wpos += weight* (worldPos);
-		weights += weight;
-	}
-
-	return weights;
-}
-
-__global__ void mls_smooth_kernel(ftl::cuda::TextureObject<float4> output, HashParams hashParams, int numcams, int cam) {
-	const unsigned int x = blockIdx.x*blockDim.x + threadIdx.x;
-	const unsigned int y = blockIdx.y*blockDim.y + threadIdx.y;
-
-	const int width = output.width();
-	const int height = output.height();
-
-	const DepthCameraCUDA &mainCamera = c_cameras[cam];
-
-	if (x < width && y < height) {
-
-		const float depth = tex2D<float>(mainCamera.depth, x, y);
-		const uchar4 c1 = tex2D<uchar4>(mainCamera.colour, x, y);
-		const float4 norm = tex2D<float4>(mainCamera.normal, x, y);
-		//if (x == 400 && y == 200) printf("NORMX: %f\n", norm.x);
-
-		float3 wpos = make_float3(0.0f);
-		float3 wnorm = make_float3(0.0f);
-		float weights = 0.0f;
-
-		if (depth >= mainCamera.params.m_sensorDepthWorldMin && depth <= mainCamera.params.m_sensorDepthWorldMax) {
-			float3 mPos = mainCamera.pose * mainCamera.params.kinectDepthToSkeleton(x, y, depth);
-
-			if ((!(hashParams.m_flags & ftl::voxhash::kFlagClipping)) || (mPos.x > hashParams.m_minBounds.x && mPos.x < hashParams.m_maxBounds.x &&
-					mPos.y > hashParams.m_minBounds.y && mPos.y < hashParams.m_maxBounds.y &&
-					mPos.z > hashParams.m_minBounds.z && mPos.z < hashParams.m_maxBounds.z)) {
-
-				if (hashParams.m_flags & ftl::voxhash::kFlagMLS) {
-					for (uint cam2=0; cam2<numcams; ++cam2) {
-						//if (cam2 == cam) weights += mlsCameraNoColour(cam2, mPos, c1, wpos, c_hashParams.m_spatialSmoothing*0.1f); //weights += 0.5*mlsCamera(cam2, mPos, c1, wpos);
-						weights += mlsCameraNoColour(cam2, mPos, c1, norm, wpos, c_hashParams.m_spatialSmoothing); //*((cam == cam2)? 0.1f : 5.0f));
-
-						// Previous approach
-						//if (cam2 == cam) continue;
-						//weights += mlsCameraBest(cam2, mPos, c1, wpos);
-					}
-					wpos /= weights;
-				} else {
-					weights = 1000.0f;
-					wpos = mPos;
-				} 
-
-				//output(x,y) = (weights >= hashParams.m_confidenceThresh) ? make_float4(wpos, 0.0f) : make_float4(MINF,MINF,MINF,MINF);
-
-				if (weights >= hashParams.m_confidenceThresh) output(x,y) = make_float4(wpos, 0.0f);
-
-				//const uint2 screenPos = make_uint2(mainCamera.params.cameraToKinectScreenInt(mainCamera.poseInverse * wpos));
-				//if (screenPos.x < output.width() && screenPos.y < output.height()) {
-				//	output(screenPos.x,screenPos.y) = (weights >= hashParams.m_confidenceThresh) ? make_float4(wpos, 0.0f) : make_float4(MINF,MINF,MINF,MINF);
-				//}
-			}
-		}
-	}
-}
-
-void ftl::cuda::mls_smooth(TextureObject<float4> &output, const HashParams &hashParams, int numcams, int cam, cudaStream_t stream) {
-	const dim3 gridSize((output.width() + T_PER_BLOCK - 1)/T_PER_BLOCK, (output.height() + T_PER_BLOCK - 1)/T_PER_BLOCK);
-	const dim3 blockSize(T_PER_BLOCK, T_PER_BLOCK);
-
-	mls_smooth_kernel<<<gridSize, blockSize, 0, stream>>>(output, hashParams, numcams, cam);
-
-#ifdef _DEBUG
-	cudaSafeCall(cudaDeviceSynchronize());
-#endif
-}
-
-#define RESAMPLE_RADIUS 7
-
-__global__ void mls_resample_kernel(ftl::cuda::TextureObject<int> depthin, ftl::cuda::TextureObject<uchar4> colourin, ftl::cuda::TextureObject<float> depthout, HashParams hashParams, int numcams, SplatParams params) {
-	const unsigned int x = blockIdx.x*blockDim.x + threadIdx.x;
-	const unsigned int y = blockIdx.y*blockDim.y + threadIdx.y;
-
-	const int width = depthin.width();
-	const int height = depthin.height();
-
-	if (x < width && y < height) {
-
-		//const int depth = depthin.tex2D((int)x, (int)y);
-		//if (depth != 0x7FFFFFFF) {
-		//	depthout(x,y) = (float)depth / 1000.0f;
-		//	return;
-		//}
-
-		struct map_t {
-			int d;
-			int quad;
-		};
-
-		map_t mappings[5];
-		int mapidx = 0;
-
-		for (int v=-RESAMPLE_RADIUS; v<=RESAMPLE_RADIUS; ++v) {
-			for (int u=-RESAMPLE_RADIUS; u<=RESAMPLE_RADIUS; ++u) {
-
-				const int depth = depthin.tex2D((int)x+u, (int)y+v);
-				const uchar4 c1 = colourin.tex2D((int)x+u, (int)y+v);
-				
-				if (depth != 0x7FFFFFFF) {
-					int i=0;
-					for (i=0; i<mapidx; ++i) {
-						if (abs(mappings[i].d - depth) < 100) {
-							if (u < 0 && v < 0) mappings[i].quad |= 0x1;
-							if (u > 0 && v < 0) mappings[i].quad |= 0x2;
-							if (u > 0 && v > 0) mappings[i].quad |= 0x4;
-							if (u < 0 && v > 0) mappings[i].quad |= 0x8;
-							break;
-						}
-					}
-					if (i == mapidx && i < 5) {
-						mappings[mapidx].d = depth;
-						mappings[mapidx].quad = 0;
-						if (u < 0 && v < 0) mappings[mapidx].quad |= 0x1;
-						if (u > 0 && v < 0) mappings[mapidx].quad |= 0x2;
-						if (u > 0 && v > 0) mappings[mapidx].quad |= 0x4;
-						if (u < 0 && v > 0) mappings[mapidx].quad |= 0x8;
-						++mapidx;
-					} else {
-						//printf("EXCEEDED\n");
-					}
-				}
-			}
-		}
-
-		int bestdepth = 1000000;
-		//int count = 0;
-		for (int i=0; i<mapidx; ++i) {
-			if (__popc(mappings[i].quad) >= 3 && mappings[i].d < bestdepth) bestdepth = mappings[i].d;
-			//if (mappings[i].quad == 15 && mappings[i].d < bestdepth) bestdepth = mappings[i].d;
-			//if (mappings[i].quad == 15) count ++;
-		}
-
-		//depthout(x,y) = (mapidx == 5) ? 3.0f : 0.0f;
-
-		if (bestdepth < 1000000) {
-			depthout(x,y) = (float)bestdepth / 1000.0f;
-		}
-	}
-}
-
-void ftl::cuda::mls_resample(const TextureObject<int> &depthin, const TextureObject<uchar4> &colourin, TextureObject<float> &depthout, const HashParams &hashParams, int numcams, const SplatParams &params, cudaStream_t stream) {
-	const dim3 gridSize((depthin.width() + T_PER_BLOCK - 1)/T_PER_BLOCK, (depthin.height() + T_PER_BLOCK - 1)/T_PER_BLOCK);
-	const dim3 blockSize(T_PER_BLOCK, T_PER_BLOCK);
-
-	mls_resample_kernel<<<gridSize, blockSize, 0, stream>>>(depthin, colourin, depthout, hashParams, numcams, params);
-
-#ifdef _DEBUG
-	cudaSafeCall(cudaDeviceSynchronize());
-#endif
-}
-
-
 /// ===== Median Filter ======
 
 #define WINDOW_SIZE 3
diff --git a/applications/reconstruct/src/depth_camera_cuda.hpp b/applications/reconstruct/src/depth_camera_cuda.hpp
index 552c2e59d44206c089fee68124ca814d3e752ad6..26bfcad73f5ae72f20cfe2dd29d0e91c62fca62b 100644
--- a/applications/reconstruct/src/depth_camera_cuda.hpp
+++ b/applications/reconstruct/src/depth_camera_cuda.hpp
@@ -10,8 +10,10 @@ namespace cuda {
 
 void clear_depth(const TextureObject<float> &depth, cudaStream_t stream);
 void clear_depth(const TextureObject<int> &depth, cudaStream_t stream);
+void clear_to_zero(const ftl::cuda::TextureObject<float> &depth, cudaStream_t stream);
 void clear_points(const ftl::cuda::TextureObject<float4> &depth, cudaStream_t stream);
 void clear_colour(const ftl::cuda::TextureObject<uchar4> &depth, cudaStream_t stream);
+void clear_colour(const ftl::cuda::TextureObject<float4> &depth, cudaStream_t stream);
 
 void median_filter(const ftl::cuda::TextureObject<int> &in, ftl::cuda::TextureObject<float> &out, cudaStream_t stream);
 
@@ -21,7 +23,7 @@ void float_to_int(const ftl::cuda::TextureObject<float> &in, ftl::cuda::TextureO
 
 void mls_smooth(TextureObject<float4> &output, const ftl::voxhash::HashParams &hashParams, int numcams, int cam, cudaStream_t stream);
 
-void mls_resample(const TextureObject<int> &depthin, const TextureObject<uchar4> &colourin, TextureObject<float> &depthout, const ftl::voxhash::HashParams &hashParams, int numcams, const ftl::render::SplatParams &params, cudaStream_t stream);
+void mls_render_depth(const TextureObject<int> &input, TextureObject<int> &output, const ftl::render::SplatParams &params, int numcams, cudaStream_t stream);
 
 void hole_fill(const TextureObject<int> &depth_in, const TextureObject<float> &depth_out, const DepthCameraParams &params, cudaStream_t stream);
 
diff --git a/applications/reconstruct/src/dibr.cu b/applications/reconstruct/src/dibr.cu
deleted file mode 100644
index 9558a0e0d2ac4b7a3978ccd48a6f6791f99a337f..0000000000000000000000000000000000000000
--- a/applications/reconstruct/src/dibr.cu
+++ /dev/null
@@ -1,221 +0,0 @@
-#include "splat_render_cuda.hpp"
-#include <cuda_runtime.h>
-
-#include <ftl/cuda_matrix_util.hpp>
-
-#include "splat_params.hpp"
-#include <ftl/depth_camera.hpp>
-
-#define T_PER_BLOCK 8
-
-using ftl::cuda::TextureObject;
-using ftl::render::SplatParams;
-
-extern __constant__ ftl::voxhash::DepthCameraCUDA c_cameras[MAX_CAMERAS];
-
-__global__ void clearColourKernel(TextureObject<uchar4> colour) {
-	const unsigned int x = blockIdx.x*blockDim.x + threadIdx.x;
-	const unsigned int y = blockIdx.y*blockDim.y + threadIdx.y;
-
-	if (x < colour.width() && y < colour.height()) {
-		//depth(x,y) = 0x7f800000; //PINF;
-		colour(x,y) = make_uchar4(76,76,82,0);
-	}
-}
-
-
-__global__ void dibr_depthmap_kernel(
-    TextureObject<int> depth, int numcams, SplatParams params) {
-
-    const int i = threadIdx.y*blockDim.y + threadIdx.x;
-    const int bx = blockIdx.x*blockDim.x;
-    const int by = blockIdx.y*blockDim.y;
-    const int x = bx + threadIdx.x;
-    const int y = by + threadIdx.y;
-    
-    for (int j=0; j<numcams; ++j) {
-        const ftl::voxhash::DepthCameraCUDA camera = c_cameras[j];
-	
-		float4 d = tex2D<float4>(camera.points, x, y);
-		if (d.z < 0.0f) continue;
-        //if (d >= params.camera.m_sensorDepthWorldMax) continue;
-        
-        //const float3 worldPos = camera.pose * camera.params.kinectDepthToSkeleton(x, y, d);
-        
-        const float3 worldPos = make_float3(d);
-        const float3 camPos = params.m_viewMatrix * worldPos;
-	    const float2 screenPosf = params.camera.cameraToKinectScreenFloat(camPos);
-	    const uint2 screenPos = make_uint2(make_int2(screenPosf));
-
-        if (camPos.z < params.camera.m_sensorDepthWorldMin) continue;
-
-        const unsigned int cx = screenPos.x;
-        const unsigned int cy = screenPos.y;
-
-
-        if (cx < depth.width() && cy < depth.height()) {
-            //float camd = depth_in.tex2D((int)cx,(int)cy);
-            //atomicMin(&depth(x,y), idepth);
-            //float camdiff = fabs(camPos.z-camd);
-            //if (camdiff < 0.1f) {
-           		//colour_out(cx,cy) = tex2D<uchar4>(camera.colour,x,y);
-            //} else {
-				//colour_out(cx,cy) = make_uchar4(camdiff * 100, 0, 0, 255);
-            //}
-            
-            atomicMin(&depth(cx,cy), camPos.z * 1000.0f);
-        }
-    }
-}
-
-
-__global__ void dibr_kernel(
-    TextureObject<int> depth_in,
-    TextureObject<uchar4> colour_out, int numcams, SplatParams params) {
-
-    const int i = threadIdx.y*blockDim.y + threadIdx.x;
-    const int bx = blockIdx.x*blockDim.x;
-    const int by = blockIdx.y*blockDim.y;
-    const int x = bx + threadIdx.x;
-    const int y = by + threadIdx.y;
-    
-    for (int j=0; j<numcams; ++j) {
-        const ftl::voxhash::DepthCameraCUDA camera = c_cameras[j];
-	
-		float4 d = tex2D<float4>(camera.points, x, y);
-		if (d.z < 0.0f) continue;
-        //if (d >= params.camera.m_sensorDepthWorldMax) continue;
-        
-        //const float3 worldPos = camera.pose * camera.params.kinectDepthToSkeleton(x, y, d);
-        
-        const float3 worldPos = make_float3(d);
-        const float3 camPos = params.m_viewMatrix * worldPos;
-	    const float2 screenPosf = params.camera.cameraToKinectScreenFloat(camPos);
-	    const uint2 screenPos = make_uint2(make_int2(screenPosf));
-
-        if (camPos.z < params.camera.m_sensorDepthWorldMin) continue;
-
-        const unsigned int cx = screenPos.x;
-        const unsigned int cy = screenPos.y;
-
-
-        if (cx < colour_out.width() && cy < colour_out.height()) {
-            //float camd = depth_in.tex2D((int)cx,(int)cy);
-            //atomicMin(&depth(x,y), idepth);
-            //float camdiff = fabs(camPos.z-camd);
-            //if (camdiff < 0.1f) {
-
-            if (depth_in(cx,cy) == (int)(camPos.z * 1000.0f)) {
-				   colour_out(cx,cy) = tex2D<uchar4>(camera.colour,x,y);
-				   //colour_out(cx,cy) = (j==0) ? make_uchar4(20,255,0,255) : make_uchar4(20,0,255,255);
-            }
-                   
-
-            //} else {
-				//colour_out(cx,cy) = make_uchar4(camdiff * 100, 0, 0, 255);
-			//}
-        }
-    }
-}
-
-__device__ inline float4 make_float4(const uchar4 &c) {
-    return make_float4(c.x,c.y,c.z,c.w);
-}
-
-__global__ void dibr_kernel_rev(
-    TextureObject<float> depth_in,
-    TextureObject<uchar4> colour_out, int numcams, SplatParams params) {
-
-    const int i = threadIdx.y*blockDim.y + threadIdx.x;
-    const int bx = blockIdx.x*blockDim.x;
-    const int by = blockIdx.y*blockDim.y;
-    const int x = bx + threadIdx.x;
-	const int y = by + threadIdx.y;
-	
-	float camd = depth_in.tex2D((int)x,(int)y);
-	if (camd < 0.01f)	return;
-	if (camd >= params.camera.m_sensorDepthWorldMax) return;
-	
-	const float3 worldPos = params.m_viewMatrixInverse * params.camera.kinectDepthToSkeleton(x, y, camd);
-    float mindiff = 1000.0f;
-    float4 col = make_float4(0.0f,0.0f,0.0f,0.0f);
-    int count = 0;
-
-    for (int j=0; j<numcams; ++j) {
-		const ftl::voxhash::DepthCameraCUDA camera = c_cameras[j];
-		
-		const float3 camPos = camera.poseInverse * worldPos;
-	    const float2 screenPosf = camera.params.cameraToKinectScreenFloat(camPos);
-		const uint2 screenPos = make_uint2(make_int2(screenPosf));
-		
-		if (camPos.z < params.camera.m_sensorDepthWorldMin) continue;
-
-		const unsigned int cx = screenPos.x;
-        const unsigned int cy = screenPos.y;
-
-        if (cx < camera.params.m_imageWidth && cy < camera.params.m_imageHeight) {
-			float d = tex2D<float>(camera.depth, (int)cx, (int)cy);
-            float camdiff = fabs(camPos.z-d);
-            
-            if (camdiff < mindiff) {
-                mindiff = camdiff;
-                col += make_float4(tex2D<uchar4>(camera.colour,cx,cy));
-                ++count;
-            }
-
-            //if (camdiff < 0.1f) {
-            //	colour_out(x,y) = tex2D<uchar4>(camera.colour,cx,cy);
-            //} else {
-				//colour_out(x,y) = make_uchar4(camdiff * 100, 0, 0, 255);
-			//}
-		}
-    }
-
-    if (count > 0) {
-        col = col / (float)count;
-        colour_out(x,y) = make_uchar4(col.x,col.y,col.z,255);
-    } else {
-        colour_out(x,y) = make_uchar4(76,76,76,255);
-    }
-}
-
-void ftl::cuda::dibr(const TextureObject<int> &depth_out,
-    const TextureObject<uchar4> &colour_out, int numcams, const SplatParams &params, cudaStream_t stream) {
-
-    const dim3 gridSize((depth_out.width() + T_PER_BLOCK - 1)/T_PER_BLOCK, (depth_out.height() + T_PER_BLOCK - 1)/T_PER_BLOCK);
-    const dim3 blockSize(T_PER_BLOCK, T_PER_BLOCK);
-
-	clearColourKernel<<<gridSize, blockSize, 0, stream>>>(colour_out);
-	
-#ifdef _DEBUG
-	cudaSafeCall(cudaDeviceSynchronize());
-#endif
-
-    dibr_depthmap_kernel<<<gridSize, blockSize, 0, stream>>>(depth_out, numcams, params);
-    dibr_kernel<<<gridSize, blockSize, 0, stream>>>(depth_out, colour_out, numcams, params);
-	cudaSafeCall( cudaGetLastError() );
-	
-#ifdef _DEBUG
-	cudaSafeCall(cudaDeviceSynchronize());
-#endif
-}
-
-void ftl::cuda::dibr(const TextureObject<float> &depth_out,
-    const TextureObject<uchar4> &colour_out, int numcams, const SplatParams &params, cudaStream_t stream) {
-
-    const dim3 gridSize((depth_out.width() + T_PER_BLOCK - 1)/T_PER_BLOCK, (depth_out.height() + T_PER_BLOCK - 1)/T_PER_BLOCK);
-    const dim3 blockSize(T_PER_BLOCK, T_PER_BLOCK);
-
-	clearColourKernel<<<gridSize, blockSize, 0, stream>>>(colour_out);
-	
-#ifdef _DEBUG
-	cudaSafeCall(cudaDeviceSynchronize());
-#endif
-
-    dibr_kernel_rev<<<gridSize, blockSize, 0, stream>>>(depth_out, colour_out, numcams, params);
-	cudaSafeCall( cudaGetLastError() );
-	
-#ifdef _DEBUG
-	cudaSafeCall(cudaDeviceSynchronize());
-#endif
-}
diff --git a/applications/reconstruct/src/garbage.cu b/applications/reconstruct/src/garbage.cu
deleted file mode 100644
index b685e9e6b7d94434ff425eff268699a715261522..0000000000000000000000000000000000000000
--- a/applications/reconstruct/src/garbage.cu
+++ /dev/null
@@ -1,135 +0,0 @@
-#include <ftl/voxel_hash.hpp>
-#include "garbage.hpp"
-
-using namespace ftl::voxhash;
-
-#define T_PER_BLOCK 8
-#define NUM_CUDA_BLOCKS	10000
-
-/*__global__ void starveVoxelsKernel(HashData hashData) {
-	int ptr;
-
-	// Stride over all allocated blocks
-	for (int bi=blockIdx.x; bi<*hashData.d_hashCompactifiedCounter; bi+=NUM_CUDA_BLOCKS) {
-
-	ptr = hashData.d_hashCompactified[bi].ptr;
-	int weight = hashData.d_SDFBlocks[ptr + threadIdx.x].weight;
-	weight = max(0, weight-2);	
-	hashData.d_SDFBlocks[ptr + threadIdx.x].weight = weight;  //CHECK Remove to totally clear previous frame (Nick)
-
-	}
-}
-
-void ftl::cuda::starveVoxels(HashData& hashData, const HashParams& hashParams, cudaStream_t stream) {
-	const unsigned int threadsPerBlock = SDF_BLOCK_SIZE*SDF_BLOCK_SIZE*SDF_BLOCK_SIZE;
-	const dim3 gridSize(NUM_CUDA_BLOCKS, 1);
-	const dim3 blockSize(threadsPerBlock, 1);
-
-	//if (hashParams.m_numOccupiedBlocks > 0) {
-		starveVoxelsKernel << <gridSize, blockSize, 0, stream >> >(hashData);
-	//}
-#ifdef _DEBUG
-	cudaSafeCall(cudaDeviceSynchronize());
-	//cutilCheckMsg(__FUNCTION__);
-#endif
-}*/
-
-#define ENTRIES_PER_BLOCK 4
-
-__global__ void clearVoxelsKernel(HashData hashData) {
-	const int lane = threadIdx.x % 16;
-	const int halfWarp = threadIdx.x / 16;
-
-	// Stride over all allocated blocks
-	for (int bi=blockIdx.x+halfWarp; bi<*hashData.d_hashCompactifiedCounter; bi+=NUM_CUDA_BLOCKS*ENTRIES_PER_BLOCK) {
-
-	HashEntry *entry = hashData.d_hashCompactified[bi];	
-	//hashData.d_SDFBlocks[entry.ptr + threadIdx.x].weight = 0;
-	entry->voxels[lane] = 0;
-
-	}
-}
-
-void ftl::cuda::clearVoxels(HashData& hashData, const HashParams& hashParams) {
-	const unsigned int threadsPerBlock = 16 * ENTRIES_PER_BLOCK;
-	const dim3 gridSize(NUM_CUDA_BLOCKS, 1);
-	const dim3 blockSize(threadsPerBlock, 1);
-
-	clearVoxelsKernel << <gridSize, blockSize >> >(hashData);
-}
-
-
-__global__ void garbageCollectIdentifyKernel(HashData hashData) {
-	const int lane = threadIdx.x % 16;
-	const int halfWarp = threadIdx.x / 16;
-
-	// Stride over all allocated blocks
-	for (int bi=blockIdx.x+halfWarp; bi<*hashData.d_hashCompactifiedCounter; bi+=NUM_CUDA_BLOCKS * ENTRIES_PER_BLOCK) {
-
-	const HashEntry *entry = hashData.d_hashCompactified[bi];
-
-	const uint v = entry->voxels[lane];
-	const uint mask = (halfWarp & 0x1) ? 0xFFFF0000 : 0x0000FFFF;
-	uint ballot_result = __ballot_sync(mask, v == 0 || v == 0xFFFFFFFF);
-
-	if (lane == 0) hashData.d_hashDecision[bi] = (ballot_result == mask) ? 1 : 0;
-
-	}
-}
- 
-void ftl::cuda::garbageCollectIdentify(HashData& hashData, const HashParams& hashParams, cudaStream_t stream) {
-	
-	const unsigned int threadsPerBlock = SDF_BLOCK_SIZE * SDF_BLOCK_SIZE * SDF_BLOCK_SIZE / 2;
-	const dim3 gridSize(NUM_CUDA_BLOCKS, 1);
-	const dim3 blockSize(threadsPerBlock, 1);
-
-	//if (hashParams.m_numOccupiedBlocks > 0) {
-		garbageCollectIdentifyKernel << <gridSize, blockSize, 0, stream >> >(hashData);
-	//}
-#ifdef _DEBUG
-	cudaSafeCall(cudaDeviceSynchronize());
-	//cutilCheckMsg(__FUNCTION__);
-#endif
-}
-
-
-__global__ void garbageCollectFreeKernel(HashData hashData) {
-
-	// Stride over all allocated blocks
-	for (int bi=blockIdx.x*blockDim.x + threadIdx.x; bi<*hashData.d_hashCompactifiedCounter; bi+=NUM_CUDA_BLOCKS*blockDim.x) {
-
-	HashEntry *entry = hashData.d_hashCompactified[bi];
-
-	if ((entry->head.flags & ftl::voxhash::kFlagSurface) == 0) {	//decision to delete the hash entry
-
-		
-		//if (entry->head.offset == FREE_ENTRY) return; //should never happen since we did compactify before
-
-		int3 posI3 = make_int3(entry->head.posXYZ.x, entry->head.posXYZ.y, entry->head.posXYZ.z);
-
-		if (hashData.deleteHashEntryElement(posI3)) {	//delete hash entry from hash (and performs heap append)
-			//#pragma unroll
-			//for (uint i = 0; i < 16; i++) {	//clear sdf block: CHECK TODO another kernel?
-			//	entry->voxels[i] = 0;
-			//}
-		}
-	}
-
-	}
-}
-
-
-void ftl::cuda::garbageCollectFree(HashData& hashData, const HashParams& hashParams, cudaStream_t stream) {
-	
-	const unsigned int threadsPerBlock = T_PER_BLOCK*T_PER_BLOCK;
-	const dim3 gridSize(NUM_CUDA_BLOCKS, 1);  // (hashParams.m_numOccupiedBlocks + threadsPerBlock - 1) / threadsPerBlock
-	const dim3 blockSize(threadsPerBlock, 1);
-	
-	//if (hashParams.m_numOccupiedBlocks > 0) {
-		garbageCollectFreeKernel << <gridSize, blockSize, 0, stream >> >(hashData);
-	//}
-#ifdef _DEBUG
-	cudaSafeCall(cudaDeviceSynchronize());
-	//cutilCheckMsg(__FUNCTION__);
-#endif
-}
diff --git a/applications/reconstruct/src/garbage.hpp b/applications/reconstruct/src/garbage.hpp
deleted file mode 100644
index 5d1d7574d252b40da18008da39f1bf89a7d667fb..0000000000000000000000000000000000000000
--- a/applications/reconstruct/src/garbage.hpp
+++ /dev/null
@@ -1,15 +0,0 @@
-#ifndef _FTL_RECONSTRUCTION_GARBAGE_HPP_
-#define _FTL_RECONSTRUCTION_GARBAGE_HPP_
-
-namespace ftl {
-namespace cuda {
-
-void clearVoxels(ftl::voxhash::HashData& hashData, const ftl::voxhash::HashParams& hashParams);
-void starveVoxels(ftl::voxhash::HashData& hashData, const ftl::voxhash::HashParams& hashParams, cudaStream_t stream);
-void garbageCollectIdentify(ftl::voxhash::HashData& hashData, const ftl::voxhash::HashParams& hashParams, cudaStream_t stream);
-void garbageCollectFree(ftl::voxhash::HashData& hashData, const ftl::voxhash::HashParams& hashParams, cudaStream_t stream);
-
-}
-}
-
-#endif  // _FTL_RECONSTRUCTION_GARBAGE_HPP_
diff --git a/applications/reconstruct/src/ilw.cpp b/applications/reconstruct/src/ilw.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..86a4cca5e4f82047ed6591a137b694788da879eb
--- /dev/null
+++ b/applications/reconstruct/src/ilw.cpp
@@ -0,0 +1,120 @@
+#include "ilw.hpp"
+#include <ftl/utility/matrix_conversion.hpp>
+#include <ftl/rgbd/source.hpp>
+#include <ftl/cuda/points.hpp>
+#include <loguru.hpp>
+
+#include "ilw_cuda.hpp"
+
+using ftl::ILW;
+using ftl::detail::ILWData;
+using ftl::rgbd::Channel;
+using ftl::rgbd::Channels;
+using ftl::rgbd::Format;
+using cv::cuda::GpuMat;
+
+ILW::ILW(nlohmann::json &config) : ftl::Configurable(config) {
+
+}
+
+ILW::~ILW() {
+
+}
+
+bool ILW::process(ftl::rgbd::FrameSet &fs, cudaStream_t stream) {
+    _phase0(fs, stream);
+
+    //for (int i=0; i<2; ++i) {
+        _phase1(fs, stream);
+        //for (int j=0; j<3; ++j) {
+        //    _phase2(fs);
+        //}
+
+		// TODO: Break if no time left
+    //}
+
+    return true;
+}
+
+bool ILW::_phase0(ftl::rgbd::FrameSet &fs, cudaStream_t stream) {
+    // Make points channel...
+    for (size_t i=0; i<fs.frames.size(); ++i) {
+		auto &f = fs.frames[i];
+		auto *s = fs.sources[i];
+
+		if (f.empty(Channel::Depth + Channel::Colour)) {
+			LOG(ERROR) << "Missing required channel";
+			continue;
+		}
+			
+        auto &t = f.createTexture<float4>(Channel::Points, Format<float4>(f.get<GpuMat>(Channel::Colour).size()));
+        auto pose = MatrixConversion::toCUDA(s->getPose().cast<float>()); //.inverse());
+        ftl::cuda::point_cloud(t, f.createTexture<float>(Channel::Depth), s->parameters(), pose, stream);
+
+        // TODO: Create energy vector texture and clear it
+        // Create energy and clear it
+
+        // Convert colour from BGR to BGRA if needed
+		if (f.get<GpuMat>(Channel::Colour).type() == CV_8UC3) {
+			// Convert to 4 channel colour
+			auto &col = f.get<GpuMat>(Channel::Colour);
+			GpuMat tmp(col.size(), CV_8UC4);
+			cv::cuda::swap(col, tmp);
+			cv::cuda::cvtColor(tmp,col, cv::COLOR_BGR2BGRA);
+		}
+
+        f.createTexture<float4>(Channel::EnergyVector, Format<float4>(f.get<GpuMat>(Channel::Colour).size()));
+        f.createTexture<float>(Channel::Energy, Format<float>(f.get<GpuMat>(Channel::Colour).size()));
+        f.createTexture<uchar4>(Channel::Colour);
+    }
+
+    return true;
+}
+
+bool ILW::_phase1(ftl::rgbd::FrameSet &fs, cudaStream_t stream) {
+    // Run correspondence kernel to create an energy vector
+
+	// For each camera combination
+    for (size_t i=0; i<fs.frames.size(); ++i) {
+        for (size_t j=0; j<fs.frames.size(); ++j) {
+            if (i == j) continue;
+
+            LOG(INFO) << "Running phase1";
+
+            auto &f1 = fs.frames[i];
+            auto &f2 = fs.frames[j];
+            //auto s1 = fs.frames[i];
+            auto s2 = fs.sources[j];
+
+            auto pose = MatrixConversion::toCUDA(s2->getPose().cast<float>().inverse());
+
+            try {
+            //Calculate energy vector to best correspondence
+            ftl::cuda::correspondence_energy_vector(
+                f1.getTexture<float4>(Channel::Points),
+                f2.getTexture<float4>(Channel::Points),
+                f1.getTexture<uchar4>(Channel::Colour),
+                f2.getTexture<uchar4>(Channel::Colour),
+                // TODO: Add normals and other things...
+                f1.getTexture<float4>(Channel::EnergyVector),
+                f1.getTexture<float>(Channel::Energy),
+                pose,
+                s2->parameters(),
+                stream
+            );
+            } catch (ftl::exception &e) {
+                LOG(ERROR) << "Exception in correspondence: " << e.what();
+            }
+
+            LOG(INFO) << "Correspondences done... " << i;
+        }
+    }
+
+    return true;
+}
+
+bool ILW::_phase2(ftl::rgbd::FrameSet &fs) {
+    // Run energies and motion kernel
+
+    return true;
+}
diff --git a/applications/reconstruct/src/ilw.cu b/applications/reconstruct/src/ilw.cu
new file mode 100644
index 0000000000000000000000000000000000000000..90133a3a57800ee87a91fd50902deea5f701258a
--- /dev/null
+++ b/applications/reconstruct/src/ilw.cu
@@ -0,0 +1,86 @@
+#include "ilw_cuda.hpp"
+
+using ftl::cuda::TextureObject;
+using ftl::rgbd::Camera;
+
+#define WARP_SIZE 32
+#define T_PER_BLOCK 8
+#define FULL_MASK 0xffffffff
+
+__device__ inline float warpMax(float e) {
+	for (int i = WARP_SIZE/2; i > 0; i /= 2) {
+		const float other = __shfl_xor_sync(FULL_MASK, e, i, WARP_SIZE);
+		e = max(e, other);
+	}
+	return e;
+}
+
+__global__ void correspondence_energy_vector_kernel(
+        TextureObject<float4> p1,
+        TextureObject<float4> p2,
+        TextureObject<uchar4> c1,
+        TextureObject<uchar4> c2,
+        TextureObject<float4> vout,
+        TextureObject<float> eout,
+        float4x4 pose2,  // Inverse
+        Camera cam2) {
+
+    // Each warp picks point in p1
+    const int tid = (threadIdx.x + threadIdx.y * blockDim.x);
+	const int x = (blockIdx.x*blockDim.x + threadIdx.x) / WARP_SIZE;
+    const int y = blockIdx.y*blockDim.y + threadIdx.y;
+    
+    const float3 world1 = make_float3(p1.tex2D(x, y));
+    const float3 camPos2 = pose2 * world1;
+    const uint2 screen2 = cam2.camToScreen<uint2>(camPos2);
+
+    const int upsample = 8;
+
+    // Project to p2 using cam2
+    // Each thread takes a possible correspondence and calculates a weighting
+    const int lane = tid % WARP_SIZE;
+	for (int i=lane; i<upsample*upsample; i+=WARP_SIZE) {
+		const float u = (i % upsample) - (upsample / 2);
+        const float v = (i / upsample) - (upsample / 2);
+        
+        const float3 world2 = make_float3(p2.tex2D(screen2.x+u, screen2.y+v));
+
+        // Determine degree of correspondence
+        const float confidence = 1.0f / length(world1 - world2);
+
+        printf("conf %f\n", confidence);
+        const float maxconf = warpMax(confidence);
+
+        // This thread has best confidence value
+        if (maxconf == confidence) {
+            vout(x,y) = vout.tex2D(x, y) + make_float4(
+                (world1.x - world2.x) * maxconf,
+                (world1.y - world2.y) * maxconf,
+                (world1.z - world2.z) * maxconf,
+                maxconf);
+            eout(x,y) = eout.tex2D(x,y) + length(world1 - world2)*maxconf;
+        }
+    }
+}
+
+void ftl::cuda::correspondence_energy_vector(
+        TextureObject<float4> &p1,
+        TextureObject<float4> &p2,
+        TextureObject<uchar4> &c1,
+        TextureObject<uchar4> &c2,
+        TextureObject<float4> &vout,
+        TextureObject<float> &eout,
+        float4x4 &pose2,
+        const Camera &cam2,
+        cudaStream_t stream) {
+
+    const dim3 gridSize((p1.width() + 2 - 1)/2, (p1.height() + T_PER_BLOCK - 1)/T_PER_BLOCK);
+    const dim3 blockSize(2*WARP_SIZE, T_PER_BLOCK);
+
+    printf("COR SIZE %d,%d\n", p1.width(), p1.height());
+
+    correspondence_energy_vector_kernel<<<gridSize, blockSize, 0, stream>>>(
+        p1, p2, c1, c2, vout, eout, pose2, cam2
+    );
+    cudaSafeCall( cudaGetLastError() );
+}
diff --git a/applications/reconstruct/src/ilw.hpp b/applications/reconstruct/src/ilw.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..0be45d015e976b540263a2c16cc5605376092a43
--- /dev/null
+++ b/applications/reconstruct/src/ilw.hpp
@@ -0,0 +1,66 @@
+#ifndef _FTL_RECONSTRUCT_ILW_HPP_
+#define _FTL_RECONSTRUCT_ILW_HPP_
+
+#include <ftl/cuda_common.hpp>
+#include <ftl/rgbd/frameset.hpp>
+#include <ftl/configurable.hpp>
+#include <vector>
+
+namespace ftl {
+
+namespace detail {
+struct ILWData{
+    // x,y,z + confidence
+    ftl::cuda::TextureObject<float4> correspondence;
+
+    ftl::cuda::TextureObject<float4> points;
+
+	// Residual potential energy
+	ftl::cuda::TextureObject<float> residual;
+
+	// Flow magnitude
+	ftl::cuda::TextureObject<float> flow;
+};
+}
+
+/**
+ * For a set of sources, perform Iterative Lattice Warping to correct the
+ * location of points between the cameras. The algorithm finds possible
+ * correspondences and warps the original pixel lattice of points in each
+ * camera towards the correspondences, iterating the process as many times as
+ * possible. The result is that both local and global adjustment is made to the
+ * point clouds to improve micro alignment that may have been incorrect due to
+ * either inaccurate camera pose estimation or noise/errors in the depth maps.
+ */
+class ILW : public ftl::Configurable {
+    public:
+    explicit ILW(nlohmann::json &config);
+    ~ILW();
+
+    /**
+     * Take a frameset and perform the iterative lattice warping.
+     */
+    bool process(ftl::rgbd::FrameSet &fs, cudaStream_t stream=0);
+
+    private:
+    /*
+     * Initialise data.
+     */
+    bool _phase0(ftl::rgbd::FrameSet &fs, cudaStream_t stream);
+
+    /*
+     * Find possible correspondences and a confidence value.
+     */
+    bool _phase1(ftl::rgbd::FrameSet &fs, cudaStream_t stream);
+
+    /*
+     * Calculate energies and move the points.
+     */
+    bool _phase2(ftl::rgbd::FrameSet &fs);
+
+    std::vector<detail::ILWData> data_;
+};
+
+}
+
+#endif  // _FTL_RECONSTRUCT_ILW_HPP_
diff --git a/applications/reconstruct/src/ilw_cuda.hpp b/applications/reconstruct/src/ilw_cuda.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..a01af75149409fe033ba39ffb0170489ee926be9
--- /dev/null
+++ b/applications/reconstruct/src/ilw_cuda.hpp
@@ -0,0 +1,26 @@
+#ifndef _FTL_ILW_CUDA_HPP_
+#define _FTL_ILW_CUDA_HPP_
+
+#include <ftl/cuda_common.hpp>
+#include <ftl/rgbd/camera.hpp>
+#include <ftl/cuda_matrix_util.hpp>
+
+namespace ftl {
+namespace cuda {
+
+void correspondence_energy_vector(
+    ftl::cuda::TextureObject<float4> &p1,
+    ftl::cuda::TextureObject<float4> &p2,
+    ftl::cuda::TextureObject<uchar4> &c1,
+    ftl::cuda::TextureObject<uchar4> &c2,
+    ftl::cuda::TextureObject<float4> &vout,
+    ftl::cuda::TextureObject<float> &eout,
+    float4x4 &pose2,
+    const ftl::rgbd::Camera &cam2,
+    cudaStream_t stream
+);
+
+}
+}
+
+#endif  // _FTL_ILW_CUDA_HPP_
diff --git a/applications/reconstruct/src/integrators.cu b/applications/reconstruct/src/integrators.cu
deleted file mode 100644
index 326c3dd9a59cd776e254149d47e02c684e1d6d36..0000000000000000000000000000000000000000
--- a/applications/reconstruct/src/integrators.cu
+++ /dev/null
@@ -1,342 +0,0 @@
-#include "integrators.hpp"
-//#include <ftl/ray_cast_params.hpp>
-#include <vector_types.h>
-#include <cuda_runtime.h>
-#include <ftl/cuda_matrix_util.hpp>
-#include <ftl/cuda_util.hpp>
-#include <ftl/cuda_common.hpp>
-
-#define T_PER_BLOCK 8
-#define NUM_CUDA_BLOCKS		10000
-#define WARP_SIZE 32
-
-using ftl::voxhash::HashData;
-using ftl::voxhash::HashParams;
-using ftl::voxhash::Voxel;
-using ftl::voxhash::HashEntry;
-using ftl::voxhash::HashEntryHead;
-using ftl::voxhash::FREE_ENTRY;
-
-extern __constant__ ftl::voxhash::DepthCameraCUDA c_cameras[MAX_CAMERAS];
-extern __constant__ HashParams c_hashParams;
-
-__device__ float4 make_float4(uchar4 c) {
-	return make_float4(static_cast<float>(c.x), static_cast<float>(c.y), static_cast<float>(c.z), static_cast<float>(c.w));
-}
-
-__device__ float colourDistance(const uchar4 &c1, const uchar3 &c2) {
-	float x = c1.x-c2.x;
-	float y = c1.y-c2.y;
-	float z = c1.z-c2.z;
-	return x*x + y*y + z*z;
-}
-
-/*
- * Kim, K., Chalidabhongse, T. H., Harwood, D., & Davis, L. (2005).
- * Real-time foreground-background segmentation using codebook model.
- * Real-Time Imaging. https://doi.org/10.1016/j.rti.2004.12.004
- */
-__device__ bool colordiff(const uchar4 &pa, const uchar3 &pb, float epsilon) {
-	float x_2 = pb.x * pb.x + pb.y * pb.y + pb.z * pb.z;
-	float v_2 = pa.x * pa.x + pa.y * pa.y + pa.z * pa.z;
-	float xv_2 = pow(pb.x * pa.x + pb.y * pa.y + pb.z * pa.z, 2);
-	float p_2 = xv_2 / v_2;
-	return sqrt(x_2 - p_2) < epsilon;
-}
-
-/*
- * Guennebaud, G.; Gross, M. Algebraic point set surfaces. ACMTransactions on Graphics Vol. 26, No. 3, Article No. 23, 2007.
- * Used in: FusionMLS: Highly dynamic 3D reconstruction with consumer-grade RGB-D cameras
- *     r = distance between points
- *     h = smoothing parameter in meters (default 4cm)
- */
-__device__ float spatialWeighting(float r) {
-	const float h = c_hashParams.m_spatialSmoothing;
-	if (r >= h) return 0.0f;
-	float rh = r / h;
-	rh = 1.0f - rh*rh;
-	return rh*rh*rh*rh;
-}
-
-__device__ float spatialWeighting(float r, float h) {
-	//const float h = c_hashParams.m_spatialSmoothing;
-	if (r >= h) return 0.0f;
-	float rh = r / h;
-	rh = 1.0f - rh*rh;
-	return rh*rh*rh*rh;
-}
-
-
-__global__ void integrateDepthMapsKernel(HashData hashData, HashParams hashParams, int numcams) {
-	__shared__ uint all_warp_ballot;
-	__shared__ uint voxels[16];
-
-	const uint i = threadIdx.x;	//inside of an SDF block
-	const int3 po = make_int3(hashData.delinearizeVoxelIndex(i));
-
-	// Stride over all allocated blocks
-	for (int bi=blockIdx.x; bi<*hashData.d_hashCompactifiedCounter; bi+=NUM_CUDA_BLOCKS) {
-
-	//TODO check if we should load this in shared memory
-	//HashEntryHead entry = hashData.d_hashCompactified[bi]->head;
-
-	int3 pi_base = hashData.SDFBlockToVirtualVoxelPos(make_int3(hashData.d_hashCompactified[bi]->head.posXYZ));
-
-	//uint idx = entry.offset + i;
-	int3 pi = pi_base + po;
-	float3 pfb = hashData.virtualVoxelPosToWorld(pi);
-	int count = 0;
-	//float camdepths[MAX_CAMERAS];
-
-	Voxel oldVoxel; // = hashData.d_SDFBlocks[idx];
-	hashData.deleteVoxel(oldVoxel);
-
-	for (uint cam=0; cam<numcams; ++cam) {
-		const ftl::voxhash::DepthCameraCUDA &camera = c_cameras[cam];
-	
-		float3 pf = camera.poseInverse * pfb;
-		uint2 screenPos = make_uint2(camera.params.cameraToKinectScreenInt(pf));
-
-		// For this voxel in hash, get its screen position and check it is on screen
-		if (screenPos.x < camera.params.m_imageWidth && screenPos.y < camera.params.m_imageHeight) {	//on screen
-
-			//float depth = g_InputDepth[screenPos];
-			float depth = tex2D<float>(camera.depth, screenPos.x, screenPos.y);
-			//if (depth > 20.0f) return;
-
-			//uchar4 color  = make_uchar4(0, 0, 0, 0);
-			//if (cameraData.d_colorData) {
-				//color = (cam == 0) ? make_uchar4(255,0,0,255) : make_uchar4(0,0,255,255);
-				//color = tex2D<uchar4>(camera.colour, screenPos.x, screenPos.y);
-				//color = bilinearFilterColor(cameraData.cameraToKinectScreenFloat(pf));
-			//}
-
-			//printf("screen pos %d\n", color.x);
-			//return;
-
-			// TODO:(Nick) Accumulate weighted positions
-			// TODO:(Nick) Accumulate weighted normals
-			// TODO:(Nick) Accumulate weights
-
-			// Depth is within accepted max distance from camera
-			if (depth > 0.01f && depth < hashParams.m_maxIntegrationDistance) { // valid depth and color (Nick: removed colour check)
-				//camdepths[count] = depth;
-				++count;
-
-				// Calculate SDF of this voxel wrt the depth map value
-				float sdf = depth - pf.z;
-				float truncation = hashData.getTruncation(depth);
-				float depthZeroOne = camera.params.cameraToKinectProjZ(depth);
-
-				// Is this voxel close enough to cam for depth map value
-				// CHECK Nick: If is too close then free space violation so remove?
-				if (sdf > -truncation) // && depthZeroOne >= 0.0f && depthZeroOne <= 1.0f) //check if in truncation range should already be made in depth map computation
-				{
-					float weightUpdate = max(hashParams.m_integrationWeightSample * 1.5f * (1.0f-depthZeroOne), 1.0f);
-
-					Voxel curr;	//construct current voxel
-					curr.sdf = sdf;
-					curr.weight = weightUpdate;
-					//curr.color = make_uchar3(color.x, color.y, color.z);
-
-
-					//if (entry.flags != cameraParams.flags & 0xFF) {
-					//	entry.flags = cameraParams.flags & 0xFF;
-						//hashData.d_SDFBlocks[idx].color = make_uchar3(0,0,0);
-					//}
-					
-					Voxel newVoxel;
-					//if (color.x == MINF) hashData.combineVoxelDepthOnly(hashData.d_SDFBlocks[idx], curr, newVoxel);
-					//else hashData.combineVoxel(hashData.d_SDFBlocks[idx], curr, newVoxel);
-					hashData.combineVoxel(oldVoxel, curr, newVoxel);
-
-					oldVoxel = newVoxel;
-
-					//Voxel prev = getVoxel(g_SDFBlocksSDFUAV, g_SDFBlocksRGBWUAV, idx);
-					//Voxel newVoxel = combineVoxel(curr, prev);
-					//setVoxel(g_SDFBlocksSDFUAV, g_SDFBlocksRGBWUAV, idx, newVoxel);
-				}
-			} else {
-				// Depth is invalid so what to do here?
-				// TODO(Nick) Use past voxel if available (set weight from 0 to 1)
-
-				// Naive: need to know if this is a foreground voxel
-				//bool coldist = colordiff(color, hashData.d_SDFBlocks[idx].color, 5.0f);
-				//if (!coldist) ++count;
-
-			}
-		}
-	}
-
-	// Calculate voxel sign values across a warp
-	int warpNum = i / WARP_SIZE;
-	//uint ballot_result = __ballot_sync(0xFFFFFFFF, (oldVoxel.sdf >= 0.0f) ? 0 : 1);
-	uint ballot_result = __ballot_sync(0xFFFFFFFF, (fabs(oldVoxel.sdf) <= hashParams.m_virtualVoxelSize && oldVoxel.weight > 0) ? 1 : 0);
-
-	// Aggregate each warp result into voxel mask
-	if (i % WARP_SIZE == 0) {
-		voxels[warpNum] = ballot_result;
-	}
-
-	__syncthreads();
-
-	// Work out if block is occupied or not and save voxel masks
-	// TODO:(Nick) Is it faster to do this in a separate garbage kernel?
-	if (i < 16) {
-		const uint v = voxels[i];
-		hashData.d_hashCompactified[bi]->voxels[i] = v;
-		const uint mask = 0x0000FFFF;
-		uint b1 = __ballot_sync(mask, v == 0xFFFFFFFF);
-		uint b2 = __ballot_sync(mask, v == 0);
-		if (i == 0) {
-			if (b1 != mask && b2 != mask) hashData.d_hashCompactified[bi]->head.flags |= ftl::voxhash::kFlagSurface;
-			else hashData.d_hashCompactified[bi]->head.flags &= ~ftl::voxhash::kFlagSurface;
-		}
-	}
-
-	}
-}
-
-#define WINDOW_RADIUS 1
-#define PATCH_SIZE 32
-
-__global__ void integrateMLSKernel(HashData hashData, HashParams hashParams, int numcams) {
-	__shared__ uint voxels[16];
-
-	const uint i = threadIdx.x;	//inside of an SDF block
-	const int3 po = make_int3(hashData.delinearizeVoxelIndex(i));
-	const int warpNum = i / WARP_SIZE;
-	const int lane = i % WARP_SIZE;
-
-	// Stride over all allocated blocks
-	for (int bi=blockIdx.x; bi<*hashData.d_hashCompactifiedCounter; bi+=NUM_CUDA_BLOCKS) {
-
-	//TODO check if we should load this in shared memory
-	//HashEntryHead entry = hashData.d_hashCompactified[bi]->head;
-
-	const int3 pi_base = hashData.SDFBlockToVirtualVoxelPos(make_int3(hashData.d_hashCompactified[bi]->head.posXYZ));
-
-	//uint idx = entry.offset + i;
-	const int3 pi = pi_base + po;
-	const float3 pfb = hashData.virtualVoxelPosToWorld(pi);
-	//int count = 0;
-	//float camdepths[MAX_CAMERAS];
-
-	//Voxel oldVoxel; // = hashData.d_SDFBlocks[idx];
-	//hashData.deleteVoxel(oldVoxel);
-
-	//float3 awpos = make_float3(0.0f);
-	//float3 awnorm = make_float3(0.0f);
-	//float aweights = 0.0f;
-	float sdf = 0.0f;
-	float weights = 0.0f;
-
-	// Preload depth values
-	// 1. Find min and max screen positions
-	// 2. Subtract/Add WINDOW_RADIUS to min/max
-	// ... check that the buffer is not too small to cover this
-	// ... if buffer not big enough then don't buffer at all.
-	// 3. Populate shared mem depth map buffer using all threads
-	// 4. Adjust window lookups to use shared mem buffer
-
-	//uint cam=0;
-	for (uint cam=0; cam<numcams; ++cam) {
-		const ftl::voxhash::DepthCameraCUDA &camera = c_cameras[cam];
-		const uint height = camera.params.m_imageHeight;
-		const uint width = camera.params.m_imageWidth;
-	
-		const float3 pf = camera.poseInverse * pfb;
-		const uint2 screenPos = make_uint2(camera.params.cameraToKinectScreenInt(pf));
-
-		//float3 wpos = make_float3(0.0f);
-		float3 wnorm = make_float3(0.0f);
-		
-
-		#pragma unroll
-		for (int v=-WINDOW_RADIUS; v<=WINDOW_RADIUS; ++v) {
-			for (int u=-WINDOW_RADIUS; u<=WINDOW_RADIUS; ++u) {
-				if (screenPos.x+u < width && screenPos.y+v < height) {	//on screen
-					float4 depth = tex2D<float4>(camera.points, screenPos.x+u, screenPos.y+v);
-					if (depth.z == MINF) continue;
-
-					//float4 normal = tex2D<float4>(camera.normal, screenPos.x+u, screenPos.y+v);
-					const float3 camPos = camera.poseInverse * make_float3(depth); //camera.pose * camera.params.kinectDepthToSkeleton(screenPos.x+u, screenPos.y+v, depth);
-					const float weight = spatialWeighting(length(pf - camPos));
-
-					//wpos += weight*worldPos;
-					sdf += weight*(camPos.z - pf.z);
-					//sdf += camPos.z - pf.z;
-					//wnorm += weight*make_float3(normal);
-					//weights += 1.0f;	
-					weights += weight;			
-				}
-			}
-		}
-
-		//awpos += wpos;
-		//aweights += weights;
-	}
-
-	//awpos /= aweights;
-	//wnorm /= weights;
-
-	sdf /= weights;
-
-	//float sdf = (aweights == 0.0f) ? MINF : length(pfb - awpos);
-	//float sdf = wnorm.x * (pfb.x - wpos.x) + wnorm.y * (pfb.y - wpos.y) + wnorm.z * (pfb.z - wpos.z);
-
-	//printf("WEIGHTS: %f\n", weights);
-
-	//if (weights < 0.00001f) sdf = 0.0f;
-
-	// Calculate voxel sign values across a warp
-	int warpNum = i / WARP_SIZE;
-
-	//uint solid_ballot = __ballot_sync(0xFFFFFFFF, (fabs(sdf) < hashParams.m_virtualVoxelSize && aweights >= 0.5f) ? 1 : 0);
-	//uint solid_ballot = __ballot_sync(0xFFFFFFFF, (fabs(sdf) <= hashParams.m_virtualVoxelSize) ? 1 : 0);
-	//uint solid_ballot = __ballot_sync(0xFFFFFFFF, (aweights >= 0.0f) ? 1 : 0);
-	uint solid_ballot = __ballot_sync(0xFFFFFFFF, (sdf < 0.0f ) ? 1 : 0);
-
-	// Aggregate each warp result into voxel mask
-	if (i % WARP_SIZE == 0) {
-		voxels[warpNum] = solid_ballot;
-		//valid[warpNum] = valid_ballot;
-	}
-
-	__syncthreads();
-
-	// Work out if block is occupied or not and save voxel masks
-	// TODO:(Nick) Is it faster to do this in a separate garbage kernel?
-	if (i < 16) {
-		const uint v = voxels[i];
-		hashData.d_hashCompactified[bi]->voxels[i] = v;
-		//hashData.d_hashCompactified[bi]->validity[i] = valid[i];
-		const uint mask = 0x0000FFFF;
-		uint b1 = __ballot_sync(mask, v == 0xFFFFFFFF);
-		uint b2 = __ballot_sync(mask, v == 0);
-		if (i == 0) {
-			if (b1 != mask && b2 != mask) hashData.d_hashCompactified[bi]->head.flags |= ftl::voxhash::kFlagSurface;
-			else hashData.d_hashCompactified[bi]->head.flags &= ~ftl::voxhash::kFlagSurface;
-		}
-	}
-
-	}
-}
-
-
-
-void ftl::cuda::integrateDepthMaps(HashData& hashData, const HashParams& hashParams, int numcams, cudaStream_t stream) {
-const unsigned int threadsPerBlock = SDF_BLOCK_SIZE*SDF_BLOCK_SIZE*SDF_BLOCK_SIZE;
-const dim3 gridSize(NUM_CUDA_BLOCKS, 1);
-const dim3 blockSize(threadsPerBlock, 1);
-
-//if (hashParams.m_numOccupiedBlocks > 0) {	//this guard is important if there is no depth in the current frame (i.e., no blocks were allocated)
-	integrateMLSKernel << <gridSize, blockSize, 0, stream >> >(hashData, hashParams, numcams);
-//}
-
-//cudaSafeCall( cudaGetLastError() );
-#ifdef _DEBUG
-cudaSafeCall(cudaDeviceSynchronize());
-//cutilCheckMsg(__FUNCTION__);
-#endif
-}
diff --git a/applications/reconstruct/src/integrators.hpp b/applications/reconstruct/src/integrators.hpp
deleted file mode 100644
index 789551dd1fa7347bf02c518c8c5a73f6ae4269b4..0000000000000000000000000000000000000000
--- a/applications/reconstruct/src/integrators.hpp
+++ /dev/null
@@ -1,22 +0,0 @@
-#ifndef _FTL_RECONSTRUCTION_INTEGRATORS_HPP_
-#define _FTL_RECONSTRUCTION_INTEGRATORS_HPP_
-
-#include <ftl/voxel_hash.hpp>
-#include <ftl/depth_camera.hpp>
-
-namespace ftl {
-namespace cuda {
-
-/*void integrateDepthMap(ftl::voxhash::HashData& hashData, const ftl::voxhash::HashParams& hashParams,
-		const DepthCameraData& depthCameraData, const DepthCameraParams& depthCameraParams, cudaStream_t stream);
-
-void integrateRegistration(ftl::voxhash::HashData& hashData, const ftl::voxhash::HashParams& hashParams,
-		const DepthCameraData& depthCameraData, const DepthCameraParams& depthCameraParams, cudaStream_t stream);
-*/
-
-void integrateDepthMaps(ftl::voxhash::HashData& hashData, const ftl::voxhash::HashParams& hashParams, int numcams, cudaStream_t stream);
-
-}
-}
-
-#endif  // _FTL_RECONSTRUCTION_INTEGRATORS_HPP_
diff --git a/applications/reconstruct/src/main.cpp b/applications/reconstruct/src/main.cpp
index 46629bd0d8a5a2ec0e2ebbf8f2168bd0ecdccb57..107ed5978cb65b28f19e4a1e6fa2cb346846b64a 100644
--- a/applications/reconstruct/src/main.cpp
+++ b/applications/reconstruct/src/main.cpp
@@ -9,13 +9,15 @@
 #include <ftl/config.h>
 #include <ftl/configuration.hpp>
 #include <ftl/depth_camera.hpp>
-#include <ftl/voxel_scene.hpp>
 #include <ftl/rgbd.hpp>
-#include <ftl/virtual_source.hpp>
+#include <ftl/rgbd/virtual.hpp>
 #include <ftl/rgbd/streamer.hpp>
 #include <ftl/slave.hpp>
+#include <ftl/rgbd/group.hpp>
+#include <ftl/threads.hpp>
 
-#include "splat_render.hpp"
+#include "ilw.hpp"
+#include <ftl/render/splat_render.hpp>
 
 #include <string>
 #include <vector>
@@ -36,6 +38,7 @@ using std::string;
 using std::vector;
 using ftl::rgbd::Source;
 using ftl::config::json_t;
+using ftl::rgbd::Channel;
 
 using json = nlohmann::json;
 using std::this_thread::sleep_for;
@@ -90,61 +93,77 @@ static void run(ftl::Configurable *root) {
 		}
 	}
 
-	ftl::voxhash::SceneRep *scene = ftl::create<ftl::voxhash::SceneRep>(root, "voxelhash");
-	ftl::rgbd::Streamer *stream = ftl::create<ftl::rgbd::Streamer>(root, "stream", net);
-	ftl::rgbd::Source *virt = ftl::create<ftl::rgbd::Source>(root, "virtual", net);
-	ftl::render::Splatter *splat = new ftl::render::Splatter(scene);
+	ftl::rgbd::FrameSet scene_A;  // Output of align process
+	ftl::rgbd::FrameSet scene_B;  // Input of render process
 
-	//auto virtimpl = new ftl::rgbd::VirtualSource(virt);
-	//virt->customImplementation(virtimpl);
-	//virtimpl->setScene(scene);
+	//ftl::voxhash::SceneRep *scene = ftl::create<ftl::voxhash::SceneRep>(root, "voxelhash");
+	ftl::rgbd::Streamer *stream = ftl::create<ftl::rgbd::Streamer>(root, "stream", net);
+	ftl::rgbd::VirtualSource *virt = ftl::create<ftl::rgbd::VirtualSource>(root, "virtual");
+	ftl::render::Splatter *splat = ftl::create<ftl::render::Splatter>(root, "renderer", &scene_B);
+	ftl::rgbd::Group group;
+	ftl::ILW *align = ftl::create<ftl::ILW>(root, "merge");
+
+	// Generate virtual camera render when requested by streamer
+	virt->onRender([splat,virt,&scene_B](ftl::rgbd::Frame &out) {
+		virt->setTimestamp(scene_B.timestamp);
+		splat->render(virt, out);
+	});
 	stream->add(virt);
 
 	for (size_t i=0; i<sources.size(); i++) {
 		Source *in = sources[i];
-		in->setChannel(ftl::rgbd::kChanDepth);
-		stream->add(in);
-		scene->addSource(in);
+		in->setChannel(Channel::Depth);
+		group.addSource(in);
 	}
 
-	int active = sources.size();
-	while (ftl::running) {
-		if (active == 0) {
-			LOG(INFO) << "Waiting for sources...";
-			sleep_for(milliseconds(1000));
-		}
-
-		active = 0;
-
-		if (!slave.isPaused()) {
-			// Mark voxels as cleared
-			scene->nextFrame();
-		
-			// Grab, upload frames and allocate voxel blocks
-			active = scene->upload();
-
-			// Make sure previous virtual camera frame has finished rendering
-			//stream->wait();
-			cudaSafeCall(cudaStreamSynchronize(scene->getIntegrationStream()));
+	stream->setLatency(4);  // FIXME: This depends on source!?
+	stream->run();
 
+	bool busy = false;
 
-			// Merge new frames into the voxel structure
-			scene->integrate();
+	group.setLatency(4);
+	group.setName("ReconGroup");
+	group.sync([splat,virt,&busy,&slave,&scene_A,&scene_B,&align](ftl::rgbd::FrameSet &fs) -> bool {
+		//cudaSetDevice(scene->getCUDADevice());
 
-			//LOG(INFO) << "Allocated: " << scene->getOccupiedCount();
-
-			// Remove any redundant voxels
-			scene->garbage();
-
-		} else {
-			active = 1;
+		if (slave.isPaused()) return true;
+		
+		if (busy) {
+			LOG(INFO) << "Group frameset dropped: " << fs.timestamp;
+			return true;
 		}
-
-		splat->render(virt, scene->getIntegrationStream());
-
-		// Start virtual camera rendering and previous frame compression
-		stream->poll();
-	}
+		busy = true;
+
+		// Swap the entire frameset to allow rapid return
+		fs.swapTo(scene_A);
+
+		ftl::pool.push([&scene_B,&scene_A,&busy,&slave,&align](int id) {
+			//cudaSetDevice(scene->getCUDADevice());
+			// TODO: Release frameset here...
+			//cudaSafeCall(cudaStreamSynchronize(scene->getIntegrationStream()));
+
+			UNIQUE_LOCK(scene_A.mtx, lk);
+
+			// Send all frames to GPU, block until done?
+			scene_A.upload(Channel::Colour + Channel::Depth);  // TODO: (Nick) Add scene stream.
+			//align->process(scene_A);
+
+			// TODO: To use second GPU, could do a download, swap, device change,
+			// then upload to other device. Or some direct device-2-device copy.
+			scene_A.swapTo(scene_B);
+			LOG(INFO) << "Align complete... " << scene_A.timestamp;
+			busy = false;
+		});
+		return true;
+	});
+
+	ftl::timer::stop();
+	net->shutdown();
+	delete align;
+	delete splat;
+	delete virt;
+	delete stream;
+	delete net;
 }
 
 int main(int argc, char **argv) {
diff --git a/applications/reconstruct/src/mls.cu b/applications/reconstruct/src/mls.cu
new file mode 100644
index 0000000000000000000000000000000000000000..4de8a07db023d21620f605cd3556913aeafe06cb
--- /dev/null
+++ b/applications/reconstruct/src/mls.cu
@@ -0,0 +1,273 @@
+#include <ftl/cuda_common.hpp>
+#include <ftl/cuda_util.hpp>
+#include <ftl/depth_camera.hpp>
+#include "depth_camera_cuda.hpp"
+
+#include "mls_cuda.hpp"
+
+#define T_PER_BLOCK 16
+#define MINF __int_as_float(0xff800000)
+
+using ftl::voxhash::DepthCameraCUDA;
+using ftl::voxhash::HashData;
+using ftl::voxhash::HashParams;
+using ftl::cuda::TextureObject;
+using ftl::render::SplatParams;
+
+extern __constant__ ftl::voxhash::DepthCameraCUDA c_cameras[MAX_CAMERAS];
+extern __constant__ HashParams c_hashParams;
+
+/// ===== MLS Smooth
+
+/*
+ * Kim, K., Chalidabhongse, T. H., Harwood, D., & Davis, L. (2005).
+ * Real-time foreground-background segmentation using codebook model.
+ * Real-Time Imaging. https://doi.org/10.1016/j.rti.2004.12.004
+ */
+ __device__ float colordiffFloat(const uchar4 &pa, const uchar4 &pb) {
+	const float x_2 = pb.x * pb.x + pb.y * pb.y + pb.z * pb.z;
+	const float v_2 = pa.x * pa.x + pa.y * pa.y + pa.z * pa.z;
+	const float xv_2 = powf(float(pb.x * pa.x + pb.y * pa.y + pb.z * pa.z), 2.0f);
+	const float p_2 = xv_2 / v_2;
+	return sqrt(x_2 - p_2);
+}
+
+__device__ float colordiffFloat2(const uchar4 &pa, const uchar4 &pb) {
+	float3 delta = make_float3((float)pa.x - (float)pb.x, (float)pa.y - (float)pb.y, (float)pa.z - (float)pb.z);
+	return length(delta);
+}
+
+/*
+ * Colour weighting as suggested in:
+ * C. Kuster et al. Spatio-Temporal Geometry Fusion for Multiple Hybrid Cameras using Moving Least Squares Surfaces. 2014.
+ * c = colour distance
+ */
+ __device__ float colourWeighting(float c) {
+	const float h = c_hashParams.m_colourSmoothing;
+	if (c >= h) return 0.0f;
+	float ch = c / h;
+	ch = 1.0f - ch*ch;
+	return ch*ch*ch*ch;
+}
+
+#define WINDOW_RADIUS 5
+
+__device__ float mlsCamera(int cam, const float3 &mPos, uchar4 c1, float3 &wpos) {
+	const ftl::voxhash::DepthCameraCUDA &camera = c_cameras[cam];
+
+	const float3 pf = camera.poseInverse * mPos;
+	float3 pos = make_float3(0.0f, 0.0f, 0.0f);
+	const uint2 screenPos = make_uint2(camera.params.cameraToKinectScreenInt(pf));
+	float weights = 0.0f;
+
+	//#pragma unroll
+	for (int v=-WINDOW_RADIUS; v<=WINDOW_RADIUS; ++v) {
+		for (int u=-WINDOW_RADIUS; u<=WINDOW_RADIUS; ++u) {
+			//if (screenPos.x+u < width && screenPos.y+v < height) {	//on screen
+				float depth = tex2D<float>(camera.depth, screenPos.x+u, screenPos.y+v);
+				const float3 camPos = camera.params.kinectDepthToSkeleton(screenPos.x+u, screenPos.y+v, depth);
+				float weight = ftl::cuda::spatialWeighting(length(pf - camPos), c_hashParams.m_spatialSmoothing);
+
+				if (weight > 0.0f) {
+					uchar4 c2 = tex2D<uchar4>(camera.colour, screenPos.x+u, screenPos.y+v);
+					weight *= colourWeighting(colordiffFloat2(c1,c2));
+
+					if (weight > 0.0f) {
+						wpos += weight* (camera.pose * camPos);
+						weights += weight;
+					}
+				}			
+			//}
+		}
+	}
+
+	//wpos += (camera.pose * pos);
+
+	return weights;
+}
+
+__device__ float mlsCameraNoColour(int cam, const float3 &mPos, uchar4 c1, const float4 &norm, float3 &wpos, float h) {
+	const ftl::voxhash::DepthCameraCUDA &camera = c_cameras[cam];
+
+	const float3 pf = camera.poseInverse * mPos;
+	float3 pos = make_float3(0.0f, 0.0f, 0.0f);
+	const uint2 screenPos = make_uint2(camera.params.cameraToKinectScreenInt(pf));
+	float weights = 0.0f;
+
+	//#pragma unroll
+	for (int v=-WINDOW_RADIUS; v<=WINDOW_RADIUS; ++v) {
+		for (int u=-WINDOW_RADIUS; u<=WINDOW_RADIUS; ++u) {
+			//if (screenPos.x+u < width && screenPos.y+v < height) {	//on creen
+				float depth = tex2D<float>(camera.depth, screenPos.x+u, screenPos.y+v);
+				const float3 camPos = camera.params.kinectDepthToSkeleton(screenPos.x+u, screenPos.y+v, depth);
+
+				// TODO:(Nick) dot product of normals < 0 means the point
+				// should be ignored with a weight of 0 since it is facing the wrong direction
+				// May be good to simply weight using the dot product to give
+				// a stronger weight to those whose normals are closer
+
+				float weight = ftl::cuda::spatialWeighting(length(pf - camPos), h);
+
+				if (weight > 0.0f) {
+					float4 n2 = tex2D<float4>(camera.normal, screenPos.x+u, screenPos.y+v);
+					if (dot(make_float3(norm), make_float3(n2)) > 0.0f) {
+
+						uchar4 c2 = tex2D<uchar4>(camera.colour, screenPos.x+u, screenPos.y+v);
+
+						if (colourWeighting(colordiffFloat2(c1,c2)) > 0.0f) {
+							pos += weight*camPos; // (camera.pose * camPos);
+							weights += weight;
+						}
+					}
+				}			
+			//}
+		}
+	}
+
+	if (weights > 0.0f) wpos += (camera.pose * (pos / weights)) * weights;
+
+	return weights;
+}
+
+
+__global__ void mls_smooth_kernel(ftl::cuda::TextureObject<float4> output, HashParams hashParams, int numcams, int cam) {
+	const unsigned int x = blockIdx.x*blockDim.x + threadIdx.x;
+	const unsigned int y = blockIdx.y*blockDim.y + threadIdx.y;
+
+	const int width = output.width();
+	const int height = output.height();
+
+	const DepthCameraCUDA &mainCamera = c_cameras[cam];
+
+	if (x < width && y < height) {
+
+		const float depth = tex2D<float>(mainCamera.depth, x, y);
+		const uchar4 c1 = tex2D<uchar4>(mainCamera.colour, x, y);
+		const float4 norm = tex2D<float4>(mainCamera.normal, x, y);
+		//if (x == 400 && y == 200) printf("NORMX: %f\n", norm.x);
+
+		float3 wpos = make_float3(0.0f);
+		float3 wnorm = make_float3(0.0f);
+		float weights = 0.0f;
+
+		if (depth >= mainCamera.params.m_sensorDepthWorldMin && depth <= mainCamera.params.m_sensorDepthWorldMax) {
+			float3 mPos = mainCamera.pose * mainCamera.params.kinectDepthToSkeleton(x, y, depth);
+
+			if ((!(hashParams.m_flags & ftl::voxhash::kFlagClipping)) || (mPos.x > hashParams.m_minBounds.x && mPos.x < hashParams.m_maxBounds.x &&
+					mPos.y > hashParams.m_minBounds.y && mPos.y < hashParams.m_maxBounds.y &&
+					mPos.z > hashParams.m_minBounds.z && mPos.z < hashParams.m_maxBounds.z)) {
+
+				if (hashParams.m_flags & ftl::voxhash::kFlagMLS) {
+					for (uint cam2=0; cam2<numcams; ++cam2) {
+						//if (cam2 == cam) weights += mlsCameraNoColour(cam2, mPos, c1, wpos, c_hashParams.m_spatialSmoothing*0.1f); //weights += 0.5*mlsCamera(cam2, mPos, c1, wpos);
+						weights += mlsCameraNoColour(cam2, mPos, c1, norm, wpos, c_hashParams.m_spatialSmoothing); //*((cam == cam2)? 0.1f : 5.0f));
+
+						// Previous approach
+						//if (cam2 == cam) continue;
+						//weights += mlsCameraBest(cam2, mPos, c1, wpos);
+					}
+					wpos /= weights;
+				} else {
+					weights = 1000.0f;
+					wpos = mPos;
+				} 
+
+				//output(x,y) = (weights >= hashParams.m_confidenceThresh) ? make_float4(wpos, 0.0f) : make_float4(MINF,MINF,MINF,MINF);
+
+				if (weights >= hashParams.m_confidenceThresh) output(x,y) = make_float4(wpos, 0.0f);
+
+				//const uint2 screenPos = make_uint2(mainCamera.params.cameraToKinectScreenInt(mainCamera.poseInverse * wpos));
+				//if (screenPos.x < output.width() && screenPos.y < output.height()) {
+				//	output(screenPos.x,screenPos.y) = (weights >= hashParams.m_confidenceThresh) ? make_float4(wpos, 0.0f) : make_float4(MINF,MINF,MINF,MINF);
+				//}
+			}
+		}
+	}
+}
+
+void ftl::cuda::mls_smooth(TextureObject<float4> &output, const HashParams &hashParams, int numcams, int cam, cudaStream_t stream) {
+	const dim3 gridSize((output.width() + T_PER_BLOCK - 1)/T_PER_BLOCK, (output.height() + T_PER_BLOCK - 1)/T_PER_BLOCK);
+	const dim3 blockSize(T_PER_BLOCK, T_PER_BLOCK);
+
+	mls_smooth_kernel<<<gridSize, blockSize, 0, stream>>>(output, hashParams, numcams, cam);
+
+#ifdef _DEBUG
+	cudaSafeCall(cudaDeviceSynchronize());
+#endif
+}
+
+
+// ===== Render Depth using MLS ================================================
+
+#define MAX_UPSAMPLE 5
+#define SAMPLE_BUFFER ((2*MAX_UPSAMPLE+1)*(2*MAX_UPSAMPLE+1))
+#define WARP_SIZE 32
+#define BLOCK_WIDTH 4
+#define MLS_RADIUS 5
+#define MLS_WIDTH (2*MLS_RADIUS+1)
+#define MLS_SAMPLES (MLS_WIDTH*MLS_WIDTH)
+
+__global__ void mls_render_depth_kernel(const TextureObject<int> input, TextureObject<int> output, SplatParams params, int numcams) {
+	/*const unsigned int x = blockIdx.x*blockDim.x + threadIdx.x;
+	const unsigned int y = blockIdx.y*blockDim.y + threadIdx.y;
+
+	const int width = output.width();
+	const int height = output.height();
+
+	if (x < width && y < height) {
+
+		const float depth = tex2D<float>(mainCamera.depth, x, y);
+		const uchar4 c1 = tex2D<uchar4>(mainCamera.colour, x, y);
+		const float4 norm = tex2D<float4>(mainCamera.normal, x, y);
+		//if (x == 400 && y == 200) printf("NORMX: %f\n", norm.x);
+
+		float3 wpos = make_float3(0.0f);
+		float3 wnorm = make_float3(0.0f);
+		float weights = 0.0f;
+
+		if (depth >= mainCamera.params.m_sensorDepthWorldMin && depth <= mainCamera.params.m_sensorDepthWorldMax) {
+			float3 mPos = mainCamera.pose * mainCamera.params.kinectDepthToSkeleton(x, y, depth);
+
+			if ((!(hashParams.m_flags & ftl::voxhash::kFlagClipping)) || (mPos.x > hashParams.m_minBounds.x && mPos.x < hashParams.m_maxBounds.x &&
+					mPos.y > hashParams.m_minBounds.y && mPos.y < hashParams.m_maxBounds.y &&
+					mPos.z > hashParams.m_minBounds.z && mPos.z < hashParams.m_maxBounds.z)) {
+
+				if (hashParams.m_flags & ftl::voxhash::kFlagMLS) {
+					for (uint cam2=0; cam2<numcams; ++cam2) {
+						//if (cam2 == cam) weights += mlsCameraNoColour(cam2, mPos, c1, wpos, c_hashParams.m_spatialSmoothing*0.1f); //weights += 0.5*mlsCamera(cam2, mPos, c1, wpos);
+						weights += mlsCameraNoColour(cam2, mPos, c1, norm, wpos, c_hashParams.m_spatialSmoothing); //*((cam == cam2)? 0.1f : 5.0f));
+
+						// Previous approach
+						//if (cam2 == cam) continue;
+						//weights += mlsCameraBest(cam2, mPos, c1, wpos);
+					}
+					wpos /= weights;
+				} else {
+					weights = 1000.0f;
+					wpos = mPos;
+				} 
+
+				//output(x,y) = (weights >= hashParams.m_confidenceThresh) ? make_float4(wpos, 0.0f) : make_float4(MINF,MINF,MINF,MINF);
+
+				//if (weights >= hashParams.m_confidenceThresh) output(x,y) = make_float4(wpos, 0.0f);
+
+				const uint2 screenPos = make_uint2(mainCamera.params.cameraToKinectScreenInt(mainCamera.poseInverse * wpos));
+				if (screenPos.x < output.width() && screenPos.y < output.height()) {
+					output(screenPos.x,screenPos.y) = (weights >= hashParams.m_confidenceThresh) ? make_float4(wpos, 0.0f) : make_float4(MINF,MINF,MINF,MINF);
+				}
+			}
+		}
+	}*/
+}
+
+
+void ftl::cuda::mls_render_depth(const TextureObject<int> &input, TextureObject<int> &output, const SplatParams &params, int numcams, cudaStream_t stream) {
+	const dim3 gridSize((output.width() + T_PER_BLOCK - 1)/T_PER_BLOCK, (output.height() + T_PER_BLOCK - 1)/T_PER_BLOCK);
+	const dim3 blockSize(T_PER_BLOCK, T_PER_BLOCK);
+
+	mls_render_depth_kernel<<<gridSize, blockSize, 0, stream>>>(input, output, params, numcams);
+
+#ifdef _DEBUG
+	cudaSafeCall(cudaDeviceSynchronize());
+#endif
+}
diff --git a/applications/reconstruct/src/ray_cast_sdf.cu b/applications/reconstruct/src/ray_cast_sdf.cu
index a43b608429b1fad1ac160b188c9be6b084274154..10fd3e0b7b84ca694432abc7dc68fc513ad483eb 100644
--- a/applications/reconstruct/src/ray_cast_sdf.cu
+++ b/applications/reconstruct/src/ray_cast_sdf.cu
@@ -1,5 +1,5 @@
 
-#include <cuda_runtime.h>
+//#include <cuda_runtime.h>
 
 #include <ftl/cuda_matrix_util.hpp>
 
diff --git a/applications/reconstruct/src/registration.cpp b/applications/reconstruct/src/registration.cpp
index 5b828242e168e08c9524e02470c7d187ff4a1279..dab5235445a61460c3f65fd35f75ab694cdb8128 100644
--- a/applications/reconstruct/src/registration.cpp
+++ b/applications/reconstruct/src/registration.cpp
@@ -1,26 +1,8 @@
 #include <ftl/registration.hpp>
-
-#ifdef HAVE_PCL
-
+#include <fstream>
 #define LOGURU_WITH_STREAMS 1
 #include <loguru.hpp>
-#include <pcl/common/transforms.h>
-
-#include <pcl/registration/transformation_estimation_svd.h>
-#include <pcl/registration/transformation_estimation_svd_scale.h>
-
-#include <pcl/segmentation/sac_segmentation.h>
-#include <pcl/sample_consensus/method_types.h>
-#include <pcl/sample_consensus/model_types.h>
-#include <pcl/filters/project_inliers.h>
-#include <pcl/ModelCoefficients.h>
-
-#include <pcl/io/pcd_io.h>
 
-#include <pcl/registration/transformation_validation.h>
-#include <pcl/registration/transformation_validation_euclidean.h>
-#include <pcl/common/geometry.h>
-//#include <pcl/registration/icp_nl.h>
 
 namespace ftl {
 namespace registration {
@@ -34,10 +16,6 @@ using std::pair;
 using std::map;
 using std::optional;
 
-using pcl::PointCloud;
-using pcl::PointXYZ;
-using pcl::PointXYZRGB;
-
 using cv::Mat;
 using Eigen::Matrix4f;
 using Eigen::Matrix4d;
@@ -86,489 +64,6 @@ bool saveTransformations(const string &path, map<string, Matrix4d> &data) {
 	return true;
 }
 
-// todo template: fitPlane<typename T>(PointCloud<T> cloud_in, PointCloud<T> cloud_out)
-//
-// Fit calibration pattern into plane using RANSAC + project points
-//
-pcl::ModelCoefficients::Ptr fitPlane(PointCloud<PointXYZ>::Ptr cloud_in, float distance_threshold=5.0) {
-	// TODO: include pattern in model (find best alignment of found points and return transformed reference?)
-	
-	pcl::ModelCoefficients::Ptr coefficients(new pcl::ModelCoefficients);
-	pcl::PointIndices::Ptr inliers(new pcl::PointIndices);
-	
-	// Estimate plane with RANSAC
-	pcl::SACSegmentation<PointXYZ> seg;
-	
-	seg.setOptimizeCoefficients(true);
-	seg.setModelType(pcl::SACMODEL_PLANE);
-	seg.setMethodType(pcl::SAC_RANSAC);
-	seg.setDistanceThreshold(distance_threshold);
-	seg.setInputCloud(cloud_in);
-	seg.segment(*inliers, *coefficients);
-	
-	return coefficients;
-}
-
-float fitPlaneError(PointCloud<PointXYZ>::Ptr cloud_in, float distance_threshold=5.0) {
-	auto coefficients = fitPlane(cloud_in, distance_threshold);
-	PointCloud<PointXYZ> cloud_proj;
-	
-	// Project points into plane
-	pcl::ProjectInliers<PointXYZ> proj;
-	proj.setModelType(pcl::SACMODEL_PLANE);
-	proj.setInputCloud(cloud_in);
-	proj.setModelCoefficients(coefficients);
-	proj.filter(cloud_proj); 
-	
-	CHECK(cloud_in->size() == cloud_proj.size());
-	
-	// todo: which error score is suitable? (using MSE)
-	float score = 0.0;
-	for(size_t i = 0; i < cloud_proj.size(); i++) {
-		float d = pcl::geometry::distance(cloud_in->points[i], cloud_proj.points[i]);
-		score += d * d;
-	}
-	
-	return (score / cloud_proj.size()) * 10000000.0f;
-}
-
-//template<typename T = PointXYZ> typename
-PointCloud<PointXYZ>::Ptr cornersToPointCloud(const vector<cv::Point2f> &corners, const Mat &depth, const Camera &p) {
-	
-	int corners_len = corners.size();
-	vector<cv::Vec3f> points(corners_len);
-
-	const double CX = p.cx;
-	const double CY = p.cy;
-	const double FX = p.fx;
-	const double FY = p.fy;
-	
-	// Output point cloud
-	PointCloud<PointXYZ>::Ptr cloud(new PointCloud<PointXYZ>);
-	cloud->width = corners_len;
-	cloud->height = 1;
-	
-	// Follows cv::reprojectImageTo3D(..)
-	// https://github.com/opencv/opencv/blob/371bba8f54560b374fbcd47e7e02f015ac4969ad/modules/calib3d/src/calibration.cpp#L2998
-	// documentation suggests using cv::perspectiveTransform(...) with sparse set of points
-	
-	for (int i = 0; i < corners_len; i++) {
-		double x = corners[i].x;
-		double y = corners[i].y;
-		double d = depth.at<float>((int) y, (int) x); // * 1000.0f; // todo: better estimation
-		
-		//cv::Vec4d homg_pt = Q_ * cv::Vec4d(x, y, d, 1.0);
-		//cv::Vec3d p = cv::Vec3d(homg_pt.val) / homg_pt[3];
-
-		PointXYZ point;
-		point.x = (((double)x + CX) / FX) * d; // / 1000.0f;
-		point.y = (((double)y + CY) / FY) * d; // / 1000.0f;
-		point.z = d;
-		
-		cloud->push_back(point);
-	}
-	
-	return cloud;
-}
-
-bool findChessboardCorners(Mat &rgb, const Mat &depth, const Camera &p, const cv::Size pattern_size, PointCloud<PointXYZ>::Ptr &out, float error_threshold) {
-	vector<cv::Point2f> corners(pattern_size.width * pattern_size.height);
-
-#if CV_VERSION_MAJOR >= 4
-	bool retval = cv::findChessboardCornersSB(rgb, pattern_size, corners);
-#else
-	bool retval = cv::findChessboardCorners(rgb, pattern_size, corners);
-#endif
-
-	cv::drawChessboardCorners(rgb, pattern_size, Mat(corners), retval);
-	if (!retval) { return false; }
-	
-	auto corners_cloud = cornersToPointCloud(corners, depth, p);
-	// simple check that the values make some sense
-	float error = fitPlaneError(corners_cloud, error_threshold); // should use different parameter?
-	LOG(INFO) << "MSE against estimated plane: " << error;
-	
-	if (error > error_threshold) {
-		LOG(WARNING) << "too high error score for calibration pattern, threshold " << error_threshold;
-		return false;
-	}
-	
-	if (out) { *out += *corners_cloud; } // if cloud is valid, add the points
-	else { out = corners_cloud; }
-	return true;
-}
-
-Eigen::Matrix4f findTransformation(vector<PointCloud<PointXYZ>::Ptr> clouds_source, vector<PointCloud<PointXYZ>::Ptr> clouds_target) {
-	size_t n_clouds = clouds_source.size();
-	
-	Eigen::Matrix4f T, T_tmp, T_new;
-	T.setIdentity();
-	
-	if ((clouds_source.size() != clouds_target.size()) || (n_clouds == 0)) {
-		LOG(ERROR) << "Input vectors have invalid sizes: clouds_source " << clouds_source.size()
-					<< ", clouds_target " << clouds_target.size() << ", transformation can not be estimated";
-		
-		return T; // identity
-	}
-	
-	// corresponding points have same indices (!!!)
-	int n_points = clouds_source[0]->width * clouds_source[0]->height;
-	vector<int> idx(n_points);
-	for (int i = 0; i < n_points; i++) { idx[i] = i; }
-	
-	pcl::registration::TransformationValidationEuclidean<PointXYZ, PointXYZ> validate;
-	pcl::registration::TransformationEstimationSVD<PointXYZ,PointXYZ> svd;
-	
-	double score_prev = std::numeric_limits<float>::max();
-	
-	for (size_t i = 0; i < n_clouds; ++i) {
-		PointCloud<PointXYZ> source;
-		PointCloud<PointXYZ> target = *clouds_target[i];
-		
-		pcl::transformPointCloud(*clouds_source[i], source, T);
-		svd.estimateRigidTransformation(source, idx, target, idx, T_new);
-		
-		// calculate new transformation
-		T_tmp = T_new * T;
-		
-		// score new transformation
-		double score = 0.0;
-		for (size_t j = 0; j < n_clouds; ++j) {
-			score += validate.validateTransformation(clouds_source[j], clouds_target[j], T_tmp); // CHECK Is use of T here a mistake??
-		}
-		score /= n_clouds;
-		
-		// if score doesn't improve, do not use as T, otherwise update T and score
-		if (score < score_prev) {
-			T = T_tmp;
-			score_prev = score;
-		}
-		
-		LOG(INFO) << "Validation score: " << score;
-	}
-	
-	return T;
-}
-
-Registration::Registration(nlohmann::json &config) :
-	ftl::Configurable(config) {
-	target_source_ = get<string>("targetsource");
-	if (!target_source_) {
-		LOG(WARNING) << "targetsource not set";
-	}
-}
-
-Source* Registration::getSource(size_t idx) {
-	return sources_[idx];
-}
-
-bool Registration::isTargetSourceSet() {
-	return (bool) target_source_;
-}
-
-bool Registration::isTargetSourceFound() {
-	for (Source* source : sources_ ) {
-		if (isTargetSource(source)) return true;
-	}
-	return false;
-}
-
-bool Registration::isTargetSource(Source *source) {
-	if (target_source_) { return source->getID() == *target_source_; }
-	return false;
-}
-
-bool Registration::isTargetSource(size_t idx) {
-	if (idx >= sources_.size()) return false; // assert
-	return isTargetSource(sources_[idx]);
-}
-
-size_t Registration::getTargetSourceIdx() {
-	if (!target_source_) return 0;
-	for (size_t idx = 0; idx < sources_.size(); ++idx) {
-		if (isTargetSource(sources_[idx])) return idx;
-	}
-
-	return 0;
-}
-
-void Registration::addSource(Source *source) {
-	// TODO: check that source is not already included
-	sources_.push_back(source);
-}
-
-/**
- * @param	adjacency matrix
- * @param	index of starting vertex
- * @param	(out) edges connecting each level
- * @returns	true if graph connected (all vertices visited), otherwise false
- * 
- * Breadth First Search
- */
-bool isConnected(vector<vector<bool>> matrix, size_t start_idx, vector<vector<pair<size_t, size_t>>> &edges) {
-vector<bool> visited(matrix.size(), false);
-	DCHECK(start_idx < matrix.size());
-
-	edges.clear();
-	vector<size_t> level { start_idx };
-
-	visited[start_idx] = true;
-	size_t visited_count = 1;
-	
-	while(level.size() != 0) {
-		vector<size_t> level_prev = level;
-		level = {};
-
-		vector<pair<size_t, size_t>> new_edges;
-
-		for (size_t current : level_prev) {
-		for (size_t i = 0; i < matrix.size(); ++i) {
-			if (matrix[current][i] && !visited[i]) {
-				visited[i] = true;
-				visited_count += 1;
-				level.push_back(i);
-				// could also save each level's vertices
-
-				new_edges.push_back(pair(current, i));
-			}
-		}}
-		if (new_edges.size() > 0) edges.push_back(new_edges);
-	}
-
-	return visited_count == matrix.size();
-}
-
-bool isConnected(vector<vector<bool>> matrix, size_t start_idx = 0) {
-	vector<vector<pair<size_t, size_t>>> edges;
-	return isConnected(matrix, start_idx, edges);
-}
-
-/**
- * @param	Adjacency matrix
- * @returns	Vector containing degree of each vertex
-*/
-vector<uint> verticleDegrees(vector<vector<bool>> matrix) {
-	vector<uint> res(matrix.size(), 0);
-	for (size_t i = 0; i < matrix.size(); ++i) {
-	for (size_t j = 0; j < matrix.size(); ++j) {
-		if (matrix[i][j]) res[i] = res[i] + 1;
-	}}
-	return res;
-}
-
-bool Registration::connectedVisibility() {
-	return isConnected(visibility_, getTargetSourceIdx());
-}
-
-void Registration::resetVisibility() {
-	visibility_ = vector(sources_.size(), vector<bool>(sources_.size(), false));
-}
-
-void Registration::run() {
-	resetVisibility();
-
-	do {
-		vector<bool> visible(sources_.size(), false);
-
-		for (size_t i = 0; i < sources_.size(); ++i) {
-			bool retval = findFeatures(sources_[i], i);
-			visible[i] = retval;
-		}
-
-		for (size_t i = 0; i < visible.size(); ++i) {
-		for (size_t j = 0; j < visible.size(); ++j) {
-			bool val = visible[i] && visible[j];
-			visibility_[i][j] = visibility_[i][j] || val;
-			visibility_[j][i] = visibility_[j][i] || val;
-		}}
-	}
-	while(processData());
-}
-
-bool Registration::findTransformations(map<string, Matrix4f> &data) {
-	vector<Matrix4f> T;
-	data.clear();
-
-	if (!findTransformations(T)) return false;
-	for (size_t i = 0; i < sources_.size(); ++i) {
-		data[sources_[i]->getURI()] = T[i];
-	}
-	return true;
-}
-
-ChessboardRegistration* ChessboardRegistration::create(nlohmann::json &config) {
-	if (config.value<bool>("chain", false)) {
-		return new ChessboardRegistrationChain(config);
-	}
-	else {
-		return new ChessboardRegistration(config);
-	}
-}
-
-ChessboardRegistration::ChessboardRegistration(nlohmann::json &config) :
-	Registration(config) {
-	
-	auto patternsize = get<vector<int>>("patternsize");
-	if (!patternsize) { LOG(FATAL) << "Registration run enabled but pattern size not set"; }
-	pattern_size_ = cv::Size((*patternsize)[0], (*patternsize)[1]);
-	
-	auto maxerror = get<float>("maxerror");
-	if (!maxerror) { LOG(WARNING) << "maxerror not set"; }
-	auto delay = get<int>("delay");
-	if (!delay) { LOG(INFO) << "delay not set in configuration"; }
-	auto iter = get<int>("iterations");
-	if (!iter) { LOG(INFO) << "iterations not set in configuration"; }
-	auto chain = get<bool>("chain");
-	if (!chain) { LOG(INFO) << "input chaining disabled"; }
-	else { LOG(INFO) << "Input chaining enabled"; }
-
-	error_threshold_ = maxerror ? *maxerror : std::numeric_limits<float>::infinity();
-	iter_ = iter ? *iter : 10;
-	delay_ = delay ? *delay : 50;
-}
-
-void ChessboardRegistration::run() {
-	if (!isTargetSourceFound()) {
-		LOG(WARNING) << "targetsource not found in sources";
-	}
-
-	if (data_.size() != getSourcesCount()) {
-		data_ = vector<vector<optional<PointCloud<PointXYZ>::Ptr>>>(getSourcesCount());
-	}
-	iter_remaining_ = iter_;
-
-	// TODO: Move GUI elsewhere. Also applies to processData() and findFeatures()
-	for (size_t i = 0; i < getSourcesCount(); ++i) { 
-		cv::namedWindow("Registration: " + getSource(i)->getID(),
-						cv::WINDOW_KEEPRATIO|cv::WINDOW_NORMAL);
-	}
-
-	Registration::run();
-
-	for (size_t i = 0; i < getSourcesCount(); ++i) { 
-		cv::destroyWindow("Registration: " + getSource(i)->getID());
-	}
-}
-
-bool ChessboardRegistration::findFeatures(Source *source, size_t idx) {
-	optional<PointCloud<PointXYZ>::Ptr> result;
-	PointCloud<PointXYZ>::Ptr cloud(new PointCloud<PointXYZ>);
-
-	Mat rgb, depth;
-	source->grab();
-	source->getFrames(rgb, depth);
-
-	bool retval = findChessboardCorners(rgb, depth, source->parameters(), pattern_size_, cloud, error_threshold_);
-	if (retval) {
-		result.emplace(cloud);
-	}
-	data_[idx].push_back(result);
-	
-	cv::imshow("Registration: " + source->getID(), rgb);
-
-	return retval;
-}
-
-bool ChessboardRegistration::processData() {
-	bool retval = connectedVisibility();
-	resetVisibility();
-
-	if (retval) {
-		iter_remaining_--;
-	}
-	else{
-		LOG(INFO) << "Pattern not visible in all inputs";
-		for (auto &sample : data_) { sample.pop_back(); }
-	}
-
-	//std::this_thread::sleep_for(std::chrono::milliseconds(delay_));
-	cv::waitKey(delay_); // OpenCV GUI doesn't show otherwise
-
-	return iter_remaining_ > 0;
-}
-
-bool ChessboardRegistration::findTransformations(vector<Matrix4f> &data) {
-	data.clear();
-	vector<bool> status(getSourcesCount(), false);
-	size_t idx_target = getTargetSourceIdx();
-
-	for (size_t idx = 0; idx < getSourcesCount(); ++idx) {
-		Matrix4f T;
-		if (idx == idx_target) {
-			T.setIdentity(); 
-		}
-		else {
-			vector<PointCloud<PointXYZ>::Ptr> d;
-			vector<PointCloud<PointXYZ>::Ptr> d_target;
-			d.reserve(iter_);
-			d_target.reserve(iter_);
-			
-			for (size_t i = 0; i < iter_; ++i) {
-				auto val = data_[idx][i];
-				auto val_target = data_[idx_target][i];
-				if (val && val_target) {
-					d.push_back(*val);
-					d_target.push_back(*val_target);
-				}
-			}
-			T = findTransformation(d, d_target); 
-		}
-		data.push_back(T);
-	}
-	return true;
-}
-
-ChessboardRegistrationChain::ChessboardRegistrationChain(nlohmann::json &config) :
-							ChessboardRegistration(config) {
-		error_threshold_ = std::numeric_limits<float>::infinity();
-}
-
-bool ChessboardRegistrationChain::processData() {
-	for (auto &sample : data_ ) { sample.clear(); }
-	bool retval = isConnected(visibility_, getTargetSourceIdx(), edges_);
-	
-	if (retval) {
-		LOG(INFO) << "Chain complete, depth: " << edges_.size();
-		return false;
-	}
-	else{
-		LOG(5) << "Chain not complete ";
-	}
-
-	return true;
-}
-
-bool ChessboardRegistrationChain::findTransformations(vector<Matrix4f> &data) {
-	// TODO	Change to group registration: register all sources which have visibility
-	//		to the target source in chain.
-
-	LOG(INFO) << "Running pairwise registration";
-	data = vector<Matrix4f>(getSourcesCount(), Matrix4f::Identity());
-
-	for (vector<pair<size_t, size_t>> level : edges_) {
-		for (pair<size_t, size_t> edge : level) {
-			LOG(INFO) 	<< "Registering source "
-						<< getSource(edge.second)->getID() << " to source"
-						<< getSource(edge.first)->getID();
-			
-			nlohmann::json conf(getConfig());
-			conf["targetsource"] = getSource(edge.first)->getID();
-			conf["chain"] = false;
-
-			vector<Matrix4f> result;
-			ChessboardRegistration reg(conf);
-			reg.addSource(getSource(edge.first));
-			reg.addSource(getSource(edge.second));
-			reg.run();
-			if (!reg.findTransformations(result)) { return false; }
-			data[edge.second] = data[edge.first] * result[1];
-		}
-	}
-	
-	return true;
-}
 
 } // namespace registration
 } // namespace ftl
-
-#endif // HAVE_PCL
\ No newline at end of file
diff --git a/applications/reconstruct/src/scene_rep_hash_sdf.cu b/applications/reconstruct/src/scene_rep_hash_sdf.cu
index 247247b6cc5279186f9f9bdc81bbe627ab0621ea..4750d3e7ed4f7aeb68875e0b5050d76efed5b715 100644
--- a/applications/reconstruct/src/scene_rep_hash_sdf.cu
+++ b/applications/reconstruct/src/scene_rep_hash_sdf.cu
@@ -2,8 +2,8 @@
 
 //#include <cutil_inline.h>
 //#include <cutil_math.h>
-#include <vector_types.h>
-#include <cuda_runtime.h>
+//#include <vector_types.h>
+//#include <cuda_runtime.h>
 
 #include <ftl/cuda_matrix_util.hpp>
 
diff --git a/applications/reconstruct/src/splat_params.hpp b/applications/reconstruct/src/splat_params.hpp
deleted file mode 100644
index e38e4447286bf6c70f6c753deed35154b6aafd8a..0000000000000000000000000000000000000000
--- a/applications/reconstruct/src/splat_params.hpp
+++ /dev/null
@@ -1,26 +0,0 @@
-#ifndef _FTL_RENDER_SPLAT_PARAMS_HPP_
-#define _FTL_RENDER_SPLAT_PARAMS_HPP_
-
-#include <ftl/cuda_util.hpp>
-#include <ftl/cuda_matrix_util.hpp>
-#include <ftl/depth_camera_params.hpp>
-
-namespace ftl {
-namespace render {
-
-static const uint kShowBlockBorders = 0x0001;
-
-struct __align__(16) SplatParams {
-	float4x4 m_viewMatrix;
-	float4x4 m_viewMatrixInverse;
-
-	uint m_flags;
-	float voxelSize;
-
-	DepthCameraParams camera;
-};
-
-}
-}
-
-#endif
diff --git a/applications/reconstruct/src/splat_render.cpp b/applications/reconstruct/src/splat_render.cpp
deleted file mode 100644
index b1c39f6493a8b1849df664c092bb15737b76165a..0000000000000000000000000000000000000000
--- a/applications/reconstruct/src/splat_render.cpp
+++ /dev/null
@@ -1,126 +0,0 @@
-#include "splat_render.hpp"
-#include "splat_render_cuda.hpp"
-#include "compactors.hpp"
-#include "depth_camera_cuda.hpp"
-
-using ftl::render::Splatter;
-
-Splatter::Splatter(ftl::voxhash::SceneRep *scene) : scene_(scene) {
-
-}
-
-Splatter::~Splatter() {
-
-}
-
-static Eigen::Matrix4f adjustPose(const Eigen::Matrix4f &pose, float baseline) {
-	Eigen::Affine3f transform(Eigen::Translation3f(baseline,0.0f,0.0f));
-	Eigen::Matrix4f matrix = pose;
-	Eigen::Matrix4f rmat = pose;
-	rmat(0,3) = 0.0f;
-	rmat(1,3) = 0.0f;
-	rmat(2,3) = 0.0f;
-	Eigen::Matrix4f tmat = transform.matrix();
-	tmat(0,0) = 0.0f;
-	tmat(1,1) = 0.0f;
-	tmat(2,2) = 0.0f;
-	tmat(3,3) = 0.0f;
-	return (tmat * pose) + pose;
-}
-
-void Splatter::render(ftl::rgbd::Source *src, cudaStream_t stream) {
-	if (!src->isReady()) return;
-
-	const auto &camera = src->parameters();
-
-	cudaSafeCall(cudaSetDevice(scene_->getCUDADevice()));
-
-	// Create buffers if they don't exists
-	if ((unsigned int)depth1_.width() != camera.width || (unsigned int)depth1_.height() != camera.height) {
-		depth1_ = ftl::cuda::TextureObject<int>(camera.width, camera.height);
-	}
-	if ((unsigned int)colour1_.width() != camera.width || (unsigned int)colour1_.height() != camera.height) {
-		colour1_ = ftl::cuda::TextureObject<uchar4>(camera.width, camera.height);
-	}
-	if ((unsigned int)depth2_.width() != camera.width || (unsigned int)depth2_.height() != camera.height) {
-		depth2_ = ftl::cuda::TextureObject<float>(camera.width, camera.height);
-	}
-	if ((unsigned int)colour2_.width() != camera.width || (unsigned int)colour2_.height() != camera.height) {
-		colour2_ = ftl::cuda::TextureObject<uchar4>(camera.width, camera.height);
-	}
-
-	// Parameters object to pass to CUDA describing the camera
-	SplatParams params;
-	params.m_flags = 0;
-
-	// Adjust pose to left eye position
-	Eigen::Matrix4f matrix =  adjustPose(src->getPose().cast<float>(), -camera.baseline/2.0f);
-	params.m_viewMatrix = MatrixConversion::toCUDA(matrix.inverse());
-	params.m_viewMatrixInverse = MatrixConversion::toCUDA(matrix);
-
-	//params.m_viewMatrix = MatrixConversion::toCUDA(src->getPose().cast<float>().inverse());
-	//params.m_viewMatrixInverse = MatrixConversion::toCUDA(src->getPose().cast<float>());
-	params.voxelSize = scene_->getHashParams().m_virtualVoxelSize;
-	params.camera.flags = 0;
-	params.camera.fx = camera.fx;
-	params.camera.fy = camera.fy;
-	params.camera.mx = -camera.cx;
-	params.camera.my = -camera.cy;
-	params.camera.m_imageWidth = camera.width;
-	params.camera.m_imageHeight = camera.height;
-	params.camera.m_sensorDepthWorldMax = camera.maxDepth;
-	params.camera.m_sensorDepthWorldMin = camera.minDepth;
-
-	//ftl::cuda::compactifyAllocated(scene_->getHashData(), scene_->getHashParams(), stream);
-	//LOG(INFO) << "Occupied: " << scene_->getOccupiedCount();
-
-	if (scene_->value("voxels", false)) {
-		// TODO:(Nick) Stereo for voxel version
-		ftl::cuda::isosurface_point_image(scene_->getHashData(), depth1_, params, stream);
-		ftl::cuda::splat_points(depth1_, depth2_, params, stream);
-		ftl::cuda::dibr(depth2_, colour1_, scene_->cameraCount(), params, stream);
-		src->writeFrames(colour1_, depth2_, stream);
-	} else {
-		//ftl::cuda::clear_colour(colour1_, stream);
-		ftl::cuda::clear_depth(depth1_, stream);
-		ftl::cuda::clear_depth(depth2_, stream);
-		ftl::cuda::dibr(depth1_, colour1_, scene_->cameraCount(), params, stream);
-		//ftl::cuda::hole_fill(depth1_, depth2_, params.camera, stream);
-
-		// Splat the depth from first DIBR to expand the points
-		ftl::cuda::splat_points(depth1_, depth2_, params, stream);
-
-		// Alternative to above...
-		//ftl::cuda::mls_resample(depth1_, colour1_, depth2_, scene_->getHashParams(), scene_->cameraCount(), params, stream);
-
-
-		// Use reverse sampling to obtain more colour details
-		// Should use nearest neighbor point depths to verify which camera to use
-		//ftl::cuda::dibr(depth2_, colour1_, scene_->cameraCount(), params, stream);
-
-		if (src->getChannel() == ftl::rgbd::kChanDepth) {
-			ftl::cuda::int_to_float(depth1_, depth2_, 1.0f / 1000.0f, stream);
-			src->writeFrames(colour1_, depth2_, stream);
-		} else if (src->getChannel() == ftl::rgbd::kChanRight) {
-			// Adjust pose to right eye position
-			Eigen::Matrix4f matrix =  adjustPose(src->getPose().cast<float>(), camera.baseline/2.0f);
-			params.m_viewMatrix = MatrixConversion::toCUDA(matrix.inverse());
-			params.m_viewMatrixInverse = MatrixConversion::toCUDA(matrix);
-
-			ftl::cuda::clear_depth(depth1_, stream);
-			ftl::cuda::dibr(depth1_, colour2_, scene_->cameraCount(), params, stream);
-			src->writeFrames(colour1_, colour2_, stream);
-		} else {
-			src->writeFrames(colour1_, depth2_, stream);
-		}
-	}
-
-	//ftl::cuda::median_filter(depth1_, depth2_, stream);
-	//ftl::cuda::splat_points(depth1_, depth2_, params, stream);
-
-	// TODO: Second pass
-}
-
-void Splatter::setOutputDevice(int device) {
-	device_ = device;
-}
diff --git a/applications/reconstruct/src/splat_render.cu b/applications/reconstruct/src/splat_render.cu
deleted file mode 100644
index 3addf108dcf89ea8f3069e3ba0daea59e67ae918..0000000000000000000000000000000000000000
--- a/applications/reconstruct/src/splat_render.cu
+++ /dev/null
@@ -1,400 +0,0 @@
-#include "splat_render_cuda.hpp"
-#include <cuda_runtime.h>
-
-#include <ftl/cuda_matrix_util.hpp>
-
-#include "splat_params.hpp"
-
-#define T_PER_BLOCK 8
-#define NUM_GROUPS_X 1024
-
-#define NUM_CUDA_BLOCKS  10000
-
-using ftl::cuda::TextureObject;
-using ftl::render::SplatParams;
-
-__global__ void clearDepthKernel(ftl::voxhash::HashData hashData, TextureObject<int> depth) {
-	const unsigned int x = blockIdx.x*blockDim.x + threadIdx.x;
-	const unsigned int y = blockIdx.y*blockDim.y + threadIdx.y;
-
-	if (x < depth.width() && y < depth.height()) {
-		depth(x,y) = 0x7f800000; //PINF;
-		//colour(x,y) = make_uchar4(76,76,82,0);
-	}
-}
-
-#define SDF_BLOCK_SIZE_PAD 8
-#define SDF_BLOCK_BUFFER 512  // > 8x8x8
-#define SDF_DX 1
-#define SDF_DY SDF_BLOCK_SIZE_PAD
-#define SDF_DZ (SDF_BLOCK_SIZE_PAD*SDF_BLOCK_SIZE_PAD)
-
-#define LOCKED 0x7FFFFFFF
-
-//! computes the (local) virtual voxel pos of an index; idx in [0;511]
-__device__ 
-int3 pdelinVoxelIndex(uint idx)	{
-	int x = idx % SDF_BLOCK_SIZE_PAD;
-	int y = (idx % (SDF_BLOCK_SIZE_PAD * SDF_BLOCK_SIZE_PAD)) / SDF_BLOCK_SIZE_PAD;
-	int z = idx / (SDF_BLOCK_SIZE_PAD * SDF_BLOCK_SIZE_PAD);	
-	return make_int3(x,y,z);
-}
-
-//! computes the linearized index of a local virtual voxel pos; pos in [0;7]^3
-__device__ 
-uint plinVoxelPos(const int3& virtualVoxelPos) {
-	return  
-		virtualVoxelPos.z * SDF_BLOCK_SIZE_PAD * SDF_BLOCK_SIZE_PAD +
-		virtualVoxelPos.y * SDF_BLOCK_SIZE_PAD +
-		virtualVoxelPos.x;
-}
-
-//! computes the linearized index of a local virtual voxel pos; pos in [0;7]^3
-__device__ 
-uint plinVoxelPos(int x, int y, int z) {
-	return  
-		z * SDF_BLOCK_SIZE_PAD * SDF_BLOCK_SIZE_PAD +
-		y * SDF_BLOCK_SIZE_PAD + x;
-}
-
-__device__  
-void deleteVoxel(ftl::voxhash::Voxel& v) {
-	v.color = make_uchar3(0,0,0);
-	v.weight = 0;
-	v.sdf = PINF;
-}
-
-__device__ inline int3 blockDelinear(const int3 &base, uint i) {
-	return make_int3(base.x + (i & 0x1), base.y + (i & 0x2), base.z + (i & 0x4));
-}
-
-__device__ inline uint blockLinear(int x, int y, int z) {
-	return x + (y << 1) + (z << 2);
-}
-
-__device__ inline bool getVoxel(uint *voxels, int ix) {
-	return voxels[ix/32] & (0x1 << (ix % 32));
-}
-
-__global__ void occupied_image_kernel(ftl::voxhash::HashData hashData, TextureObject<int> depth, SplatParams params) {
-	__shared__ uint voxels[16];
-	__shared__ ftl::voxhash::HashEntryHead block;
-
-	// Stride over all allocated blocks
-	for (int bi=blockIdx.x; bi<*hashData.d_hashCompactifiedCounter; bi+=NUM_CUDA_BLOCKS) {
-	__syncthreads();
-
-	const uint i = threadIdx.x;	//inside of an SDF block
-
-	if (i == 0) block = hashData.d_hashCompactified[bi]->head;
-	if (i < 16) {
-		voxels[i] = hashData.d_hashCompactified[bi]->voxels[i];
-		//valid[i] = hashData.d_hashCompactified[bi]->validity[i];
-	}
-
-	// Make sure all hash entries are cached
-	__syncthreads();
-
-	const int3 pi_base = hashData.SDFBlockToVirtualVoxelPos(make_int3(block.posXYZ));
-	const int3 vp = make_int3(hashData.delinearizeVoxelIndex(i));
-	const int3 pi = pi_base + vp;
-	const float3 worldPos = hashData.virtualVoxelPosToWorld(pi);
-
-	const bool v = getVoxel(voxels, i);
-
-	uchar4 color = make_uchar4(255,0,0,255);
-	bool is_surface = v; //((params.m_flags & ftl::render::kShowBlockBorders) && edgeX + edgeY + edgeZ >= 2);
-
-
-	// Only for surface voxels, work out screen coordinates
-	if (!is_surface) continue;
-
-	// TODO: For each original camera, render a new depth map
-
-	const float3 camPos = params.m_viewMatrix * worldPos;
-	const float2 screenPosf = params.camera.cameraToKinectScreenFloat(camPos);
-	const uint2 screenPos = make_uint2(make_int2(screenPosf)); //  + make_float2(0.5f, 0.5f)
-
-	//printf("Worldpos: %f,%f,%f\n", camPos.x, camPos.y, camPos.z);
-
-	if (camPos.z < params.camera.m_sensorDepthWorldMin) continue;
-
-	const unsigned int x = screenPos.x;
-	const unsigned int y = screenPos.y;
-	const int idepth = static_cast<int>(camPos.z * 1000.0f);
-
-	// See: Gunther et al. 2013. A GPGPU-based Pipeline for Accelerated Rendering of Point Clouds
-	if (x < depth.width() && y < depth.height()) {
-		atomicMin(&depth(x,y), idepth);
-	}
-
-	}  // Stride
-}
-
-__global__ void isosurface_image_kernel(ftl::voxhash::HashData hashData, TextureObject<int> depth, SplatParams params) {
-	// TODO:(Nick) Reduce bank conflicts by aligning these
-	__shared__ uint voxels[16];
-	//__shared__ uint valid[16];
-	__shared__ ftl::voxhash::HashEntryHead block;
-
-	// Stride over all allocated blocks
-	for (int bi=blockIdx.x; bi<*hashData.d_hashCompactifiedCounter; bi+=NUM_CUDA_BLOCKS) {
-	__syncthreads();
-
-	const uint i = threadIdx.x;	//inside of an SDF block
-
-	if (i == 0) block = hashData.d_hashCompactified[bi]->head;
-	if (i < 16) {
-		voxels[i] = hashData.d_hashCompactified[bi]->voxels[i];
-		//valid[i] = hashData.d_hashCompactified[bi]->validity[i];
-	}
-
-	// Make sure all hash entries are cached
-	__syncthreads();
-
-	const int3 pi_base = hashData.SDFBlockToVirtualVoxelPos(make_int3(block.posXYZ));
-	const int3 vp = make_int3(hashData.delinearizeVoxelIndex(i));
-	const int3 pi = pi_base + vp;
-	//const uint j = plinVoxelPos(vp);  // Padded linear index
-	const float3 worldPos = hashData.virtualVoxelPosToWorld(pi);
-
-	// Load distances and colours into shared memory + padding
-	//const ftl::voxhash::Voxel &v = hashData.d_SDFBlocks[block.ptr + i];
-	//voxels[j] = v;
-	const bool v = getVoxel(voxels, i);
-
-	//__syncthreads();
-
-	//if (voxels[j].weight == 0) continue;
-	if (vp.x == 7 || vp.y == 7 || vp.z == 7) continue;
-
-
-	int edgeX = (vp.x == 0 ) ? 1 : 0;
-	int edgeY = (vp.y == 0 ) ? 1 : 0;
-	int edgeZ = (vp.z == 0 ) ? 1 : 0;
-
-	uchar4 color = make_uchar4(255,0,0,255);
-	bool is_surface = v; //((params.m_flags & ftl::render::kShowBlockBorders) && edgeX + edgeY + edgeZ >= 2);
-	//if (is_surface) color = make_uchar4(255,(vp.x == 0 && vp.y == 0 && vp.z == 0) ? 255 : 0,0,255);
-
-	if (v) continue;  // !getVoxel(valid, i)
-
-	//if (vp.z == 7) voxels[j].color = make_uchar3(0,255,(voxels[j].sdf < 0.0f) ? 255 : 0);
-
-	// Identify surfaces through sign change. Since we only check in one direction
-	// it is fine to check for any sign change?
-
-
-#pragma unroll
-	for (int u=0; u<=1; u++) {
-		for (int v=0; v<=1; v++) {
-			for (int w=0; w<=1; w++) {
-				const int3 uvi = make_int3(vp.x+u,vp.y+v,vp.z+w);
-
-				// Skip these cases since we didn't load voxels properly
-				//if (uvi.x == 8 || uvi.z == 8 || uvi.y == 8) continue;
-
-				const bool vox = getVoxel(voxels, hashData.linearizeVoxelPos(uvi));
-				if (vox) { //getVoxel(valid, hashData.linearizeVoxelPos(uvi))) {
-					is_surface = true;
-					// Should break but is slower?
-				}
-			}
-		}
-	}
-
-	// Only for surface voxels, work out screen coordinates
-	if (!is_surface) continue;
-
-	// TODO: For each original camera, render a new depth map
-
-	const float3 camPos = params.m_viewMatrix * worldPos;
-	const float2 screenPosf = params.camera.cameraToKinectScreenFloat(camPos);
-	const uint2 screenPos = make_uint2(make_int2(screenPosf)); //  + make_float2(0.5f, 0.5f)
-
-	//printf("Worldpos: %f,%f,%f\n", camPos.x, camPos.y, camPos.z);
-
-	if (camPos.z < params.camera.m_sensorDepthWorldMin) continue;
-
-	// For this voxel in hash, get its screen position and check it is on screen
-	// Convert depth map to int by x1000 and use atomicMin
-	//const int pixsize = static_cast<int>((c_hashParams.m_virtualVoxelSize*params.camera.fx/(camPos.z*0.8f)))+1;  // Magic number increase voxel to ensure coverage
-
-	const unsigned int x = screenPos.x;
-	const unsigned int y = screenPos.y;
-	const int idepth = static_cast<int>(camPos.z * 1000.0f);
-
-	// See: Gunther et al. 2013. A GPGPU-based Pipeline for Accelerated Rendering of Point Clouds
-	if (x < depth.width() && y < depth.height()) {
-		atomicMin(&depth(x,y), idepth);
-	}
-
-	}  // Stride
-}
-
-void ftl::cuda::isosurface_point_image(const ftl::voxhash::HashData& hashData,
-			const TextureObject<int> &depth,
-			const SplatParams &params, cudaStream_t stream) {
-
-	const dim3 clear_gridSize((depth.width() + T_PER_BLOCK - 1)/T_PER_BLOCK, (depth.height() + T_PER_BLOCK - 1)/T_PER_BLOCK);
-	const dim3 clear_blockSize(T_PER_BLOCK, T_PER_BLOCK);
-
-	clearDepthKernel<<<clear_gridSize, clear_blockSize, 0, stream>>>(hashData, depth);
-
-#ifdef _DEBUG
-	cudaSafeCall(cudaDeviceSynchronize());
-#endif
-
-	const unsigned int threadsPerBlock = SDF_BLOCK_SIZE*SDF_BLOCK_SIZE*SDF_BLOCK_SIZE;
-	const dim3 gridSize(NUM_CUDA_BLOCKS, 1);
-	const dim3 blockSize(threadsPerBlock, 1);
-
-	occupied_image_kernel<<<gridSize, blockSize, 0, stream>>>(hashData, depth, params);
-
-	cudaSafeCall( cudaGetLastError() );
-
-#ifdef _DEBUG
-	cudaSafeCall(cudaDeviceSynchronize());
-#endif
-}
-
-// ---- Pass 2: Expand the point splats ----------------------------------------
-
-#define SPLAT_RADIUS 7
-#define SPLAT_BOUNDS (2*SPLAT_RADIUS+T_PER_BLOCK+1)
-#define SPLAT_BUFFER_SIZE (SPLAT_BOUNDS*SPLAT_BOUNDS)
-#define MAX_VALID 100
-
-__device__ float distance2(float3 a, float3 b) {
-	const float x = a.x-b.x;
-	const float y = a.y-b.y;
-	const float z = a.z-b.z;
-	return x*x+y*y+z*z;
-}
-
-__global__ void splatting_kernel(
-		TextureObject<int> depth_in,
-		TextureObject<float> depth_out, SplatParams params) {
-	// Read an NxN region and
-	// - interpolate a depth value for this pixel
-	// - interpolate an rgb value for this pixel
-	// Must respect depth discontinuities.
-	// How much influence a given neighbour has depends on its depth value
-
-	__shared__ float3 positions[SPLAT_BUFFER_SIZE];
-
-	const int i = threadIdx.y*blockDim.y + threadIdx.x;
-	const int bx = blockIdx.x*blockDim.x;
-	const int by = blockIdx.y*blockDim.y;
-	const int x = bx + threadIdx.x;
-	const int y = by + threadIdx.y;
-
-	// const float camMinDepth = params.camera.m_sensorDepthWorldMin;
-	// const float camMaxDepth = params.camera.m_sensorDepthWorldMax;
-
-	for (int j=i; j<SPLAT_BUFFER_SIZE; j+=T_PER_BLOCK) {
-		const unsigned int sx = (j % SPLAT_BOUNDS)+bx-SPLAT_RADIUS;
-		const unsigned int sy = (j / SPLAT_BOUNDS)+by-SPLAT_RADIUS;
-		if (sx >= depth_in.width() || sy >= depth_in.height()) {
-			positions[j] = make_float3(1000.0f,1000.0f, 1000.0f);
-		} else {
-			positions[j] = params.camera.kinectDepthToSkeleton(sx, sy, (float)depth_in.tex2D((int)sx,(int)sy) / 1000.0f);
-		}
-	}
-
-	__syncthreads();
-
-	if (x >= depth_in.width() || y >= depth_in.height()) return;
-
-	const float voxelSquared = params.voxelSize*params.voxelSize;
-	float mindepth = 1000.0f;
-	int minidx = -1;
-	// float3 minpos;
-
-	//float3 validPos[MAX_VALID];
-	int validIndices[MAX_VALID];
-	int validix = 0;
-
-	for (int v=-SPLAT_RADIUS; v<=SPLAT_RADIUS; ++v) {
-		for (int u=-SPLAT_RADIUS; u<=SPLAT_RADIUS; ++u) {
-			//const int idx = (threadIdx.y+v)*SPLAT_BOUNDS+threadIdx.x+u;
-			const int idx = (threadIdx.y+v+SPLAT_RADIUS)*SPLAT_BOUNDS+threadIdx.x+u+SPLAT_RADIUS;
-
-			float3 posp = positions[idx];
-			const float d = posp.z;
-			//if (d < camMinDepth || d > camMaxDepth) continue;
-
-			float3 pos = params.camera.kinectDepthToSkeleton(x, y, d);
-			float dist = distance2(pos, posp);
-
-			if (dist < voxelSquared) {
-				// Valid so check for minimum
-				//validPos[validix] = pos;
-				//validIndices[validix++] = idx;
-				if (d < mindepth) {
-					mindepth = d;
-					minidx = idx;
-					// minpos = pos;
-				}	
-			}
-		}
-	}
-
-	if (minidx == -1) {
-		depth_out(x,y) = 0.0f;
-		//colour_out(x,y) = make_uchar4(76,76,82,255);
-		return;
-	}
-
-	//float3 colour = make_float3(0.0f, 0.0f, 0.0f);
-	float depth = 0.0f;
-	float contrib = 0.0f;
-	float3 pos = params.camera.kinectDepthToSkeleton(x, y, mindepth);  // TODO:(Nick) Mindepth assumption is poor choice.
-
-	//for (int j=0; j<validix; ++j) {
-		const int idx = minidx; //validIndices[j];
-		float3 posp = positions[idx];
-		//float3 pos = params.camera.kinectDepthToSkeleton(x, y, posp.z);
-		float3 delta = (posp - pos) / 2*params.voxelSize;
-		float dist = delta.x*delta.x + delta.y*delta.y + delta.z*delta.z;
-
-		// It contributes to pixel
-		if (dist < 1.0f && fabs(posp.z - mindepth) < 2*params.voxelSize) {
-			const unsigned int sx = (idx % SPLAT_BOUNDS)+bx-SPLAT_RADIUS;
-			const unsigned int sy = (idx / SPLAT_BOUNDS)+by-SPLAT_RADIUS;
-
-			// Fast and simple trilinear interpolation
-			float c = fabs((1.0f - delta.x) * (1.0f - delta.y) * (1.0f - delta.z));
-			//uchar4 col = colour_in.tex2D((int)sx, (int)sy);
-			//colour.x += col.x*c;
-			//colour.y += col.y*c;
-			//colour.z += col.z*c;
-			contrib += c;
-			depth += posp.z * c;
-		}
-	//}
-
-	// Normalise
-	//colour.x /= contrib;
-	//colour.y /= contrib;
-	//colour.z /= contrib;
-	depth /= contrib;
-
-	depth_out(x,y) = depth;
-	//colour_out(x,y) = make_uchar4(colour.x, colour.y, colour.z, 255);
-}
-
-void ftl::cuda::splat_points(const TextureObject<int> &depth_in,
-		const TextureObject<float> &depth_out, const SplatParams &params, cudaStream_t stream) 
-{
-
-	const dim3 gridSize((depth_in.width() + T_PER_BLOCK - 1)/T_PER_BLOCK, (depth_in.height() + T_PER_BLOCK - 1)/T_PER_BLOCK);
-	const dim3 blockSize(T_PER_BLOCK, T_PER_BLOCK);
-
-	splatting_kernel<<<gridSize, blockSize, 0, stream>>>(depth_in, depth_out, params);
-	cudaSafeCall( cudaGetLastError() );
-
-#ifdef _DEBUG
-	cudaSafeCall(cudaDeviceSynchronize());
-#endif
-}
diff --git a/applications/reconstruct/src/splat_render_cuda.hpp b/applications/reconstruct/src/splat_render_cuda.hpp
deleted file mode 100644
index d8fd9cf55649280c20805c8d4f8b9f7b758b2223..0000000000000000000000000000000000000000
--- a/applications/reconstruct/src/splat_render_cuda.hpp
+++ /dev/null
@@ -1,42 +0,0 @@
-#ifndef _FTL_RECONSTRUCTION_SPLAT_CUDA_HPP_
-#define _FTL_RECONSTRUCTION_SPLAT_CUDA_HPP_
-
-#include <ftl/depth_camera.hpp>
-#include <ftl/voxel_hash.hpp>
-//#include <ftl/ray_cast_util.hpp>
-
-#include "splat_params.hpp"
-
-namespace ftl {
-namespace cuda {
-
-/**
- * NOTE: Not strictly isosurface currently since it includes the internals
- * of objects up to at most truncation depth.
- */
-void isosurface_point_image(const ftl::voxhash::HashData& hashData,
-			const ftl::cuda::TextureObject<int> &depth,
-			const ftl::render::SplatParams &params, cudaStream_t stream);
-
-//void isosurface_point_image_stereo(const ftl::voxhash::HashData& hashData,
-//		const ftl::voxhash::HashParams& hashParams,
-//		const RayCastData &rayCastData, const RayCastParams &params,
-//		cudaStream_t stream);
-
-// TODO: isosurface_point_cloud
-
-void splat_points(const ftl::cuda::TextureObject<int> &depth_in,
-		const ftl::cuda::TextureObject<float> &depth_out,
-		const ftl::render::SplatParams &params, cudaStream_t stream);
-
-void dibr(const ftl::cuda::TextureObject<int> &depth_out,
-		const ftl::cuda::TextureObject<uchar4> &colour_out, int numcams,
-		const ftl::render::SplatParams &params, cudaStream_t stream);
-
-void dibr(const ftl::cuda::TextureObject<float> &depth_out,
-    const ftl::cuda::TextureObject<uchar4> &colour_out, int numcams, const ftl::render::SplatParams &params, cudaStream_t stream);
-
-}
-}
-
-#endif  // _FTL_RECONSTRUCTION_SPLAT_CUDA_HPP_
diff --git a/applications/reconstruct/src/voxel_hash.cpp b/applications/reconstruct/src/voxel_hash.cpp
deleted file mode 100644
index 14cb75078652001275404e87c09f77f0b9d19385..0000000000000000000000000000000000000000
--- a/applications/reconstruct/src/voxel_hash.cpp
+++ /dev/null
@@ -1,94 +0,0 @@
-#include <ftl/voxel_hash.hpp>
-
-using ftl::voxhash::HashData;
-using ftl::voxhash::HashParams;
-
-void HashData::allocate(const HashParams& params, bool dataOnGPU) {
-	m_bIsOnGPU = dataOnGPU;
-	if (m_bIsOnGPU) {
-		cudaSafeCall(cudaMalloc(&d_hash, sizeof(HashEntry)* params.m_hashNumBuckets));
-		cudaSafeCall(cudaMalloc(&d_hashDecision, sizeof(int)* params.m_hashNumBuckets));
-		cudaSafeCall(cudaMalloc(&d_hashDecisionPrefix, sizeof(int)* params.m_hashNumBuckets));
-		cudaSafeCall(cudaMalloc(&d_hashCompactified, sizeof(HashEntry*)* params.m_hashNumBuckets));
-		cudaSafeCall(cudaMalloc(&d_hashCompactifiedCounter, sizeof(int)));
-		cudaSafeCall(cudaMalloc(&d_hashBucketMutex, sizeof(int)* params.m_hashNumBuckets));
-	} else {
-		d_hash = new HashEntry[params.m_hashNumBuckets];
-		d_hashDecision = new int[params.m_hashNumBuckets];
-		d_hashDecisionPrefix = new int[params.m_hashNumBuckets];
-		d_hashCompactified = new HashEntry*[params.m_hashNumBuckets];
-		d_hashCompactifiedCounter = new int[1];
-		d_hashBucketMutex = new int[params.m_hashNumBuckets];
-	}
-
-	updateParams(params);
-}
-
-void HashData::updateParams(const HashParams& params) {
-	if (m_bIsOnGPU) {
-		updateConstantHashParams(params);
-	} 
-}
-
-void HashData::free() {
-	if (m_bIsOnGPU) {
-		cudaSafeCall(cudaFree(d_hash));
-		cudaSafeCall(cudaFree(d_hashDecision));
-		cudaSafeCall(cudaFree(d_hashDecisionPrefix));
-		cudaSafeCall(cudaFree(d_hashCompactified));
-		cudaSafeCall(cudaFree(d_hashCompactifiedCounter));
-		cudaSafeCall(cudaFree(d_hashBucketMutex));
-	} else {
-		if (d_hash) delete[] d_hash;
-		if (d_hashDecision) delete[] d_hashDecision;
-		if (d_hashDecisionPrefix) delete[] d_hashDecisionPrefix;
-		if (d_hashCompactified) delete[] d_hashCompactified;
-		if (d_hashCompactifiedCounter) delete[] d_hashCompactifiedCounter;
-		if (d_hashBucketMutex) delete[] d_hashBucketMutex;
-	}
-
-	d_hash = NULL;
-	d_hashDecision = NULL;
-	d_hashDecisionPrefix = NULL;
-	d_hashCompactified = NULL;
-	d_hashCompactifiedCounter = NULL;
-	d_hashBucketMutex = NULL;
-}
-
-HashData HashData::download() const {
-	if (!m_bIsOnGPU) return *this;
-	HashParams params;
-	
-	HashData hashData;
-	hashData.allocate(params, false);	//allocate the data on the CPU
-	cudaSafeCall(cudaMemcpy(hashData.d_hash, d_hash, sizeof(HashEntry)* params.m_hashNumBuckets, cudaMemcpyDeviceToHost));
-	cudaSafeCall(cudaMemcpy(hashData.d_hashDecision, d_hashDecision, sizeof(int)*params.m_hashNumBuckets, cudaMemcpyDeviceToHost));
-	cudaSafeCall(cudaMemcpy(hashData.d_hashDecisionPrefix, d_hashDecisionPrefix, sizeof(int)*params.m_hashNumBuckets, cudaMemcpyDeviceToHost));
-	cudaSafeCall(cudaMemcpy(hashData.d_hashCompactified, d_hashCompactified, sizeof(HashEntry*)* params.m_hashNumBuckets, cudaMemcpyDeviceToHost));
-	cudaSafeCall(cudaMemcpy(hashData.d_hashCompactifiedCounter, d_hashCompactifiedCounter, sizeof(unsigned int), cudaMemcpyDeviceToHost));
-	cudaSafeCall(cudaMemcpy(hashData.d_hashBucketMutex, d_hashBucketMutex, sizeof(int)* params.m_hashNumBuckets, cudaMemcpyDeviceToHost));
-	
-	return hashData;
-}
-
-HashData HashData::upload() const {
-	if (m_bIsOnGPU) return *this;
-	HashParams params;
-	
-	HashData hashData;
-	hashData.allocate(params, false);	//allocate the data on the CPU
-	cudaSafeCall(cudaMemcpy(hashData.d_hash, d_hash, sizeof(HashEntry)* params.m_hashNumBuckets, cudaMemcpyHostToDevice));
-	cudaSafeCall(cudaMemcpy(hashData.d_hashDecision, d_hashDecision, sizeof(int)*params.m_hashNumBuckets, cudaMemcpyHostToDevice));
-	cudaSafeCall(cudaMemcpy(hashData.d_hashDecisionPrefix, d_hashDecisionPrefix, sizeof(int)*params.m_hashNumBuckets, cudaMemcpyHostToDevice));
-	cudaSafeCall(cudaMemcpy(hashData.d_hashCompactified, d_hashCompactified, sizeof(HashEntry)* params.m_hashNumBuckets, cudaMemcpyHostToDevice));
-	cudaSafeCall(cudaMemcpy(hashData.d_hashCompactifiedCounter, d_hashCompactifiedCounter, sizeof(unsigned int), cudaMemcpyHostToDevice));
-	cudaSafeCall(cudaMemcpy(hashData.d_hashBucketMutex, d_hashBucketMutex, sizeof(int)* params.m_hashNumBuckets, cudaMemcpyHostToDevice));
-	
-	return hashData;
-}
-
-/*size_t HashData::getAllocatedBlocks() const {
-	unsigned int count;
-	cudaSafeCall(cudaMemcpy(d_heapCounter, &count, sizeof(unsigned int), cudaMemcpyDeviceToHost));
-	return count;
-}*/
diff --git a/applications/reconstruct/src/voxel_hash.cu b/applications/reconstruct/src/voxel_hash.cu
deleted file mode 100644
index c2d07c391a6e48d2b45cc23dbf32b00878ffd5c9..0000000000000000000000000000000000000000
--- a/applications/reconstruct/src/voxel_hash.cu
+++ /dev/null
@@ -1,257 +0,0 @@
-#include <ftl/voxel_hash.hpp>
-
-using namespace ftl::voxhash;
-
-#define COLLISION_LIST_SIZE 6
-
-__device__ inline uint64_t compactPosition(const int3 &pos) {
-	union __align__(8) {
-	short4 posXYZ;
-	uint64_t pos64;
-	};
-	posXYZ.x = pos.x; posXYZ.y = pos.y; posXYZ.z = pos.z; posXYZ.w = 0;
-	return pos64;
-}
-
-//! returns the hash entry for a given sdf block id; if there was no hash entry the returned entry will have a ptr with FREE_ENTRY set
-__device__ 
-int HashData::getHashEntryForSDFBlockPos(const int3& sdfBlock) const {
-	uint h = computeHashPos(sdfBlock); //hash
-	uint64_t pos = compactPosition(sdfBlock);
-
-	HashEntryHead curr;
-
-	int i = h;
-	unsigned int maxIter = 0;
-
-	#pragma unroll 2
-	while (maxIter < COLLISION_LIST_SIZE) {
-		curr = d_hash[i].head;
-
-		if (curr.pos == pos && curr.offset != FREE_ENTRY) return i;
-		if (curr.offset == 0 || curr.offset == FREE_ENTRY) break;
-
-		i +=  curr.offset;  //go to next element in the list
-		i %= (params().m_hashNumBuckets);  //check for overflow
-		++maxIter;
-	}
-
-	// Could not find
-	return -1;
-}
-
-//for histogram (collisions traversal only)
-__device__ 
-unsigned int HashData::getNumHashLinkedList(unsigned int bucketID) {
-	unsigned int listLen = 0;
-
-	unsigned int i = bucketID;	//start with the last entry of the current bucket
-	HashEntryHead curr;	curr.offset = 0;
-
-	unsigned int maxIter = 0;
-
-	#pragma unroll 2 
-	while (maxIter < COLLISION_LIST_SIZE) {
-		curr = d_hash[i].head;
-
-		if (curr.offset == 0 || curr.offset == FREE_ENTRY) break;
-
-		i += curr.offset;		//go to next element in the list
-		i %= (params().m_hashNumBuckets);	//check for overflow
-		++listLen;
-		++maxIter;
-	}
-	
-	return listLen;
-}
-
-//pos in SDF block coordinates
-__device__
-void HashData::allocBlock(const int3& pos) {
-	uint h = computeHashPos(pos);				//hash bucket
-	uint i = h;
-	HashEntryHead curr;	//curr.offset = 0;
-	const uint64_t pos64 = compactPosition(pos);
-
-	unsigned int maxIter = 0;
-	#pragma  unroll 2
-	while (maxIter < COLLISION_LIST_SIZE) {
-		//offset = curr.offset;
-		curr = d_hash[i].head;	//TODO MATTHIAS do by reference
-		if (curr.pos == pos64 && curr.offset != FREE_ENTRY) return;
-		if (curr.offset == 0 || curr.offset == FREE_ENTRY) break;
-
-		i += curr.offset;		//go to next element in the list
-		i %= (params().m_hashNumBuckets);	//check for overflow
-		++maxIter;
-	}
-
-	// Limit reached...
-	//if (maxIter == COLLISION_LIST_SIZE) return;
-
-	int j = i;
-	while (maxIter < COLLISION_LIST_SIZE) {
-		//offset = curr.offset;
-
-		if (curr.offset == FREE_ENTRY) {
-			int prevValue = atomicExch(&d_hashBucketMutex[i], LOCK_ENTRY);
-			if (prevValue != LOCK_ENTRY) {
-				if (i == j) {
-					HashEntryHead& entry = d_hash[j].head;
-					entry.pos = pos64;
-					entry.offset = 0;
-					entry.flags = 0;
-				} else {
-					//InterlockedExchange(g_HashBucketMutex[h], LOCK_ENTRY, prevValue);	//lock the hash bucket where we have found a free entry
-					prevValue = atomicExch(&d_hashBucketMutex[j], LOCK_ENTRY);
-					if (prevValue != LOCK_ENTRY) {	//only proceed if the bucket has been locked
-						HashEntryHead& entry = d_hash[j].head;
-						entry.pos = pos64;
-						entry.offset = 0;
-						entry.flags = 0;  // Flag block as valid in this frame (Nick)		
-						//entry.ptr = consumeHeap() * SDF_BLOCK_SIZE*SDF_BLOCK_SIZE*SDF_BLOCK_SIZE;	//memory alloc
-						d_hash[i].head.offset = j-i;
-						//setHashEntry(g_Hash, idxLastEntryInBucket, lastEntryInBucket);
-					}
-				}
-			} 
-			return;	//bucket was already locked
-		}
-
-		++j;
-		j %= (params().m_hashNumBuckets);	//check for overflow
-		curr = d_hash[j].head;	//TODO MATTHIAS do by reference
-		++maxIter;
-	}
-}
-
-
-//!inserts a hash entry without allocating any memory: used by streaming: TODO MATTHIAS check the atomics in this function
-/*__device__
-bool HashData::insertHashEntry(HashEntry entry)
-{
-	uint h = computeHashPos(entry.pos);
-	uint hp = h * HASH_BUCKET_SIZE;
-
-	for (uint j = 0; j < HASH_BUCKET_SIZE; j++) {
-		uint i = j + hp;		
-		//const HashEntry& curr = d_hash[i];
-		int prevWeight = 0;
-		//InterlockedCompareExchange(hash[3*i+2], FREE_ENTRY, LOCK_ENTRY, prevWeight);
-		prevWeight = atomicCAS(&d_hash[i].ptr, FREE_ENTRY, LOCK_ENTRY);
-		if (prevWeight == FREE_ENTRY) {
-			d_hash[i] = entry;
-			//setHashEntry(hash, i, entry);
-			return true;
-		}
-	}
-
-#ifdef HANDLE_COLLISIONS
-	//updated variables as after the loop
-	const uint idxLastEntryInBucket = (h+1)*HASH_BUCKET_SIZE - 1;	//get last index of bucket
-
-	uint i = idxLastEntryInBucket;											//start with the last entry of the current bucket
-	HashEntry curr;
-
-	unsigned int maxIter = 0;
-	//[allow_uav_condition]
-	uint g_MaxLoopIterCount = params().m_hashMaxCollisionLinkedListSize;
-	#pragma  unroll 1 
-	while (maxIter < g_MaxLoopIterCount) {									//traverse list until end // why find the end? we you are inserting at the start !!!
-		//curr = getHashEntry(hash, i);
-		curr = d_hash[i];	//TODO MATTHIAS do by reference
-		if (curr.offset == 0) break;									//we have found the end of the list
-		i = idxLastEntryInBucket + curr.offset;							//go to next element in the list
-		i %= (HASH_BUCKET_SIZE * params().m_hashNumBuckets);	//check for overflow
-
-		maxIter++;
-	}
-
-	maxIter = 0;
-	int offset = 0;
-	#pragma  unroll 1 
-	while (maxIter < g_MaxLoopIterCount) {													//linear search for free entry
-		offset++;
-		uint i = (idxLastEntryInBucket + offset) % (HASH_BUCKET_SIZE * params().m_hashNumBuckets);	//go to next hash element
-		if ((offset % HASH_BUCKET_SIZE) == 0) continue;										//cannot insert into a last bucket element (would conflict with other linked lists)
-
-		int prevWeight = 0;
-		//InterlockedCompareExchange(hash[3*i+2], FREE_ENTRY, LOCK_ENTRY, prevWeight);		//check for a free entry
-		uint* d_hashUI = (uint*)d_hash;
-		prevWeight = prevWeight = atomicCAS(&d_hashUI[3*idxLastEntryInBucket+1], (uint)FREE_ENTRY, (uint)LOCK_ENTRY);
-		if (prevWeight == FREE_ENTRY) {														//if free entry found set prev->next = curr & curr->next = prev->next
-			//[allow_uav_condition]
-			//while(hash[3*idxLastEntryInBucket+2] == LOCK_ENTRY); // expects setHashEntry to set the ptr last, required because pos.z is packed into the same value -> prev->next = curr -> might corrput pos.z
-
-			HashEntry lastEntryInBucket = d_hash[idxLastEntryInBucket];			//get prev (= lastEntry in Bucket)
-
-			int newOffsetPrev = (offset << 16) | (lastEntryInBucket.pos.z & 0x0000ffff);	//prev->next = curr (maintain old z-pos)
-			int oldOffsetPrev = 0;
-			//InterlockedExchange(hash[3*idxLastEntryInBucket+1], newOffsetPrev, oldOffsetPrev);	//set prev offset atomically
-			uint* d_hashUI = (uint*)d_hash;
-			oldOffsetPrev = prevWeight = atomicExch(&d_hashUI[3*idxLastEntryInBucket+1], newOffsetPrev);
-			entry.offset = oldOffsetPrev >> 16;													//remove prev z-pos from old offset
-
-			//setHashEntry(hash, i, entry);														//sets the current hashEntry with: curr->next = prev->next
-			d_hash[i] = entry;
-			return true;
-		}
-
-		maxIter++;
-	} 
-#endif
-
-	return false;
-}*/
-
-
-
-//! deletes a hash entry position for a given sdfBlock index (returns true uppon successful deletion; otherwise returns false)
-__device__
-bool HashData::deleteHashEntryElement(const int3& sdfBlock) {
-	uint h = computeHashPos(sdfBlock);	//hash bucket
-	const uint64_t pos = compactPosition(sdfBlock);
-
-	int i = h;
-	int prev = -1;
-	HashEntryHead curr;
-	unsigned int maxIter = 0;
-
-	#pragma  unroll 2 
-	while (maxIter < COLLISION_LIST_SIZE) {
-		curr = d_hash[i].head;
-	
-		//found that dude that we need/want to delete
-		if (curr.pos == pos && curr.offset != FREE_ENTRY) {
-			//int prevValue = 0;
-			//InterlockedExchange(bucketMutex[h], LOCK_ENTRY, prevValue);	//lock the hash bucket
-			int prevValue = atomicExch(&d_hashBucketMutex[i], LOCK_ENTRY);
-			if (prevValue == LOCK_ENTRY)	return false;
-			if (prevValue != LOCK_ENTRY) {
-				prevValue = (prev >= 0) ? atomicExch(&d_hashBucketMutex[prev], LOCK_ENTRY) : 0;
-				if (prevValue == LOCK_ENTRY)	return false;
-				if (prevValue != LOCK_ENTRY) {
-					//const uint linBlockSize = SDF_BLOCK_SIZE * SDF_BLOCK_SIZE * SDF_BLOCK_SIZE;
-					//appendHeap(curr.ptr / linBlockSize);
-					deleteHashEntry(i);
-
-					if (prev >= 0) {
-						d_hash[prev].head.offset = curr.offset;
-					}
-					return true;
-				}
-			}
-		}
-
-		if (curr.offset == 0 || curr.offset == FREE_ENTRY) {	//we have found the end of the list
-			return false;	//should actually never happen because we need to find that guy before
-		}
-		prev = i;
-		i += curr.offset;		//go to next element in the list
-		i %= (params().m_hashNumBuckets);	//check for overflow
-
-		++maxIter;
-	}
-
-	return false;
-}
\ No newline at end of file
diff --git a/applications/reconstruct/src/voxel_scene.cpp b/applications/reconstruct/src/voxel_scene.cpp
index 5ec9204e9899c885e5ad3188c7127b7269b4d1f0..09067b1f2334e0190e27022b64d374e31532f8c2 100644
--- a/applications/reconstruct/src/voxel_scene.cpp
+++ b/applications/reconstruct/src/voxel_scene.cpp
@@ -1,7 +1,4 @@
 #include <ftl/voxel_scene.hpp>
-#include "compactors.hpp"
-#include "garbage.hpp"
-#include "integrators.hpp"
 #include "depth_camera_cuda.hpp"
 
 #include <opencv2/core/cuda_stream_accessor.hpp>
@@ -10,15 +7,16 @@
 
 using namespace ftl::voxhash;
 using ftl::rgbd::Source;
+using ftl::rgbd::Channel;
 using ftl::Configurable;
 using cv::Mat;
 using std::vector;
 
 #define 	SAFE_DELETE_ARRAY(a)   { delete [] (a); (a) = NULL; }
 
-extern "C" void resetCUDA(ftl::voxhash::HashData& hashData, const ftl::voxhash::HashParams& hashParams);
-extern "C" void resetHashBucketMutexCUDA(ftl::voxhash::HashData& hashData, const ftl::voxhash::HashParams& hashParams, cudaStream_t);
-extern "C" void allocCUDA(ftl::voxhash::HashData& hashData, const ftl::voxhash::HashParams& hashParams, int camid, const DepthCameraParams &depthCameraParams, cudaStream_t);
+//extern "C" void resetCUDA(ftl::voxhash::HashData& hashData, const ftl::voxhash::HashParams& hashParams);
+//extern "C" void resetHashBucketMutexCUDA(ftl::voxhash::HashData& hashData, const ftl::voxhash::HashParams& hashParams, cudaStream_t);
+//extern "C" void allocCUDA(ftl::voxhash::HashData& hashData, const ftl::voxhash::HashParams& hashParams, int camid, const DepthCameraParams &depthCameraParams, cudaStream_t);
 //extern "C" void fillDecisionArrayCUDA(ftl::voxhash::HashData& hashData, const ftl::voxhash::HashParams& hashParams, const DepthCameraData& depthCameraData);
 //extern "C" void compactifyHashCUDA(ftl::voxhash::HashData& hashData, const ftl::voxhash::HashParams& hashParams);
 //extern "C" unsigned int compactifyHashAllInOneCUDA(ftl::voxhash::HashData& hashData, const ftl::voxhash::HashParams& hashParams);
@@ -98,6 +96,7 @@ void SceneRep::addSource(ftl::rgbd::Source *src) {
 	auto &cam = cameras_.emplace_back();
 	cam.source = src;
 	cam.params.m_imageWidth = 0;
+	src->setChannel(Channel::Depth);
 }
 
 extern "C" void updateCUDACameraConstant(ftl::voxhash::DepthCameraCUDA *data, int count);
@@ -183,7 +182,89 @@ int SceneRep::upload() {
 		//if (i > 0) cudaSafeCall(cudaStreamSynchronize(cv::cuda::StreamAccessor::getStream(cameras_[i-1].stream)));
 
 		//allocate all hash blocks which are corresponding to depth map entries
-		if (value("voxels", false)) _alloc(i, cv::cuda::StreamAccessor::getStream(cam.stream));
+		//if (value("voxels", false)) _alloc(i, cv::cuda::StreamAccessor::getStream(cam.stream));
+
+		// Calculate normals
+	}
+
+	// Must have finished all allocations and rendering before next integration
+	cudaSafeCall(cudaDeviceSynchronize());
+
+	return active;
+}
+
+int SceneRep::upload(ftl::rgbd::FrameSet &fs) {
+	int active = 0;
+
+	for (size_t i=0; i<cameras_.size(); ++i) {
+		auto &cam = cameras_[i];
+
+		if (!cam.source->isReady()) {
+			cam.params.m_imageWidth = 0;
+			// TODO(Nick) : Free gpu allocs if was ready before
+			LOG(INFO) << "Source not ready: " << cam.source->getURI();
+			continue;
+		} else {
+			auto in = cam.source;
+
+			cam.params.fx = in->parameters().fx;
+			cam.params.fy = in->parameters().fy;
+			cam.params.mx = -in->parameters().cx;
+			cam.params.my = -in->parameters().cy;
+
+			// Only now do we have camera parameters for allocations...
+			if (cam.params.m_imageWidth == 0) {
+				cam.params.m_imageWidth = in->parameters().width;
+				cam.params.m_imageHeight = in->parameters().height;
+				cam.params.m_sensorDepthWorldMax = in->parameters().maxDepth;
+				cam.params.m_sensorDepthWorldMin = in->parameters().minDepth;
+				cam.gpu.alloc(cam.params, true);
+				LOG(INFO) << "GPU Allocated camera " << i;
+			}
+		}
+
+		cam.params.flags = m_frameCount;
+	}
+
+	_updateCameraConstant();
+	//cudaSafeCall(cudaDeviceSynchronize());
+
+	for (size_t i=0; i<cameras_.size(); ++i) {
+		auto &cam = cameras_[i];
+		auto &chan1 = fs.frames[i].get<cv::Mat>(Channel::Colour);
+		auto &chan2 = fs.frames[i].get<cv::Mat>(fs.sources[i]->getChannel());
+
+		auto test = fs.frames[i].createTexture<uchar4>(Channel::Flow, ftl::rgbd::Format<uchar4>(100,100));
+
+		// Get the RGB-Depth frame from input
+		Source *input = cam.source;
+		//Mat rgb, depth;
+
+		// TODO(Nick) Direct GPU upload to save copy
+		//input->getFrames(rgb,depth);
+		
+		active += 1;
+
+		//if (depth.cols == 0) continue;
+
+		// Must be in RGBA for GPU
+		Mat rgbt, rgba;
+		cv::cvtColor(chan1,rgbt, cv::COLOR_BGR2Lab);
+		cv::cvtColor(rgbt,rgba, cv::COLOR_BGR2BGRA);
+
+		// Send to GPU and merge view into scene
+		//cam.gpu.updateParams(cam.params);
+		cam.gpu.updateData(chan2, rgba, cam.stream);
+
+		//setLastRigidTransform(input->getPose().cast<float>());
+
+		//make the rigid transform available on the GPU
+		//m_hashData.updateParams(m_hashParams, cv::cuda::StreamAccessor::getStream(cam.stream));
+
+		//if (i > 0) cudaSafeCall(cudaStreamSynchronize(cv::cuda::StreamAccessor::getStream(cameras_[i-1].stream)));
+
+		//allocate all hash blocks which are corresponding to depth map entries
+		//if (value("voxels", false)) _alloc(i, cv::cuda::StreamAccessor::getStream(cam.stream));
 
 		// Calculate normals
 	}
@@ -218,7 +299,7 @@ void SceneRep::integrate() {
 
 void SceneRep::garbage() {
 	//_compactifyAllocated();
-	if (value("voxels", false)) _garbageCollect();
+	//if (value("voxels", false)) _garbageCollect();
 
 	//cudaSafeCall(cudaStreamSynchronize(integ_stream_));
 }
@@ -294,14 +375,14 @@ HashParams SceneRep::_parametersFromConfig() {
 	params.m_flags = 0;
 	params.m_flags |= (value("clipping", false)) ? ftl::voxhash::kFlagClipping : 0;
 	params.m_flags |= (value("mls", false)) ? ftl::voxhash::kFlagMLS : 0;
-	params.m_maxBounds = make_int3(
-		value("bbox_x_max", 2.0f) / (params.m_virtualVoxelSize*SDF_BLOCK_SIZE),
-		value("bbox_y_max", 2.0f) / (params.m_virtualVoxelSize*SDF_BLOCK_SIZE),
-		value("bbox_z_max", 2.0f) / (params.m_virtualVoxelSize*SDF_BLOCK_SIZE));
-	params.m_minBounds = make_int3(
-		value("bbox_x_min", -2.0f) / (params.m_virtualVoxelSize*SDF_BLOCK_SIZE),
-		value("bbox_y_min", -2.0f) / (params.m_virtualVoxelSize*SDF_BLOCK_SIZE),
-		value("bbox_z_min", -2.0f) / (params.m_virtualVoxelSize*SDF_BLOCK_SIZE));
+	params.m_maxBounds = make_float3(
+		value("bbox_x_max", 2.0f),
+		value("bbox_y_max", 2.0f),
+		value("bbox_z_max", 2.0f));
+	params.m_minBounds = make_float3(
+		value("bbox_x_min", -2.0f),
+		value("bbox_y_min", -2.0f),
+		value("bbox_z_min", -2.0f));
 	return params;
 }
 
@@ -338,19 +419,19 @@ void SceneRep::_alloc(int camid, cudaStream_t stream) {
 	}
 	else {*/
 		//this version is faster, but it doesn't guarantee that all blocks are allocated (staggers alloc to the next frame)
-		resetHashBucketMutexCUDA(m_hashData, m_hashParams, stream);
-		allocCUDA(m_hashData, m_hashParams, camid, cameras_[camid].params, stream);
+		//resetHashBucketMutexCUDA(m_hashData, m_hashParams, stream);
+		//allocCUDA(m_hashData, m_hashParams, camid, cameras_[camid].params, stream);
 	//}
 }
 
 
 void SceneRep::_compactifyVisible(const DepthCameraParams &camera) { //const DepthCameraData& depthCameraData) {
-	ftl::cuda::compactifyOccupied(m_hashData, m_hashParams, integ_stream_);		//this version uses atomics over prefix sums, which has a much better performance
+	//ftl::cuda::compactifyOccupied(m_hashData, m_hashParams, integ_stream_);		//this version uses atomics over prefix sums, which has a much better performance
 	//m_hashData.updateParams(m_hashParams);	//make sure numOccupiedBlocks is updated on the GPU
 }
 
 void SceneRep::_compactifyAllocated() {
-	ftl::cuda::compactifyAllocated(m_hashData, m_hashParams, integ_stream_);		//this version uses atomics over prefix sums, which has a much better performance
+	//ftl::cuda::compactifyAllocated(m_hashData, m_hashParams, integ_stream_);		//this version uses atomics over prefix sums, which has a much better performance
 	//std::cout << "Occ blocks = " << m_hashParams.m_numOccupiedBlocks << std::endl;
 	//m_hashData.updateParams(m_hashParams);	//make sure numOccupiedBlocks is updated on the GPU
 }
@@ -360,7 +441,7 @@ void SceneRep::_compactifyAllocated() {
 	else ftl::cuda::integrateRegistration(m_hashData, m_hashParams, depthCameraData, depthCameraParams, integ_stream_);
 }*/
 
-extern "C" void bilateralFilterFloatMap(float* d_output, float* d_input, float sigmaD, float sigmaR, unsigned int width, unsigned int height);
+//extern "C" void bilateralFilterFloatMap(float* d_output, float* d_input, float sigmaD, float sigmaR, unsigned int width, unsigned int height);
 
 void SceneRep::_integrateDepthMaps() {
 	//cudaSafeCall(cudaDeviceSynchronize());
@@ -375,11 +456,11 @@ void SceneRep::_integrateDepthMaps() {
 		//ftl::cuda::hole_fill(*(cameras_[i].gpu.depth2_tex_), *(cameras_[i].gpu.depth_tex_), cameras_[i].params, integ_stream_);
 		//bilateralFilterFloatMap(cameras_[i].gpu.depth_tex_->devicePtr(), cameras_[i].gpu.depth3_tex_->devicePtr(), 3, 7, cameras_[i].gpu.depth_tex_->width(), cameras_[i].gpu.depth_tex_->height());
 	}
-	if (value("voxels", false)) ftl::cuda::integrateDepthMaps(m_hashData, m_hashParams, cameras_.size(), integ_stream_);
+	//if (value("voxels", false)) ftl::cuda::integrateDepthMaps(m_hashData, m_hashParams, cameras_.size(), integ_stream_);
 }
 
 void SceneRep::_garbageCollect() {
 	//ftl::cuda::garbageCollectIdentify(m_hashData, m_hashParams, integ_stream_);
-	resetHashBucketMutexCUDA(m_hashData, m_hashParams, integ_stream_);	//needed if linked lists are enabled -> for memeory deletion
-	ftl::cuda::garbageCollectFree(m_hashData, m_hashParams, integ_stream_);
+	//resetHashBucketMutexCUDA(m_hashData, m_hashParams, integ_stream_);	//needed if linked lists are enabled -> for memeory deletion
+	//ftl::cuda::garbageCollectFree(m_hashData, m_hashParams, integ_stream_);
 }
diff --git a/applications/registration/src/aruco.cpp b/applications/registration/src/aruco.cpp
index 8ff84854603123f0799455f223bcd98506b0b2e0..99d3552d9693cb548384043cef741703f115ce05 100644
--- a/applications/registration/src/aruco.cpp
+++ b/applications/registration/src/aruco.cpp
@@ -35,6 +35,8 @@ void ftl::registration::aruco(ftl::Configurable *root) {
 
 	if (sources.size() == 0) return;
 
+	for (auto *src : sources) src->setChannel(ftl::rgbd::kChanDepth);
+
 	cv::namedWindow("Target", cv::WINDOW_KEEPRATIO);
 	cv::namedWindow("Source", cv::WINDOW_KEEPRATIO);
 
diff --git a/applications/vision/CMakeLists.txt b/applications/vision/CMakeLists.txt
index 684b195b5a2a8605e9bc3e04e629d21a35dcd545..1753488f3407b74518efad555ecdd87be54ba1e9 100644
--- a/applications/vision/CMakeLists.txt
+++ b/applications/vision/CMakeLists.txt
@@ -21,6 +21,6 @@ set_property(TARGET ftl-vision PROPERTY CUDA_SEPARABLE_COMPILATION OFF)
 endif()
 
 #target_include_directories(cv-node PUBLIC ${PROJECT_SOURCE_DIR}/include)
-target_link_libraries(ftl-vision ftlrgbd ftlcommon ftlctrl ftlrender ${OpenCV_LIBS} ${LIBSGM_LIBRARIES} ${CUDA_LIBRARIES} ftlnet)
+target_link_libraries(ftl-vision ftlrgbd ftlcommon ftlctrl ${OpenCV_LIBS} ${LIBSGM_LIBRARIES} ${CUDA_LIBRARIES} ftlnet)
 
 
diff --git a/applications/vision/src/main.cpp b/applications/vision/src/main.cpp
index 1ce6cb162c322d82a51988fe63a9256505283b12..ba07e90424db91c5cc36e18d0aa40a68109bddbd 100644
--- a/applications/vision/src/main.cpp
+++ b/applications/vision/src/main.cpp
@@ -19,7 +19,7 @@
 #include <opencv2/opencv.hpp>
 #include <ftl/rgbd.hpp>
 #include <ftl/middlebury.hpp>
-#include <ftl/display.hpp>
+//#include <ftl/display.hpp>
 #include <ftl/rgbd/streamer.hpp>
 #include <ftl/net/universe.hpp>
 #include <ftl/slave.hpp>
@@ -36,7 +36,7 @@
 
 using ftl::rgbd::Source;
 using ftl::rgbd::Camera;
-using ftl::Display;
+//using ftl::Display;
 using ftl::rgbd::Streamer;
 using ftl::net::Universe;
 using std::string;
@@ -87,7 +87,7 @@ static void run(ftl::Configurable *root) {
 
 	if (file != "") source->set("uri", file);
 	
-	Display *display = ftl::create<Display>(root, "display", "local");
+	//Display *display = ftl::create<Display>(root, "display", "local");
 	
 	Streamer *stream = ftl::create<Streamer>(root, "stream", net);
 	stream->add(source);
@@ -95,7 +95,7 @@ static void run(ftl::Configurable *root) {
 	net->start();
 
 	LOG(INFO) << "Running...";
-	if (display->hasDisplays()) {
+	/*if (display->hasDisplays()) {
 		stream->run();
 		while (ftl::running && display->active()) {
 			cv::Mat rgb, depth;
@@ -103,9 +103,9 @@ static void run(ftl::Configurable *root) {
 			if (!rgb.empty()) display->render(rgb, depth, source->parameters());
 			display->wait(10);
 		}
-	} else {
+	} else {*/
 		stream->run(true);
-	}
+	//}
 
 	LOG(INFO) << "Stopping...";
 	slave.stop();
@@ -115,7 +115,7 @@ static void run(ftl::Configurable *root) {
 	ftl::pool.stop();
 
 	delete stream;
-	delete display;
+	//delete display;
 	//delete source;  // TODO(Nick) Add ftl::destroy
 	delete net;
 }
diff --git a/components/codecs/CMakeLists.txt b/components/codecs/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..f951f94a6c10127c989862d154dbe6cee896812a
--- /dev/null
+++ b/components/codecs/CMakeLists.txt
@@ -0,0 +1,26 @@
+set(CODECSRC
+	src/bitrates.cpp
+	src/encoder.cpp
+	src/decoder.cpp
+	src/opencv_encoder.cpp
+	src/opencv_decoder.cpp
+	src/generate.cpp
+)
+
+if (HAVE_NVPIPE)
+	list(APPEND CODECSRC src/nvpipe_encoder.cpp)
+	list(APPEND CODECSRC src/nvpipe_decoder.cpp)
+endif()
+
+add_library(ftlcodecs ${CODECSRC})
+
+target_include_directories(ftlcodecs PUBLIC
+	$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
+	$<INSTALL_INTERFACE:include>
+	PRIVATE src)
+
+#target_include_directories(cv-node PUBLIC ${PROJECT_SOURCE_DIR}/include)
+target_link_libraries(ftlcodecs ftlcommon ${OpenCV_LIBS} ${CUDA_LIBRARIES} Eigen3::Eigen nvpipe)
+
+add_subdirectory(test)
+
diff --git a/components/codecs/README.md b/components/codecs/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/components/codecs/include/ftl/codecs/bitrates.hpp b/components/codecs/include/ftl/codecs/bitrates.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..19572daa5600fd7b78106e951307e92e17f51e33
--- /dev/null
+++ b/components/codecs/include/ftl/codecs/bitrates.hpp
@@ -0,0 +1,118 @@
+#ifndef _FTL_CODECS_BITRATES_HPP_
+#define _FTL_CODECS_BITRATES_HPP_
+
+#include <cstdint>
+#include <msgpack.hpp>
+
+namespace ftl {
+namespace codecs {
+
+/**
+ * Compression format used.
+ */
+enum struct codec_t : uint8_t {
+	JPG = 0,
+	PNG,
+    H264,
+    HEVC  // H265
+};
+
+/**
+ * Resolution of encoding.
+ */
+enum struct definition_t : uint8_t {
+	UHD8k = 0,
+	UHD4k = 1,
+	HD1080 = 2,
+	HD720 = 3,
+	SD576 = 4,
+	SD480 = 5,
+	LD360 = 6,
+	Any = 7
+};
+
+/**
+ * Get width in pixels of definition.
+ */
+int getWidth(definition_t);
+
+/**
+ * Get height in pixels of definition.
+ */
+int getHeight(definition_t);
+
+/**
+ * General indication of desired quality. Exact bitrate numbers depends also
+ * upon chosen definition and codec.
+ */
+enum struct bitrate_t {
+	High,
+	Standard,
+	Low
+};
+
+/**
+ * Pre-specified definition and quality levels for encoding where kPreset0 is
+ * the best quality and kPreset9 is the worst. The use of presets is useful for
+ * adaptive bitrate scenarios where the numbers are increased or decreased.
+ */
+typedef uint8_t preset_t;
+static const preset_t kPreset0 = 0;
+static const preset_t kPreset1 = 1;
+static const preset_t kPreset2 = 2;
+static const preset_t kPreset3 = 3;
+static const preset_t kPreset4 = 4;
+static const preset_t kPreset5 = 5;
+static const preset_t kPreset6 = 6;
+static const preset_t kPreset7 = 7;
+static const preset_t kPreset8 = 8;
+static const preset_t kPreset9 = 9;
+static const preset_t kPresetBest = 0;
+static const preset_t kPresetWorst = 9;
+static const preset_t kPresetLQThreshold = 4;
+
+/**
+ * Represents the details of each preset codec configuration.
+ */
+struct CodecPreset {
+	definition_t colour_res;
+	definition_t depth_res;
+	bitrate_t colour_qual;
+	bitrate_t depth_qual;
+};
+
+/**
+ * Get preset details structure from preset number.
+ */
+const CodecPreset &getPreset(preset_t);
+
+/**
+ * Get preset based upon a requested definition. If multiple presets match then
+ * the highest quality one is returned.
+ */
+const CodecPreset &getPreset(definition_t, definition_t);
+
+/**
+ * Get preset based upon a requested definition for colour channel.
+ * If multiple presets match then the highest quality one is returned.
+ */
+const CodecPreset &getPreset(definition_t);
+
+/**
+ * Get the preset id nearest to requested definition for colour and depth.
+ */
+preset_t findPreset(definition_t, definition_t);
+
+/**
+ * Get the preset id nearest to requested colour definition. If multiple presets
+ * match then return highest quality.
+ */
+preset_t findPreset(definition_t);
+
+}
+}
+
+MSGPACK_ADD_ENUM(ftl::codecs::codec_t);
+MSGPACK_ADD_ENUM(ftl::codecs::definition_t);
+
+#endif  // _FTL_CODECS_BITRATES_HPP_
diff --git a/components/codecs/include/ftl/codecs/decoder.hpp b/components/codecs/include/ftl/codecs/decoder.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..e6883680f34085f8b39fbcd02959b19ea4a2ca9e
--- /dev/null
+++ b/components/codecs/include/ftl/codecs/decoder.hpp
@@ -0,0 +1,46 @@
+#ifndef _FTL_CODECS_DECODER_HPP_
+#define _FTL_CODECS_DECODER_HPP_
+
+#include <opencv2/opencv.hpp>
+#include <opencv2/core/cuda.hpp>
+
+#include <ftl/codecs/packet.hpp>
+
+
+namespace ftl {
+namespace codecs {
+
+class Decoder;
+
+/**
+ * Allocate a decoder that can handle the received packet.
+ */
+Decoder *allocateDecoder(const ftl::codecs::Packet &);
+
+/**
+ * Release a decoder to be reused by some other stream.
+ */
+void free(Decoder *&e);
+
+/**
+ * An abstract interface for a frame decoder. An implementation of this class
+ * will take codec packets and reconstruct an image or partial image into a
+ * specified OpenCV Mat. It is not the job of the decoder to check when a frame
+ * is completed, the user of the decoder should check the packet block number
+ * and total to ensure all packets are received for a given frame. The decoder
+ * is threadsafe.
+ */
+class Decoder {
+	public:
+	Decoder() {};
+	virtual ~Decoder() {};
+
+	virtual bool decode(const ftl::codecs::Packet &pkt, cv::Mat &out)=0;
+
+	virtual bool accepts(const ftl::codecs::Packet &)=0;
+};
+
+}
+}
+
+#endif  // _FTL_CODECS_DECODER_HPP_
diff --git a/components/codecs/include/ftl/codecs/encoder.hpp b/components/codecs/include/ftl/codecs/encoder.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..560810b84060b3b062b426614da40836634fdee5
--- /dev/null
+++ b/components/codecs/include/ftl/codecs/encoder.hpp
@@ -0,0 +1,99 @@
+#ifndef _FTL_CODECS_ENCODER_HPP_
+#define _FTL_CODECS_ENCODER_HPP_
+
+#include <ftl/cuda_util.hpp>
+#include <opencv2/opencv.hpp>
+#include <opencv2/core/cuda.hpp>
+
+#include <ftl/codecs/bitrates.hpp>
+#include <ftl/codecs/packet.hpp>
+
+namespace ftl {
+namespace codecs {
+
+static const unsigned int kVideoBufferSize = 10*1024*1024;
+
+class Encoder;
+
+enum class device_t {
+	Any = 0,
+	Hardware,
+	Software,
+	NVIDIA,
+	x265,
+	OpenCV
+};
+
+/**
+ * Allocate a high quality encoder. This is a hardware encoding device if
+ * available, or otherwise a CPU encoder. There are a restricted number of
+ * high quality encoders due to system resources. A nullptr is returned if no
+ * encoders are available, however, it will return a low quality encoder if no
+ * high quality encoders are available.
+ */
+Encoder *allocateEncoder(
+		ftl::codecs::definition_t maxdef=ftl::codecs::definition_t::HD1080,
+		ftl::codecs::device_t dev=ftl::codecs::device_t::Any);
+
+/**
+ * Release an encoder to be reused by some other stream.
+ */
+void free(Encoder *&e);
+
+/**
+ * Abstract encoder interface to be implemented. Anything implementing this will
+ * convert an OpenCV Mat or GpuMat into a compressed byte array of some form.
+ */
+class Encoder {
+    public:
+    friend Encoder *allocateEncoder(ftl::codecs::definition_t,
+			ftl::codecs::device_t);
+    friend void free(Encoder *&);
+
+    public:
+    Encoder(ftl::codecs::definition_t maxdef,
+			ftl::codecs::definition_t mindef,
+			ftl::codecs::device_t dev);
+    virtual ~Encoder();
+
+	/**
+	 * Wrapper encode to allow use of presets.
+	 */
+	virtual bool encode(const cv::Mat &in, ftl::codecs::preset_t preset,
+			const std::function<void(const ftl::codecs::Packet&)> &cb);
+
+	/**
+	 * Encode a frame at specified preset and call a callback function for each
+	 * block as it is encoded. The callback may be called only once with the
+	 * entire frame or it may be called many times from many threads with
+	 * partial blocks of a frame. Each packet should be sent over the network
+	 * to all listening clients.
+	 * 
+	 * @param in An OpenCV image of the frame to compress
+	 * @param preset Codec preset for resolution and quality
+	 * @param iframe Full frame if true, else might be a delta frame
+	 * @param cb Callback containing compressed data
+	 * @return True if succeeded with encoding.
+	 */
+    virtual bool encode(
+			const cv::Mat &in,
+			ftl::codecs::definition_t definition,
+			ftl::codecs::bitrate_t bitrate,
+			const std::function<void(const ftl::codecs::Packet&)> &cb)=0;
+
+	// TODO: Eventually, use GPU memory directly since some encoders can support this
+    //virtual bool encode(const cv::cuda::GpuMat &in, std::vector<uint8_t> &out, bitrate_t bix, bool)=0;
+
+	virtual void reset() {}
+
+    protected:
+    bool available;
+	const ftl::codecs::definition_t max_definition;
+	const ftl::codecs::definition_t min_definition;
+	const ftl::codecs::device_t device;
+};
+
+}
+}
+
+#endif  // _FTL_CODECS_ENCODER_HPP_
diff --git a/components/codecs/include/ftl/codecs/nvpipe_decoder.hpp b/components/codecs/include/ftl/codecs/nvpipe_decoder.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..c13b26ec0c3ab72198f357bdb1f1ad8fae8d1f5e
--- /dev/null
+++ b/components/codecs/include/ftl/codecs/nvpipe_decoder.hpp
@@ -0,0 +1,31 @@
+#ifndef _FTL_CODECS_NVPIPE_DECODER_HPP_
+#define _FTL_CODECS_NVPIPE_DECODER_HPP_
+
+#include <ftl/codecs/decoder.hpp>
+#include <ftl/threads.hpp>
+
+#include <NvPipe.h>
+
+namespace ftl {
+namespace codecs {
+
+class NvPipeDecoder : public ftl::codecs::Decoder {
+	public:
+	NvPipeDecoder();
+	~NvPipeDecoder();
+
+	bool decode(const ftl::codecs::Packet &pkt, cv::Mat &out);
+
+	bool accepts(const ftl::codecs::Packet &pkt);
+
+	private:
+	NvPipe *nv_decoder_;
+	bool is_float_channel_;
+	ftl::codecs::definition_t last_definition_;
+	MUTEX mutex_;
+};
+
+}
+}
+
+#endif  // _FTL_CODECS_NVPIPE_DECODER_HPP_
diff --git a/components/codecs/include/ftl/codecs/nvpipe_encoder.hpp b/components/codecs/include/ftl/codecs/nvpipe_encoder.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..3f8de66cf7b77265e245a30da4522936058426c0
--- /dev/null
+++ b/components/codecs/include/ftl/codecs/nvpipe_encoder.hpp
@@ -0,0 +1,42 @@
+#ifndef _FTL_CODECS_NVPIPE_ENCODER_HPP_
+#define _FTL_CODECS_NVPIPE_ENCODER_HPP_
+
+#include <ftl/codecs/encoder.hpp>
+#include <NvPipe.h>
+
+namespace ftl {
+namespace codecs {
+
+class NvPipeEncoder : public ftl::codecs::Encoder {
+    public:
+    NvPipeEncoder(ftl::codecs::definition_t maxdef,
+			ftl::codecs::definition_t mindef);
+    ~NvPipeEncoder();
+
+	bool encode(const cv::Mat &in, ftl::codecs::preset_t preset,
+			const std::function<void(const ftl::codecs::Packet&)> &cb) {
+		return Encoder::encode(in, preset, cb);
+	}
+
+    bool encode(const cv::Mat &in, ftl::codecs::definition_t definition, ftl::codecs::bitrate_t bitrate,
+			const std::function<void(const ftl::codecs::Packet&)>&) override;
+
+    //bool encode(const cv::cuda::GpuMat &in, std::vector<uint8_t> &out, bitrate_t bix, bool);
+
+	void reset();
+
+    private:
+    NvPipe *nvenc_;
+    definition_t current_definition_;
+    bool is_float_channel_;
+	bool was_reset_;
+
+    bool _encoderMatch(const cv::Mat &in, definition_t def);
+    bool _createEncoder(const cv::Mat &in, definition_t def, bitrate_t rate);
+	ftl::codecs::definition_t _verifiedDefinition(ftl::codecs::definition_t def, const cv::Mat &in);
+};
+
+}
+}
+
+#endif  // _FTL_CODECS_NVPIPE_ENCODER_HPP_
diff --git a/components/codecs/include/ftl/codecs/opencv_decoder.hpp b/components/codecs/include/ftl/codecs/opencv_decoder.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..c4ef5ab057b1e60da12f36fd603642ea16026888
--- /dev/null
+++ b/components/codecs/include/ftl/codecs/opencv_decoder.hpp
@@ -0,0 +1,22 @@
+#ifndef _FTL_CODECS_OPENCV_DECODER_HPP_
+#define _FTL_CODECS_OPENCV_DECODER_HPP_
+
+#include <ftl/codecs/decoder.hpp>
+
+namespace ftl {
+namespace codecs {
+
+class OpenCVDecoder : public ftl::codecs::Decoder {
+	public:
+	OpenCVDecoder();
+	~OpenCVDecoder();
+
+	bool decode(const ftl::codecs::Packet &pkt, cv::Mat &out);
+
+	bool accepts(const ftl::codecs::Packet &pkt);
+};
+
+}
+}
+
+#endif  // _FTL_CODECS_OPENCV_DECODER_HPP_
diff --git a/components/codecs/include/ftl/codecs/opencv_encoder.hpp b/components/codecs/include/ftl/codecs/opencv_encoder.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..a34f8811de62277b5be53dd329517deb3ed06af3
--- /dev/null
+++ b/components/codecs/include/ftl/codecs/opencv_encoder.hpp
@@ -0,0 +1,48 @@
+#ifndef _FTL_CODECS_OPENCV_ENCODER_HPP_
+#define _FTL_CODECS_OPENCV_ENCODER_HPP_
+
+#include <ftl/codecs/encoder.hpp>
+#include <ftl/threads.hpp>
+#include <condition_variable>
+
+namespace ftl {
+namespace codecs {
+
+/**
+ * Use OpenCV imencode to encode frames as images. This codec divides each
+ * image into blocks and uses a thread per block to compress each one. It is
+ * a simplistic codec that can function at high resolution but is best used for
+ * low definition encoding due to the poor compressed data size.
+ */
+class OpenCVEncoder : public ftl::codecs::Encoder {
+    public:
+    OpenCVEncoder(ftl::codecs::definition_t maxdef,
+			ftl::codecs::definition_t mindef);
+    ~OpenCVEncoder();
+
+	bool encode(const cv::Mat &in, ftl::codecs::preset_t preset,
+			const std::function<void(const ftl::codecs::Packet&)> &cb) {
+		return Encoder::encode(in, preset, cb);
+	}
+
+    bool encode(const cv::Mat &in, ftl::codecs::definition_t definition, ftl::codecs::bitrate_t bitrate,
+			const std::function<void(const ftl::codecs::Packet&)>&) override;
+
+    //bool encode(const cv::cuda::GpuMat &in, std::vector<uint8_t> &out, bitrate_t bix, bool);
+
+	private:
+	int chunk_count_;
+	int chunk_dim_;
+	ftl::codecs::preset_t current_preset_;
+	ftl::codecs::definition_t current_definition_;
+	std::atomic<int> jobs_;
+	std::mutex job_mtx_;
+	std::condition_variable job_cv_;
+
+	bool _encodeBlock(const cv::Mat &in, ftl::codecs::Packet &pkt, ftl::codecs::bitrate_t);
+};
+
+}
+}
+
+#endif  // _FTL_CODECS_OPENCV_ENCODER_HPP_
diff --git a/components/codecs/include/ftl/codecs/packet.hpp b/components/codecs/include/ftl/codecs/packet.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..98e46a60122e6813fd22b0c0be0f09e40fbc96ce
--- /dev/null
+++ b/components/codecs/include/ftl/codecs/packet.hpp
@@ -0,0 +1,44 @@
+#ifndef _FTL_CODECS_PACKET_HPP_
+#define _FTL_CODECS_PACKET_HPP_
+
+#include <cstdint>
+#include <vector>
+#include <ftl/codecs/bitrates.hpp>
+
+#include <msgpack.hpp>
+
+namespace ftl {
+namespace codecs {
+
+/**
+ * A single network packet for the compressed video stream. It includes the raw
+ * data along with any block metadata required to reconstruct. The underlying
+ * codec may use its own blocks and packets, in which case this is essentially
+ * an empty wrapper around that. It is used in the encoding callback.
+ */
+struct Packet {
+	ftl::codecs::codec_t codec;
+	ftl::codecs::definition_t definition;
+	uint8_t block_total;	// Packets expected per frame
+	uint8_t block_number; 	// This packets number within a frame
+	std::vector<uint8_t> data;
+
+	MSGPACK_DEFINE(codec, definition, block_total, block_number, data);
+};
+
+/**
+ * Add timestamp and channel information to a raw encoded frame packet. This
+ * allows the packet to be located within a larger stream and should be sent
+ * or included before a frame packet structure.
+ */
+struct StreamPacket {
+	int64_t timestamp;
+	uint8_t channel;  // first bit = channel, second bit indicates second channel being sent
+
+	MSGPACK_DEFINE(timestamp, channel);
+};
+
+}
+}
+
+#endif  // _FTL_CODECS_PACKET_HPP_
diff --git a/components/codecs/src/bitrates.cpp b/components/codecs/src/bitrates.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..519c86712487a8be9285f28392342e6ca6c16713
--- /dev/null
+++ b/components/codecs/src/bitrates.cpp
@@ -0,0 +1,53 @@
+
+#include <ftl/codecs/bitrates.hpp>
+#include <cmath>
+
+using ftl::codecs::CodecPreset;
+using ftl::codecs::bitrate_t;
+using ftl::codecs::preset_t;
+using ftl::codecs::definition_t;
+using ftl::codecs::codec_t;
+
+static const CodecPreset presets[] = {
+	definition_t::HD1080, definition_t::HD1080, bitrate_t::High, bitrate_t::High,
+	definition_t::HD1080, definition_t::HD720, bitrate_t::Standard, bitrate_t::Standard,
+	definition_t::HD720, definition_t::HD720, bitrate_t::High, bitrate_t::High,
+	definition_t::HD720, definition_t::SD576, bitrate_t::Standard, bitrate_t::Standard,
+	definition_t::SD576, definition_t::SD576, bitrate_t::High, bitrate_t::High,
+	definition_t::SD576, definition_t::SD480, bitrate_t::Standard, bitrate_t::Standard,
+	definition_t::SD480, definition_t::SD480, bitrate_t::High, bitrate_t::High,
+	definition_t::SD480, definition_t::LD360, bitrate_t::Standard, bitrate_t::Standard,
+	definition_t::LD360, definition_t::LD360, bitrate_t::Standard, bitrate_t::Standard,
+	definition_t::LD360, definition_t::LD360, bitrate_t::Low, bitrate_t::Low
+};
+
+static const float kAspectRatio = 1.777778f;
+
+struct Resolution {
+	int width;
+	int height;
+};
+
+static const Resolution resolutions[] = {
+	7680, 4320,		// UHD8k
+	3840, 2160,		// UHD4k
+	1920, 1080,		// HD1080
+	1280, 720,		// HD720
+	1024, 576,		// SD576
+	854, 480,		// SD480
+	640, 360		// LD360
+};
+
+int ftl::codecs::getWidth(definition_t d) {
+	return resolutions[static_cast<int>(d)].width;
+}
+
+int ftl::codecs::getHeight(definition_t d) {
+	return resolutions[static_cast<int>(d)].height;
+}
+
+const CodecPreset &ftl::codecs::getPreset(preset_t p) {
+    if (p > kPresetWorst) return presets[kPresetWorst];
+    if (p < kPresetBest) return presets[kPresetBest];
+    return presets[p];
+};
diff --git a/components/codecs/src/decoder.cpp b/components/codecs/src/decoder.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..4cd5437d0cb6ea84251b55bdd139a7c0b799f411
--- /dev/null
+++ b/components/codecs/src/decoder.cpp
@@ -0,0 +1,25 @@
+#include <ftl/codecs/decoder.hpp>
+
+#include <ftl/codecs/opencv_decoder.hpp>
+#include <ftl/codecs/nvpipe_decoder.hpp>
+
+using ftl::codecs::Decoder;
+using ftl::codecs::codec_t;
+
+Decoder *ftl::codecs::allocateDecoder(const ftl::codecs::Packet &pkt) {
+	switch(pkt.codec) {
+	case codec_t::JPG		:
+	case codec_t::PNG		: return new ftl::codecs::OpenCVDecoder;
+	case codec_t::HEVC		: return new ftl::codecs::NvPipeDecoder;
+	}
+
+	return nullptr;
+}
+
+/**
+ * Release a decoder to be reused by some other stream.
+ */
+void ftl::codecs::free(Decoder *&e) {
+	delete e;
+	e = nullptr;
+}
diff --git a/components/codecs/src/encoder.cpp b/components/codecs/src/encoder.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3ebe40cf91ad75cec92937b013d3215de4104004
--- /dev/null
+++ b/components/codecs/src/encoder.cpp
@@ -0,0 +1,77 @@
+#include <ftl/codecs/encoder.hpp>
+#include <ftl/threads.hpp>
+
+#include <list>
+
+#include <ftl/config.h>
+#include <loguru.hpp>
+
+using ftl::codecs::Encoder;
+using ftl::codecs::bitrate_t;
+using ftl::codecs::definition_t;
+using ftl::codecs::preset_t;
+using ftl::codecs::device_t;
+using ftl::codecs::kPresetBest;
+using ftl::codecs::kPresetWorst;
+using ftl::codecs::kPresetLQThreshold;
+
+namespace ftl {
+namespace codecs {
+namespace internal {
+
+std::list<Encoder*> encoders;
+
+bool has_been_init = false;
+
+void init_encoders();
+
+}
+}
+}
+
+using namespace ftl::codecs::internal;
+
+static MUTEX mutex;
+
+Encoder *ftl::codecs::allocateEncoder(ftl::codecs::definition_t maxdef,
+		ftl::codecs::device_t dev) {
+    UNIQUE_LOCK(mutex, lk);
+	if (!has_been_init) init_encoders();
+
+	for (auto i=encoders.begin(); i!=encoders.end(); ++i) {
+		auto *e = *i;
+		if (!e->available) continue;
+		if (dev != device_t::Any && dev != e->device) continue;
+		if (maxdef != definition_t::Any && (maxdef < e->max_definition || maxdef > e->min_definition)) continue;
+		
+		e->available = false;
+		return e;
+	}
+
+	LOG(ERROR) << "No encoder found";
+	return nullptr;
+}
+
+void ftl::codecs::free(Encoder *&enc) {
+    UNIQUE_LOCK(mutex, lk);
+    enc->reset();
+	enc->available = true;
+    enc = nullptr;
+}
+
+Encoder::Encoder(definition_t maxdef, definition_t mindef, device_t dev) :
+		available(true), max_definition(maxdef), min_definition(mindef), device(dev) {
+
+}
+
+Encoder::~Encoder() {
+
+}
+
+bool Encoder::encode(const cv::Mat &in, preset_t preset,
+			const std::function<void(const ftl::codecs::Packet&)> &cb) {
+	const auto &settings = ftl::codecs::getPreset(preset);
+	const definition_t definition = (in.type() == CV_32F) ? settings.depth_res : settings.colour_res;
+	const bitrate_t bitrate = (in.type() == CV_32F) ? settings.depth_qual : settings.colour_qual;
+	return encode(in, definition, bitrate, cb);
+}
diff --git a/components/codecs/src/generate.cpp b/components/codecs/src/generate.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b6b3c3be98aeef8a1672ea1322c359c0632f3197
--- /dev/null
+++ b/components/codecs/src/generate.cpp
@@ -0,0 +1,45 @@
+#include <ftl/codecs/encoder.hpp>
+#include <ftl/codecs/opencv_encoder.hpp>
+#include <ftl/configurable.hpp>
+
+#include <ftl/config.h>
+#include <loguru.hpp>
+
+#ifdef HAVE_NVPIPE
+#include <ftl/codecs/nvpipe_encoder.hpp>
+#endif
+
+namespace ftl {
+namespace codecs {
+namespace internal {
+
+extern std::list<Encoder*> encoders;
+extern bool has_been_init;
+
+void fin_encoders() {
+    LOG(INFO) << "Destroying encoders...";
+    for (auto *s : encoders) delete s;
+}
+
+void init_encoders() {
+    #ifdef HAVE_NVPIPE
+    LOG(INFO) << "Adding NvPipe Encoders";
+    encoders.push_back(new ftl::codecs::NvPipeEncoder(definition_t::UHD4k, definition_t::HD720));
+    encoders.push_back(new ftl::codecs::NvPipeEncoder(definition_t::UHD4k, definition_t::HD720));
+    #endif
+
+    encoders.push_back(new ftl::codecs::OpenCVEncoder(definition_t::HD1080, definition_t::HD720));
+    encoders.push_back(new ftl::codecs::OpenCVEncoder(definition_t::HD1080, definition_t::HD720));
+    encoders.push_back(new ftl::codecs::OpenCVEncoder(definition_t::SD576, definition_t::LD360));
+    encoders.push_back(new ftl::codecs::OpenCVEncoder(definition_t::SD576, definition_t::LD360));
+	encoders.push_back(new ftl::codecs::OpenCVEncoder(definition_t::SD576, definition_t::LD360));
+    encoders.push_back(new ftl::codecs::OpenCVEncoder(definition_t::SD576, definition_t::LD360));
+
+    has_been_init = true;
+
+    atexit(fin_encoders);
+}
+
+}
+}
+}
\ No newline at end of file
diff --git a/components/codecs/src/nvpipe_decoder.cpp b/components/codecs/src/nvpipe_decoder.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..0dda5884cc7bf156e7cb134795e6f0ff2c180130
--- /dev/null
+++ b/components/codecs/src/nvpipe_decoder.cpp
@@ -0,0 +1,80 @@
+#include <ftl/codecs/nvpipe_decoder.hpp>
+
+#include <loguru.hpp>
+
+#include <ftl/cuda_util.hpp>
+//#include <cuda_runtime.h>
+
+using ftl::codecs::NvPipeDecoder;
+
+NvPipeDecoder::NvPipeDecoder() {
+	nv_decoder_ = nullptr;
+}
+
+NvPipeDecoder::~NvPipeDecoder() {
+	if (nv_decoder_ != nullptr) {
+		NvPipe_Destroy(nv_decoder_);
+	}
+}
+
+bool NvPipeDecoder::decode(const ftl::codecs::Packet &pkt, cv::Mat &out) {
+	cudaSetDevice(0);
+	UNIQUE_LOCK(mutex_,lk);
+	if (pkt.codec != codec_t::HEVC) return false;
+
+	bool is_float_frame = out.type() == CV_32F;
+
+	// Is the previous decoder still valid for current resolution and type?
+	if (nv_decoder_ != nullptr && (last_definition_ != pkt.definition || is_float_channel_ != is_float_frame)) {
+		NvPipe_Destroy(nv_decoder_);
+		nv_decoder_ = nullptr;
+	}
+
+	is_float_channel_ = is_float_frame;
+	last_definition_ = pkt.definition;
+
+	// Build a decoder instance of the correct kind
+	if (nv_decoder_ == nullptr) {
+		nv_decoder_ = NvPipe_CreateDecoder(
+				(is_float_frame) ? NVPIPE_UINT16 : NVPIPE_RGBA32,
+				NVPIPE_HEVC,
+				ftl::codecs::getWidth(pkt.definition),
+				ftl::codecs::getHeight(pkt.definition));
+		if (!nv_decoder_) {
+			//LOG(INFO) << "Bitrate=" << (int)bitrate << " width=" << ABRController::getColourWidth(bitrate);
+			LOG(FATAL) << "Could not create decoder: " << NvPipe_GetError(NULL);
+		} else {
+			LOG(INFO) << "Decoder created";
+		}
+	}
+
+	// TODO: (Nick) Move to member variable to prevent re-creation
+	cv::Mat tmp(cv::Size(ftl::codecs::getWidth(pkt.definition),ftl::codecs::getHeight(pkt.definition)), (is_float_frame) ? CV_16U : CV_8UC4);
+
+	int rc = NvPipe_Decode(nv_decoder_, pkt.data.data(), pkt.data.size(), tmp.data, tmp.cols, tmp.rows);
+	if (rc == 0) LOG(ERROR) << "NvPipe decode error: " << NvPipe_GetError(nv_decoder_);
+
+	if (is_float_frame) {
+		// Is the received frame the same size as requested output?
+		if (out.rows == ftl::codecs::getHeight(pkt.definition)) {
+			tmp.convertTo(out, CV_32FC1, 1.0f/1000.0f);
+		} else {
+			tmp.convertTo(tmp, CV_32FC1, 1.0f/1000.0f);
+			cv::resize(tmp, out, out.size());
+		}
+	} else {
+		// Is the received frame the same size as requested output?
+		if (out.rows == ftl::codecs::getHeight(pkt.definition)) {
+			cv::cvtColor(tmp, out, cv::COLOR_BGRA2BGR);
+		} else {
+			cv::cvtColor(tmp, tmp, cv::COLOR_BGRA2BGR);
+			cv::resize(tmp, out, out.size());
+		}
+	}
+
+	return rc > 0;
+}
+
+bool NvPipeDecoder::accepts(const ftl::codecs::Packet &pkt) {
+	return pkt.codec == codec_t::HEVC;
+}
diff --git a/components/codecs/src/nvpipe_encoder.cpp b/components/codecs/src/nvpipe_encoder.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..42374bedecf95a749e842a3a2ed22b5226d6d390
--- /dev/null
+++ b/components/codecs/src/nvpipe_encoder.cpp
@@ -0,0 +1,123 @@
+#include <ftl/codecs/nvpipe_encoder.hpp>
+#include <loguru.hpp>
+#include <ftl/timer.hpp>
+#include <ftl/codecs/bitrates.hpp>
+#include <ftl/cuda_util.hpp>
+
+using ftl::codecs::NvPipeEncoder;
+using ftl::codecs::bitrate_t;
+using ftl::codecs::codec_t;
+using ftl::codecs::definition_t;
+using ftl::codecs::preset_t;
+using ftl::codecs::CodecPreset;
+using ftl::codecs::Packet;
+
+NvPipeEncoder::NvPipeEncoder(definition_t maxdef,
+			definition_t mindef) : Encoder(maxdef, mindef, ftl::codecs::device_t::Hardware) {
+    nvenc_ = nullptr;
+    current_definition_ = definition_t::HD1080;
+    is_float_channel_ = false;
+	was_reset_ = false;
+}
+
+NvPipeEncoder::~NvPipeEncoder() {
+    if (nvenc_) NvPipe_Destroy(nvenc_);
+}
+
+void NvPipeEncoder::reset() {
+	was_reset_ = true;
+}
+
+/* Check preset resolution is not better than actual resolution. */
+definition_t NvPipeEncoder::_verifiedDefinition(definition_t def, const cv::Mat &in) {
+	int height = ftl::codecs::getHeight(def);
+
+	// FIXME: Make sure this can't go forever
+	while (height > in.rows) {
+		def = static_cast<definition_t>(int(def)+1);
+		height = ftl::codecs::getHeight(def);
+	}
+
+	return def;
+}
+
+bool NvPipeEncoder::encode(const cv::Mat &in, definition_t odefinition, bitrate_t bitrate, const std::function<void(const ftl::codecs::Packet&)> &cb) {
+    cudaSetDevice(0);
+	auto definition = _verifiedDefinition(odefinition, in);
+
+	//LOG(INFO) << "Definition: " << ftl::codecs::getWidth(definition) << "x" << ftl::codecs::getHeight(definition);
+
+    if (!_createEncoder(in, definition, bitrate)) return false;
+
+    //LOG(INFO) << "NvPipe Encode: " << int(definition) << " " << in.cols;
+
+    cv::Mat tmp;
+    if (in.type() == CV_32F) {
+		in.convertTo(tmp, CV_16UC1, 1000);
+    } else if (in.type() == CV_8UC3) {
+        cv::cvtColor(in, tmp, cv::COLOR_BGR2BGRA);
+	} else {
+		tmp = in;
+	}
+
+	Packet pkt;
+	pkt.codec = codec_t::HEVC;
+	pkt.definition = definition;
+	pkt.block_total = 1;
+	pkt.block_number = 0;
+
+    pkt.data.resize(ftl::codecs::kVideoBufferSize);
+    uint64_t cs = NvPipe_Encode(
+        nvenc_,
+        tmp.data,
+        tmp.step,
+        pkt.data.data(),
+        ftl::codecs::kVideoBufferSize,
+        in.cols,
+        in.rows,
+        was_reset_		// Force IFrame!
+    );
+    pkt.data.resize(cs);
+	was_reset_ = false;
+
+    if (cs == 0) {
+        LOG(ERROR) << "Could not encode video frame: " << NvPipe_GetError(nvenc_);
+        return false;
+    } else {
+		cb(pkt);
+        return true;
+    }
+}
+
+bool NvPipeEncoder::_encoderMatch(const cv::Mat &in, definition_t def) {
+    return ((in.type() == CV_32F && is_float_channel_) ||
+        ((in.type() == CV_8UC3 || in.type() == CV_8UC4) && !is_float_channel_)) && current_definition_ == def;
+}
+
+bool NvPipeEncoder::_createEncoder(const cv::Mat &in, definition_t def, bitrate_t rate) {
+    if (_encoderMatch(in, def) && nvenc_) return true;
+
+    if (in.type() == CV_32F) is_float_channel_ = true;
+    else is_float_channel_ = false;
+    current_definition_ = def;
+
+    if (nvenc_) NvPipe_Destroy(nvenc_);
+    const int fps = 1000/ftl::timer::getInterval();
+    nvenc_ = NvPipe_CreateEncoder(
+        (is_float_channel_) ? NVPIPE_UINT16 : NVPIPE_RGBA32,
+        NVPIPE_HEVC,
+        (is_float_channel_) ? NVPIPE_LOSSLESS : NVPIPE_LOSSY,
+        16*1000*1000,
+        fps,				// FPS
+        ftl::codecs::getWidth(def),	// Output Width
+        ftl::codecs::getHeight(def)	// Output Height
+    );
+
+    if (!nvenc_) {
+        LOG(ERROR) << "Could not create video encoder: " << NvPipe_GetError(NULL);
+        return false;
+    } else {
+        LOG(INFO) << "NvPipe encoder created";
+        return true;
+    }
+}
diff --git a/components/codecs/src/opencv_decoder.cpp b/components/codecs/src/opencv_decoder.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3bbd82fa21ae4b60fef00b81c409ae9c59311d39
--- /dev/null
+++ b/components/codecs/src/opencv_decoder.cpp
@@ -0,0 +1,65 @@
+#include <ftl/codecs/opencv_decoder.hpp>
+
+#include <loguru.hpp>
+
+using ftl::codecs::OpenCVDecoder;
+using ftl::codecs::codec_t;
+
+OpenCVDecoder::OpenCVDecoder() {
+
+}
+
+OpenCVDecoder::~OpenCVDecoder() {
+
+}
+
+bool OpenCVDecoder::accepts(const ftl::codecs::Packet &pkt) {
+	return (pkt.codec == codec_t::JPG || pkt.codec == codec_t::PNG);
+}
+
+bool OpenCVDecoder::decode(const ftl::codecs::Packet &pkt, cv::Mat &out) {
+	cv::Mat tmp;
+
+	int chunk_dim = std::sqrt(pkt.block_total);
+	int chunk_width = out.cols / chunk_dim;
+	int chunk_height = out.rows / chunk_dim;
+
+	// Build chunk head
+	int cx = (pkt.block_number % chunk_dim) * chunk_width;
+	int cy = (pkt.block_number / chunk_dim) * chunk_height;
+	cv::Rect roi(cx,cy,chunk_width,chunk_height);
+	cv::Mat chunkHead = out(roi);
+
+	// Decode in temporary buffers to prevent long locks
+	cv::imdecode(pkt.data, cv::IMREAD_UNCHANGED, &tmp);
+
+	// Apply colour correction to chunk
+	//ftl::rgbd::colourCorrection(tmp_rgb, gamma_, temperature_);
+
+
+	// TODO:(Nick) Decode directly into double buffer if no scaling
+	// Can either check JPG/PNG headers or just use pkt definition.
+
+	// Original size so just copy
+	if (tmp.cols == chunkHead.cols) {
+		if (!tmp.empty() && tmp.type() == CV_16U && chunkHead.type() == CV_32F) {
+			tmp.convertTo(chunkHead, CV_32FC1, 1.0f/1000.0f); //(16.0f*10.0f));
+		} else if (!tmp.empty() && tmp.type() == CV_8UC3 && chunkHead.type() == CV_8UC3) {
+			tmp.copyTo(chunkHead);
+		} else {
+			// Silent ignore?
+		}
+	// Downsized so needs a scale up
+	} else {
+		if (!tmp.empty() && tmp.type() == CV_16U && chunkHead.type() == CV_32F) {
+			tmp.convertTo(tmp, CV_32FC1, 1.0f/1000.0f); //(16.0f*10.0f));
+			cv::resize(tmp, chunkHead, chunkHead.size(), 0, 0, cv::INTER_NEAREST);
+		} else if (!tmp.empty() && tmp.type() == CV_8UC3 && chunkHead.type() == CV_8UC3) {
+			cv::resize(tmp, chunkHead, chunkHead.size());
+		} else {
+			// Silent ignore?
+		}
+	}
+
+	return true;
+}
diff --git a/components/codecs/src/opencv_encoder.cpp b/components/codecs/src/opencv_encoder.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5dafabf29a64c14969504960da8af2bc2e27fd2a
--- /dev/null
+++ b/components/codecs/src/opencv_encoder.cpp
@@ -0,0 +1,116 @@
+#include <ftl/codecs/opencv_encoder.hpp>
+
+#include <loguru.hpp>
+#include <vector>
+
+using ftl::codecs::definition_t;
+using ftl::codecs::codec_t;
+using ftl::codecs::bitrate_t;
+using ftl::codecs::OpenCVEncoder;
+using ftl::codecs::preset_t;
+using ftl::codecs::CodecPreset;
+using std::vector;
+
+OpenCVEncoder::OpenCVEncoder(ftl::codecs::definition_t maxdef,
+			ftl::codecs::definition_t mindef) : Encoder(maxdef, mindef, ftl::codecs::device_t::Software) {
+	jobs_ = 0;
+}
+
+OpenCVEncoder::~OpenCVEncoder() {
+    
+}
+
+bool OpenCVEncoder::encode(const cv::Mat &in, definition_t definition, bitrate_t bitrate, const std::function<void(const ftl::codecs::Packet&)> &cb) {
+	cv::Mat tmp;
+	bool is_colour = in.type() != CV_32F;
+	current_definition_ = definition;
+
+	// Scale down image to match requested definition...
+	if (ftl::codecs::getHeight(current_definition_) < in.rows) {
+		cv::resize(in, tmp, cv::Size(ftl::codecs::getWidth(current_definition_), ftl::codecs::getHeight(current_definition_)), 0, 0, (is_colour) ? 1 : cv::INTER_NEAREST);
+	} else {
+		tmp = in;
+	}
+
+	// Represent float at 16bit int
+    if (!is_colour) {
+		tmp.convertTo(tmp, CV_16UC1, 1000);
+	}
+
+	// TODO: Choose these base upon resolution
+	chunk_count_ = 16;
+	chunk_dim_ = 4;
+	jobs_ = chunk_count_;
+
+	for (int i=0; i<chunk_count_; ++i) {
+		// Add chunk job to thread pool
+		ftl::pool.push([this,i,&tmp,cb,is_colour,bitrate](int id) {
+			ftl::codecs::Packet pkt;
+			pkt.block_number = i;
+			pkt.block_total = chunk_count_;
+			pkt.definition = current_definition_;
+			pkt.codec = (is_colour) ? codec_t::JPG : codec_t::PNG;
+
+			try {
+				_encodeBlock(tmp, pkt, bitrate);
+			} catch(...) {
+				LOG(ERROR) << "OpenCV encode block exception: " << i;
+			}
+
+			try {
+				cb(pkt);
+			} catch(...) {
+				LOG(ERROR) << "OpenCV encoder callback exception";
+			}
+
+			std::unique_lock<std::mutex> lk(job_mtx_);
+			--jobs_;
+			if (jobs_ == 0) job_cv_.notify_one();
+		});
+	}
+
+	std::unique_lock<std::mutex> lk(job_mtx_);
+	job_cv_.wait_for(lk, std::chrono::seconds(20), [this]{ return jobs_ == 0; });
+	if (jobs_ != 0) {
+		LOG(FATAL) << "Deadlock detected (" << jobs_ << ")";
+	}
+
+	return true;
+}
+
+bool OpenCVEncoder::_encodeBlock(const cv::Mat &in, ftl::codecs::Packet &pkt, bitrate_t bitrate) {
+	int chunk_width = in.cols / chunk_dim_;
+	int chunk_height = in.rows / chunk_dim_;
+
+	// Build chunk heads
+	int cx = (pkt.block_number % chunk_dim_) * chunk_width;
+	int cy = (pkt.block_number / chunk_dim_) * chunk_height;
+	cv::Rect roi(cx,cy,chunk_width,chunk_height);
+	cv::Mat chunkHead = in(roi);
+
+	if (pkt.codec == codec_t::PNG) {
+		vector<int> params = {cv::IMWRITE_PNG_COMPRESSION, 1};
+		if (!cv::imencode(".png", chunkHead, pkt.data, params)) {
+			LOG(ERROR) << "PNG Encoding error";
+			return false;
+		}
+		return true;
+	} else if (pkt.codec == codec_t::JPG) {
+		int q = 95;
+
+		switch (bitrate) {
+		case bitrate_t::High		: q = 95; break;
+		case bitrate_t::Standard	: q = 75; break;
+		case bitrate_t::Low			: q = 50; break;
+		}
+
+		vector<int> params = {cv::IMWRITE_JPEG_QUALITY, q};
+		cv::imencode(".jpg", chunkHead, pkt.data, params);
+		return true;
+	} else {
+		LOG(ERROR) << "Bad channel configuration: imagetype=" << in.type(); 
+	}
+
+	return false;
+}
+
diff --git a/components/codecs/test/CMakeLists.txt b/components/codecs/test/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..534336fed6ce5135199105f0784fa260204f64de
--- /dev/null
+++ b/components/codecs/test/CMakeLists.txt
@@ -0,0 +1,32 @@
+### OpenCV Codec Unit ################################################################
+add_executable(opencv_codec_unit
+	./tests.cpp
+	../src/bitrates.cpp
+	../src/encoder.cpp
+	../src/opencv_encoder.cpp
+	../src/opencv_decoder.cpp
+	./opencv_codec_unit.cpp
+)
+target_include_directories(opencv_codec_unit PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../include")
+target_link_libraries(opencv_codec_unit
+	Threads::Threads ${OS_LIBS} ${OpenCV_LIBS} ftlcommon)
+
+
+add_test(OpenCVCodecUnitTest opencv_codec_unit)
+
+
+### NvPipe Codec Unit ################################################################
+add_executable(nvpipe_codec_unit
+	./tests.cpp
+	../src/bitrates.cpp
+	../src/encoder.cpp
+	../src/nvpipe_encoder.cpp
+	../src/nvpipe_decoder.cpp
+	./nvpipe_codec_unit.cpp
+)
+target_include_directories(nvpipe_codec_unit PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../include")
+target_link_libraries(nvpipe_codec_unit
+	Threads::Threads ${OS_LIBS} ${OpenCV_LIBS} ${CUDA_LIBRARIES} ftlcommon nvpipe)
+
+
+add_test(NvPipeCodecUnitTest nvpipe_codec_unit)
diff --git a/components/codecs/test/catch.hpp b/components/codecs/test/catch.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..b1b2411d24885571e21ec4b3653af58c57011c3e
--- /dev/null
+++ b/components/codecs/test/catch.hpp
@@ -0,0 +1,14362 @@
+/*
+ *  Catch v2.5.0
+ *  Generated: 2018-11-26 20:46:12.165372
+ *  ----------------------------------------------------------
+ *  This file has been merged from multiple headers. Please don't edit it directly
+ *  Copyright (c) 2018 Two Blue Cubes Ltd. All rights reserved.
+ *
+ *  Distributed under the Boost Software License, Version 1.0. (See accompanying
+ *  file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+ */
+#ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED
+#define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED
+// start catch.hpp
+
+
+#define CATCH_VERSION_MAJOR 2
+#define CATCH_VERSION_MINOR 5
+#define CATCH_VERSION_PATCH 0
+
+#ifdef __clang__
+#    pragma clang system_header
+#elif defined __GNUC__
+#    pragma GCC system_header
+#endif
+
+// start catch_suppress_warnings.h
+
+#ifdef __clang__
+#   ifdef __ICC // icpc defines the __clang__ macro
+#       pragma warning(push)
+#       pragma warning(disable: 161 1682)
+#   else // __ICC
+#       pragma clang diagnostic push
+#       pragma clang diagnostic ignored "-Wpadded"
+#       pragma clang diagnostic ignored "-Wswitch-enum"
+#       pragma clang diagnostic ignored "-Wcovered-switch-default"
+#    endif
+#elif defined __GNUC__
+     // GCC likes to warn on REQUIREs, and we cannot suppress them
+     // locally because g++'s support for _Pragma is lacking in older,
+     // still supported, versions
+#    pragma GCC diagnostic ignored "-Wparentheses"
+#    pragma GCC diagnostic push
+#    pragma GCC diagnostic ignored "-Wunused-variable"
+#    pragma GCC diagnostic ignored "-Wpadded"
+#endif
+// end catch_suppress_warnings.h
+#if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER)
+#  define CATCH_IMPL
+#  define CATCH_CONFIG_ALL_PARTS
+#endif
+
+// In the impl file, we want to have access to all parts of the headers
+// Can also be used to sanely support PCHs
+#if defined(CATCH_CONFIG_ALL_PARTS)
+#  define CATCH_CONFIG_EXTERNAL_INTERFACES
+#  if defined(CATCH_CONFIG_DISABLE_MATCHERS)
+#    undef CATCH_CONFIG_DISABLE_MATCHERS
+#  endif
+#  if !defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER)
+#    define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER
+#  endif
+#endif
+
+#if !defined(CATCH_CONFIG_IMPL_ONLY)
+// start catch_platform.h
+
+#ifdef __APPLE__
+# include <TargetConditionals.h>
+# if TARGET_OS_OSX == 1
+#  define CATCH_PLATFORM_MAC
+# elif TARGET_OS_IPHONE == 1
+#  define CATCH_PLATFORM_IPHONE
+# endif
+
+#elif defined(linux) || defined(__linux) || defined(__linux__)
+#  define CATCH_PLATFORM_LINUX
+
+#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) || defined(__MINGW32__)
+#  define CATCH_PLATFORM_WINDOWS
+#endif
+
+// end catch_platform.h
+
+#ifdef CATCH_IMPL
+#  ifndef CLARA_CONFIG_MAIN
+#    define CLARA_CONFIG_MAIN_NOT_DEFINED
+#    define CLARA_CONFIG_MAIN
+#  endif
+#endif
+
+// start catch_user_interfaces.h
+
+namespace Catch {
+    unsigned int rngSeed();
+}
+
+// end catch_user_interfaces.h
+// start catch_tag_alias_autoregistrar.h
+
+// start catch_common.h
+
+// start catch_compiler_capabilities.h
+
+// Detect a number of compiler features - by compiler
+// The following features are defined:
+//
+// CATCH_CONFIG_COUNTER : is the __COUNTER__ macro supported?
+// CATCH_CONFIG_WINDOWS_SEH : is Windows SEH supported?
+// CATCH_CONFIG_POSIX_SIGNALS : are POSIX signals supported?
+// CATCH_CONFIG_DISABLE_EXCEPTIONS : Are exceptions enabled?
+// ****************
+// Note to maintainers: if new toggles are added please document them
+// in configuration.md, too
+// ****************
+
+// In general each macro has a _NO_<feature name> form
+// (e.g. CATCH_CONFIG_NO_POSIX_SIGNALS) which disables the feature.
+// Many features, at point of detection, define an _INTERNAL_ macro, so they
+// can be combined, en-mass, with the _NO_ forms later.
+
+#ifdef __cplusplus
+
+#  if (__cplusplus >= 201402L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201402L)
+#    define CATCH_CPP14_OR_GREATER
+#  endif
+
+#  if (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)
+#    define CATCH_CPP17_OR_GREATER
+#  endif
+
+#endif
+
+#if defined(CATCH_CPP17_OR_GREATER)
+#  define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS
+#endif
+
+#ifdef __clang__
+
+#       define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \
+            _Pragma( "clang diagnostic push" ) \
+            _Pragma( "clang diagnostic ignored \"-Wexit-time-destructors\"" ) \
+            _Pragma( "clang diagnostic ignored \"-Wglobal-constructors\"")
+#       define CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \
+            _Pragma( "clang diagnostic pop" )
+
+#       define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \
+            _Pragma( "clang diagnostic push" ) \
+            _Pragma( "clang diagnostic ignored \"-Wparentheses\"" )
+#       define CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \
+            _Pragma( "clang diagnostic pop" )
+
+#       define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \
+            _Pragma( "clang diagnostic push" ) \
+            _Pragma( "clang diagnostic ignored \"-Wunused-variable\"" )
+#       define CATCH_INTERNAL_UNSUPPRESS_UNUSED_WARNINGS \
+            _Pragma( "clang diagnostic pop" )
+
+#endif // __clang__
+
+////////////////////////////////////////////////////////////////////////////////
+// Assume that non-Windows platforms support posix signals by default
+#if !defined(CATCH_PLATFORM_WINDOWS)
+    #define CATCH_INTERNAL_CONFIG_POSIX_SIGNALS
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+// We know some environments not to support full POSIX signals
+#if defined(__CYGWIN__) || defined(__QNX__) || defined(__EMSCRIPTEN__) || defined(__DJGPP__)
+    #define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS
+#endif
+
+#ifdef __OS400__
+#       define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS
+#       define CATCH_CONFIG_COLOUR_NONE
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+// Android somehow still does not support std::to_string
+#if defined(__ANDROID__)
+#    define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+// Not all Windows environments support SEH properly
+#if defined(__MINGW32__)
+#    define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+// PS4
+#if defined(__ORBIS__)
+#    define CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+// Cygwin
+#ifdef __CYGWIN__
+
+// Required for some versions of Cygwin to declare gettimeofday
+// see: http://stackoverflow.com/questions/36901803/gettimeofday-not-declared-in-this-scope-cygwin
+#   define _BSD_SOURCE
+// some versions of cygwin (most) do not support std::to_string. Use the libstd check.
+// https://gcc.gnu.org/onlinedocs/gcc-4.8.2/libstdc++/api/a01053_source.html line 2812-2813
+# if !((__cplusplus >= 201103L) && defined(_GLIBCXX_USE_C99) \
+	       && !defined(_GLIBCXX_HAVE_BROKEN_VSWPRINTF))
+
+#	define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING
+
+# endif
+#endif // __CYGWIN__
+
+////////////////////////////////////////////////////////////////////////////////
+// Visual C++
+#ifdef _MSC_VER
+
+#  if _MSC_VER >= 1900 // Visual Studio 2015 or newer
+#    define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS
+#  endif
+
+// Universal Windows platform does not support SEH
+// Or console colours (or console at all...)
+#  if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP)
+#    define CATCH_CONFIG_COLOUR_NONE
+#  else
+#    define CATCH_INTERNAL_CONFIG_WINDOWS_SEH
+#  endif
+
+// MSVC traditional preprocessor needs some workaround for __VA_ARGS__
+// _MSVC_TRADITIONAL == 0 means new conformant preprocessor
+// _MSVC_TRADITIONAL == 1 means old traditional non-conformant preprocessor
+#  if !defined(_MSVC_TRADITIONAL) || (defined(_MSVC_TRADITIONAL) && _MSVC_TRADITIONAL)
+#    define CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR
+#  endif
+
+#endif // _MSC_VER
+
+////////////////////////////////////////////////////////////////////////////////
+// Check if we are compiled with -fno-exceptions or equivalent
+#if defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND)
+#  define CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+// DJGPP
+#ifdef __DJGPP__
+#  define CATCH_INTERNAL_CONFIG_NO_WCHAR
+#endif // __DJGPP__
+
+////////////////////////////////////////////////////////////////////////////////
+// Embarcadero C++Build
+#if defined(__BORLANDC__)
+    #define CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Use of __COUNTER__ is suppressed during code analysis in
+// CLion/AppCode 2017.2.x and former, because __COUNTER__ is not properly
+// handled by it.
+// Otherwise all supported compilers support COUNTER macro,
+// but user still might want to turn it off
+#if ( !defined(__JETBRAINS_IDE__) || __JETBRAINS_IDE__ >= 20170300L )
+    #define CATCH_INTERNAL_CONFIG_COUNTER
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+// Check if string_view is available and usable
+// The check is split apart to work around v140 (VS2015) preprocessor issue...
+#if defined(__has_include)
+#if __has_include(<string_view>) && defined(CATCH_CPP17_OR_GREATER)
+#    define CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW
+#endif
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+// Check if variant is available and usable
+#if defined(__has_include)
+#  if __has_include(<variant>) && defined(CATCH_CPP17_OR_GREATER)
+#    if defined(__clang__) && (__clang_major__ < 8)
+       // work around clang bug with libstdc++ https://bugs.llvm.org/show_bug.cgi?id=31852
+       // fix should be in clang 8, workaround in libstdc++ 8.2
+#      include <ciso646>
+#      if defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9)
+#        define CATCH_CONFIG_NO_CPP17_VARIANT
+#     else
+#        define CATCH_INTERNAL_CONFIG_CPP17_VARIANT
+#      endif // defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9)
+#    endif // defined(__clang__) && (__clang_major__ < 8)
+#  endif // __has_include(<variant>) && defined(CATCH_CPP17_OR_GREATER)
+#endif // __has_include
+
+#if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER)
+#   define CATCH_CONFIG_COUNTER
+#endif
+#if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && !defined(CATCH_CONFIG_WINDOWS_SEH) && !defined(CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH)
+#   define CATCH_CONFIG_WINDOWS_SEH
+#endif
+// This is set by default, because we assume that unix compilers are posix-signal-compatible by default.
+#if defined(CATCH_INTERNAL_CONFIG_POSIX_SIGNALS) && !defined(CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_POSIX_SIGNALS)
+#   define CATCH_CONFIG_POSIX_SIGNALS
+#endif
+// This is set by default, because we assume that compilers with no wchar_t support are just rare exceptions.
+#if !defined(CATCH_INTERNAL_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_WCHAR)
+#   define CATCH_CONFIG_WCHAR
+#endif
+
+#if !defined(CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_CPP11_TO_STRING)
+#    define CATCH_CONFIG_CPP11_TO_STRING
+#endif
+
+#if defined(CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) && !defined(CATCH_CONFIG_NO_CPP17_UNCAUGHT_EXCEPTIONS) && !defined(CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS)
+#  define CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS
+#endif
+
+#if defined(CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_NO_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_CPP17_STRING_VIEW)
+#  define CATCH_CONFIG_CPP17_STRING_VIEW
+#endif
+
+#if defined(CATCH_INTERNAL_CONFIG_CPP17_VARIANT) && !defined(CATCH_CONFIG_NO_CPP17_VARIANT) && !defined(CATCH_CONFIG_CPP17_VARIANT)
+#  define CATCH_CONFIG_CPP17_VARIANT
+#endif
+
+#if defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT)
+#  define CATCH_INTERNAL_CONFIG_NEW_CAPTURE
+#endif
+
+#if defined(CATCH_INTERNAL_CONFIG_NEW_CAPTURE) && !defined(CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NEW_CAPTURE)
+#  define CATCH_CONFIG_NEW_CAPTURE
+#endif
+
+#if !defined(CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED) && !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)
+#  define CATCH_CONFIG_DISABLE_EXCEPTIONS
+#endif
+
+#if defined(CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_NO_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_POLYFILL_ISNAN)
+#  define CATCH_CONFIG_POLYFILL_ISNAN
+#endif
+
+#if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS)
+#   define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS
+#   define CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS
+#endif
+#if !defined(CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS)
+#   define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS
+#   define CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS
+#endif
+#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS)
+#   define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS
+#   define CATCH_INTERNAL_UNSUPPRESS_UNUSED_WARNINGS
+#endif
+
+#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)
+#define CATCH_TRY if ((true))
+#define CATCH_CATCH_ALL if ((false))
+#define CATCH_CATCH_ANON(type) if ((false))
+#else
+#define CATCH_TRY try
+#define CATCH_CATCH_ALL catch (...)
+#define CATCH_CATCH_ANON(type) catch (type)
+#endif
+
+#if defined(CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_NO_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR)
+#define CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR
+#endif
+
+// end catch_compiler_capabilities.h
+#define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line
+#define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line )
+#ifdef CATCH_CONFIG_COUNTER
+#  define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ )
+#else
+#  define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ )
+#endif
+
+#include <iosfwd>
+#include <string>
+#include <cstdint>
+
+// We need a dummy global operator<< so we can bring it into Catch namespace later
+struct Catch_global_namespace_dummy {};
+std::ostream& operator<<(std::ostream&, Catch_global_namespace_dummy);
+
+namespace Catch {
+
+    struct CaseSensitive { enum Choice {
+        Yes,
+        No
+    }; };
+
+    class NonCopyable {
+        NonCopyable( NonCopyable const& )              = delete;
+        NonCopyable( NonCopyable && )                  = delete;
+        NonCopyable& operator = ( NonCopyable const& ) = delete;
+        NonCopyable& operator = ( NonCopyable && )     = delete;
+
+    protected:
+        NonCopyable();
+        virtual ~NonCopyable();
+    };
+
+    struct SourceLineInfo {
+
+        SourceLineInfo() = delete;
+        SourceLineInfo( char const* _file, std::size_t _line ) noexcept
+        :   file( _file ),
+            line( _line )
+        {}
+
+        SourceLineInfo( SourceLineInfo const& other )        = default;
+        SourceLineInfo( SourceLineInfo && )                  = default;
+        SourceLineInfo& operator = ( SourceLineInfo const& ) = default;
+        SourceLineInfo& operator = ( SourceLineInfo && )     = default;
+
+        bool empty() const noexcept;
+        bool operator == ( SourceLineInfo const& other ) const noexcept;
+        bool operator < ( SourceLineInfo const& other ) const noexcept;
+
+        char const* file;
+        std::size_t line;
+    };
+
+    std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info );
+
+    // Bring in operator<< from global namespace into Catch namespace
+    // This is necessary because the overload of operator<< above makes
+    // lookup stop at namespace Catch
+    using ::operator<<;
+
+    // Use this in variadic streaming macros to allow
+    //    >> +StreamEndStop
+    // as well as
+    //    >> stuff +StreamEndStop
+    struct StreamEndStop {
+        std::string operator+() const;
+    };
+    template<typename T>
+    T const& operator + ( T const& value, StreamEndStop ) {
+        return value;
+    }
+}
+
+#define CATCH_INTERNAL_LINEINFO \
+    ::Catch::SourceLineInfo( __FILE__, static_cast<std::size_t>( __LINE__ ) )
+
+// end catch_common.h
+namespace Catch {
+
+    struct RegistrarForTagAliases {
+        RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo );
+    };
+
+} // end namespace Catch
+
+#define CATCH_REGISTER_TAG_ALIAS( alias, spec ) \
+    CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \
+    namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } \
+    CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS
+
+// end catch_tag_alias_autoregistrar.h
+// start catch_test_registry.h
+
+// start catch_interfaces_testcase.h
+
+#include <vector>
+#include <memory>
+
+namespace Catch {
+
+    class TestSpec;
+
+    struct ITestInvoker {
+        virtual void invoke () const = 0;
+        virtual ~ITestInvoker();
+    };
+
+    using ITestCasePtr = std::shared_ptr<ITestInvoker>;
+
+    class TestCase;
+    struct IConfig;
+
+    struct ITestCaseRegistry {
+        virtual ~ITestCaseRegistry();
+        virtual std::vector<TestCase> const& getAllTests() const = 0;
+        virtual std::vector<TestCase> const& getAllTestsSorted( IConfig const& config ) const = 0;
+    };
+
+    bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config );
+    std::vector<TestCase> filterTests( std::vector<TestCase> const& testCases, TestSpec const& testSpec, IConfig const& config );
+    std::vector<TestCase> const& getAllTestCasesSorted( IConfig const& config );
+
+}
+
+// end catch_interfaces_testcase.h
+// start catch_stringref.h
+
+#include <cstddef>
+#include <string>
+#include <iosfwd>
+
+namespace Catch {
+
+    class StringData;
+
+    /// A non-owning string class (similar to the forthcoming std::string_view)
+    /// Note that, because a StringRef may be a substring of another string,
+    /// it may not be null terminated. c_str() must return a null terminated
+    /// string, however, and so the StringRef will internally take ownership
+    /// (taking a copy), if necessary. In theory this ownership is not externally
+    /// visible - but it does mean (substring) StringRefs should not be shared between
+    /// threads.
+    class StringRef {
+    public:
+        using size_type = std::size_t;
+
+    private:
+        friend struct StringRefTestAccess;
+
+        char const* m_start;
+        size_type m_size;
+
+        char* m_data = nullptr;
+
+        void takeOwnership();
+
+        static constexpr char const* const s_empty = "";
+
+    public: // construction/ assignment
+        StringRef() noexcept
+        :   StringRef( s_empty, 0 )
+        {}
+
+        StringRef( StringRef const& other ) noexcept
+        :   m_start( other.m_start ),
+            m_size( other.m_size )
+        {}
+
+        StringRef( StringRef&& other ) noexcept
+        :   m_start( other.m_start ),
+            m_size( other.m_size ),
+            m_data( other.m_data )
+        {
+            other.m_data = nullptr;
+        }
+
+        StringRef( char const* rawChars ) noexcept;
+
+        StringRef( char const* rawChars, size_type size ) noexcept
+        :   m_start( rawChars ),
+            m_size( size )
+        {}
+
+        StringRef( std::string const& stdString ) noexcept
+        :   m_start( stdString.c_str() ),
+            m_size( stdString.size() )
+        {}
+
+        ~StringRef() noexcept {
+            delete[] m_data;
+        }
+
+        auto operator = ( StringRef const &other ) noexcept -> StringRef& {
+            delete[] m_data;
+            m_data = nullptr;
+            m_start = other.m_start;
+            m_size = other.m_size;
+            return *this;
+        }
+
+        operator std::string() const;
+
+        void swap( StringRef& other ) noexcept;
+
+    public: // operators
+        auto operator == ( StringRef const& other ) const noexcept -> bool;
+        auto operator != ( StringRef const& other ) const noexcept -> bool;
+
+        auto operator[] ( size_type index ) const noexcept -> char;
+
+    public: // named queries
+        auto empty() const noexcept -> bool {
+            return m_size == 0;
+        }
+        auto size() const noexcept -> size_type {
+            return m_size;
+        }
+
+        auto numberOfCharacters() const noexcept -> size_type;
+        auto c_str() const -> char const*;
+
+    public: // substrings and searches
+        auto substr( size_type start, size_type size ) const noexcept -> StringRef;
+
+        // Returns the current start pointer.
+        // Note that the pointer can change when if the StringRef is a substring
+        auto currentData() const noexcept -> char const*;
+
+    private: // ownership queries - may not be consistent between calls
+        auto isOwned() const noexcept -> bool;
+        auto isSubstring() const noexcept -> bool;
+    };
+
+    auto operator + ( StringRef const& lhs, StringRef const& rhs ) -> std::string;
+    auto operator + ( StringRef const& lhs, char const* rhs ) -> std::string;
+    auto operator + ( char const* lhs, StringRef const& rhs ) -> std::string;
+
+    auto operator += ( std::string& lhs, StringRef const& sr ) -> std::string&;
+    auto operator << ( std::ostream& os, StringRef const& sr ) -> std::ostream&;
+
+    inline auto operator "" _sr( char const* rawChars, std::size_t size ) noexcept -> StringRef {
+        return StringRef( rawChars, size );
+    }
+
+} // namespace Catch
+
+inline auto operator "" _catch_sr( char const* rawChars, std::size_t size ) noexcept -> Catch::StringRef {
+    return Catch::StringRef( rawChars, size );
+}
+
+// end catch_stringref.h
+// start catch_type_traits.hpp
+
+
+namespace Catch{
+
+#ifdef CATCH_CPP17_OR_GREATER
+	template <typename...>
+	inline constexpr auto is_unique = std::true_type{};
+
+	template <typename T, typename... Rest>
+	inline constexpr auto is_unique<T, Rest...> = std::bool_constant<
+		(!std::is_same_v<T, Rest> && ...) && is_unique<Rest...>
+	>{};
+#else
+
+template <typename...>
+struct is_unique : std::true_type{};
+
+template <typename T0, typename T1, typename... Rest>
+struct is_unique<T0, T1, Rest...> : std::integral_constant
+<bool,
+     !std::is_same<T0, T1>::value
+     && is_unique<T0, Rest...>::value
+     && is_unique<T1, Rest...>::value
+>{};
+
+#endif
+}
+
+// end catch_type_traits.hpp
+// start catch_preprocessor.hpp
+
+
+#define CATCH_RECURSION_LEVEL0(...) __VA_ARGS__
+#define CATCH_RECURSION_LEVEL1(...) CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(__VA_ARGS__)))
+#define CATCH_RECURSION_LEVEL2(...) CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(__VA_ARGS__)))
+#define CATCH_RECURSION_LEVEL3(...) CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(__VA_ARGS__)))
+#define CATCH_RECURSION_LEVEL4(...) CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(__VA_ARGS__)))
+#define CATCH_RECURSION_LEVEL5(...) CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(__VA_ARGS__)))
+
+#ifdef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR
+#define INTERNAL_CATCH_EXPAND_VARGS(...) __VA_ARGS__
+// MSVC needs more evaluations
+#define CATCH_RECURSION_LEVEL6(...) CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(__VA_ARGS__)))
+#define CATCH_RECURSE(...)  CATCH_RECURSION_LEVEL6(CATCH_RECURSION_LEVEL6(__VA_ARGS__))
+#else
+#define CATCH_RECURSE(...)  CATCH_RECURSION_LEVEL5(__VA_ARGS__)
+#endif
+
+#define CATCH_REC_END(...)
+#define CATCH_REC_OUT
+
+#define CATCH_EMPTY()
+#define CATCH_DEFER(id) id CATCH_EMPTY()
+
+#define CATCH_REC_GET_END2() 0, CATCH_REC_END
+#define CATCH_REC_GET_END1(...) CATCH_REC_GET_END2
+#define CATCH_REC_GET_END(...) CATCH_REC_GET_END1
+#define CATCH_REC_NEXT0(test, next, ...) next CATCH_REC_OUT
+#define CATCH_REC_NEXT1(test, next) CATCH_DEFER ( CATCH_REC_NEXT0 ) ( test, next, 0)
+#define CATCH_REC_NEXT(test, next)  CATCH_REC_NEXT1(CATCH_REC_GET_END test, next)
+
+#define CATCH_REC_LIST0(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ )
+#define CATCH_REC_LIST1(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0) ) ( f, peek, __VA_ARGS__ )
+#define CATCH_REC_LIST2(f, x, peek, ...)   f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ )
+
+#define CATCH_REC_LIST0_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ )
+#define CATCH_REC_LIST1_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0_UD) ) ( f, userdata, peek, __VA_ARGS__ )
+#define CATCH_REC_LIST2_UD(f, userdata, x, peek, ...)   f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ )
+
+// Applies the function macro `f` to each of the remaining parameters, inserts commas between the results,
+// and passes userdata as the first parameter to each invocation,
+// e.g. CATCH_REC_LIST_UD(f, x, a, b, c) evaluates to f(x, a), f(x, b), f(x, c)
+#define CATCH_REC_LIST_UD(f, userdata, ...) CATCH_RECURSE(CATCH_REC_LIST2_UD(f, userdata, __VA_ARGS__, ()()(), ()()(), ()()(), 0))
+
+#define CATCH_REC_LIST(f, ...) CATCH_RECURSE(CATCH_REC_LIST2(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0))
+
+#define INTERNAL_CATCH_EXPAND1(param) INTERNAL_CATCH_EXPAND2(param)
+#define INTERNAL_CATCH_EXPAND2(...) INTERNAL_CATCH_NO## __VA_ARGS__
+#define INTERNAL_CATCH_DEF(...) INTERNAL_CATCH_DEF __VA_ARGS__
+#define INTERNAL_CATCH_NOINTERNAL_CATCH_DEF
+
+#define INTERNAL_CATCH_REMOVE_PARENS(...) INTERNAL_CATCH_EXPAND1(INTERNAL_CATCH_DEF __VA_ARGS__)
+
+#define INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME2(Name, ...) INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME3(Name, __VA_ARGS__)
+#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR
+#define INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME3(Name,...) Name " - " #__VA_ARGS__
+#define INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME(Name,...) INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME2(Name, INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__))
+#else
+// MSVC is adding extra space and needs more calls to properly remove ()
+#define INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME3(Name,...) Name " -" #__VA_ARGS__
+#define INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME1(Name, ...) INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME2(Name, __VA_ARGS__)
+#define INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME(Name, ...) INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME1(Name, INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__)))
+#endif
+
+// end catch_preprocessor.hpp
+namespace Catch {
+
+template<typename C>
+class TestInvokerAsMethod : public ITestInvoker {
+    void (C::*m_testAsMethod)();
+public:
+    TestInvokerAsMethod( void (C::*testAsMethod)() ) noexcept : m_testAsMethod( testAsMethod ) {}
+
+    void invoke() const override {
+        C obj;
+        (obj.*m_testAsMethod)();
+    }
+};
+
+auto makeTestInvoker( void(*testAsFunction)() ) noexcept -> ITestInvoker*;
+
+template<typename C>
+auto makeTestInvoker( void (C::*testAsMethod)() ) noexcept -> ITestInvoker* {
+    return new(std::nothrow) TestInvokerAsMethod<C>( testAsMethod );
+}
+
+struct NameAndTags {
+    NameAndTags( StringRef const& name_ = StringRef(), StringRef const& tags_ = StringRef() ) noexcept;
+    StringRef name;
+    StringRef tags;
+};
+
+struct AutoReg : NonCopyable {
+    AutoReg( ITestInvoker* invoker, SourceLineInfo const& lineInfo, StringRef const& classOrMethod, NameAndTags const& nameAndTags ) noexcept;
+    ~AutoReg();
+};
+
+} // end namespace Catch
+
+#if defined(CATCH_CONFIG_DISABLE)
+    #define INTERNAL_CATCH_TESTCASE_NO_REGISTRATION( TestName, ... ) \
+        static void TestName()
+    #define INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION( TestName, ClassName, ... ) \
+        namespace{                        \
+            struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName) { \
+                void test();              \
+            };                            \
+        }                                 \
+        void TestName::test()
+    #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION( TestName, ... )  \
+        template<typename TestType>                                             \
+        static void TestName()
+    #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION( TestName, ClassName, ... )    \
+        namespace{                                                                                  \
+            template<typename TestType>                                                             \
+            struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName <TestType>) {     \
+                void test();                                                                        \
+            };                                                                                      \
+        }                                                                                           \
+        template<typename TestType>                                                                 \
+        void TestName::test()
+#endif
+
+    ///////////////////////////////////////////////////////////////////////////////
+    #define INTERNAL_CATCH_TESTCASE2( TestName, ... ) \
+        static void TestName(); \
+        CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \
+        namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( &TestName ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ __VA_ARGS__ } ); } /* NOLINT */ \
+        CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \
+        static void TestName()
+    #define INTERNAL_CATCH_TESTCASE( ... ) \
+        INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), __VA_ARGS__ )
+
+    ///////////////////////////////////////////////////////////////////////////////
+    #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, ... ) \
+        CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \
+        namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( &QualifiedMethod ), CATCH_INTERNAL_LINEINFO, "&" #QualifiedMethod, Catch::NameAndTags{ __VA_ARGS__ } ); } /* NOLINT */ \
+        CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS
+
+    ///////////////////////////////////////////////////////////////////////////////
+    #define INTERNAL_CATCH_TEST_CASE_METHOD2( TestName, ClassName, ... )\
+        CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \
+        namespace{ \
+            struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName) { \
+                void test(); \
+            }; \
+            Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( Catch::makeTestInvoker( &TestName::test ), CATCH_INTERNAL_LINEINFO, #ClassName, Catch::NameAndTags{ __VA_ARGS__ } ); /* NOLINT */ \
+        } \
+        CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \
+        void TestName::test()
+    #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, ... ) \
+        INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), ClassName, __VA_ARGS__ )
+
+    ///////////////////////////////////////////////////////////////////////////////
+    #define INTERNAL_CATCH_REGISTER_TESTCASE( Function, ... ) \
+        CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \
+        Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( Function ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ __VA_ARGS__ } ); /* NOLINT */ \
+        CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS
+
+    ///////////////////////////////////////////////////////////////////////////////
+    #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_2(TestName, TestFunc, Name, Tags, ... )\
+        CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \
+        template<typename TestType> \
+        static void TestFunc();\
+        namespace {\
+            template<typename...Types> \
+            struct TestName{\
+                template<typename...Ts> \
+                TestName(Ts...names){\
+                    CATCH_INTERNAL_CHECK_UNIQUE_TYPES(CATCH_REC_LIST(INTERNAL_CATCH_REMOVE_PARENS, __VA_ARGS__)) \
+                    using expander = int[];\
+                    (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestFunc<Types> ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ names, Tags } ), 0)... };/* NOLINT */ \
+                }\
+            };\
+            INTERNAL_CATCH_TEMPLATE_REGISTRY_INITIATE(TestName, Name, __VA_ARGS__) \
+        }\
+        CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \
+        template<typename TestType> \
+        static void TestFunc()
+
+#if defined(CATCH_CPP17_OR_GREATER)
+#define CATCH_INTERNAL_CHECK_UNIQUE_TYPES(...) static_assert(Catch::is_unique<__VA_ARGS__>,"Duplicate type detected in declaration of template test case");
+#else
+#define CATCH_INTERNAL_CHECK_UNIQUE_TYPES(...) static_assert(Catch::is_unique<__VA_ARGS__>::value,"Duplicate type detected in declaration of template test case");
+#endif
+
+#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR
+    #define INTERNAL_CATCH_TEMPLATE_TEST_CASE(Name, Tags, ...) \
+        INTERNAL_CATCH_TEMPLATE_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, __VA_ARGS__ )
+#else
+    #define INTERNAL_CATCH_TEMPLATE_TEST_CASE(Name, Tags, ...) \
+        INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, __VA_ARGS__ ) )
+#endif
+
+    #define INTERNAL_CATCH_TEMPLATE_REGISTRY_INITIATE(TestName, Name, ...)\
+        static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\
+            TestName<CATCH_REC_LIST(INTERNAL_CATCH_REMOVE_PARENS, __VA_ARGS__)>(CATCH_REC_LIST_UD(INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME,Name, __VA_ARGS__));\
+            return 0;\
+        }();
+
+    #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( TestNameClass, TestName, ClassName, Name, Tags, ... ) \
+        CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \
+        namespace{ \
+            template<typename TestType> \
+            struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName <TestType>) { \
+                void test();\
+            };\
+            template<typename...Types> \
+            struct TestNameClass{\
+                template<typename...Ts> \
+                TestNameClass(Ts...names){\
+                    CATCH_INTERNAL_CHECK_UNIQUE_TYPES(CATCH_REC_LIST(INTERNAL_CATCH_REMOVE_PARENS, __VA_ARGS__)) \
+                    using expander = int[];\
+                    (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestName<Types>::test ), CATCH_INTERNAL_LINEINFO, #ClassName, Catch::NameAndTags{ names, Tags } ), 0)... };/* NOLINT */ \
+                }\
+            };\
+            INTERNAL_CATCH_TEMPLATE_REGISTRY_INITIATE(TestNameClass, Name, __VA_ARGS__)\
+        }\
+        CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS\
+        template<typename TestType> \
+        void TestName<TestType>::test()
+
+#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR
+    #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( ClassName, Name, Tags,... ) \
+        INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____C_L_A_S_S____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) , ClassName, Name, Tags, __VA_ARGS__ )
+#else
+    #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( ClassName, Name, Tags,... ) \
+        INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____C_L_A_S_S____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) , ClassName, Name, Tags, __VA_ARGS__ ) )
+#endif
+
+// end catch_test_registry.h
+// start catch_capture.hpp
+
+// start catch_assertionhandler.h
+
+// start catch_assertioninfo.h
+
+// start catch_result_type.h
+
+namespace Catch {
+
+    // ResultWas::OfType enum
+    struct ResultWas { enum OfType {
+        Unknown = -1,
+        Ok = 0,
+        Info = 1,
+        Warning = 2,
+
+        FailureBit = 0x10,
+
+        ExpressionFailed = FailureBit | 1,
+        ExplicitFailure = FailureBit | 2,
+
+        Exception = 0x100 | FailureBit,
+
+        ThrewException = Exception | 1,
+        DidntThrowException = Exception | 2,
+
+        FatalErrorCondition = 0x200 | FailureBit
+
+    }; };
+
+    bool isOk( ResultWas::OfType resultType );
+    bool isJustInfo( int flags );
+
+    // ResultDisposition::Flags enum
+    struct ResultDisposition { enum Flags {
+        Normal = 0x01,
+
+        ContinueOnFailure = 0x02,   // Failures fail test, but execution continues
+        FalseTest = 0x04,           // Prefix expression with !
+        SuppressFail = 0x08         // Failures are reported but do not fail the test
+    }; };
+
+    ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs );
+
+    bool shouldContinueOnFailure( int flags );
+    inline bool isFalseTest( int flags ) { return ( flags & ResultDisposition::FalseTest ) != 0; }
+    bool shouldSuppressFailure( int flags );
+
+} // end namespace Catch
+
+// end catch_result_type.h
+namespace Catch {
+
+    struct AssertionInfo
+    {
+        StringRef macroName;
+        SourceLineInfo lineInfo;
+        StringRef capturedExpression;
+        ResultDisposition::Flags resultDisposition;
+
+        // We want to delete this constructor but a compiler bug in 4.8 means
+        // the struct is then treated as non-aggregate
+        //AssertionInfo() = delete;
+    };
+
+} // end namespace Catch
+
+// end catch_assertioninfo.h
+// start catch_decomposer.h
+
+// start catch_tostring.h
+
+#include <vector>
+#include <cstddef>
+#include <type_traits>
+#include <string>
+// start catch_stream.h
+
+#include <iosfwd>
+#include <cstddef>
+#include <ostream>
+
+namespace Catch {
+
+    std::ostream& cout();
+    std::ostream& cerr();
+    std::ostream& clog();
+
+    class StringRef;
+
+    struct IStream {
+        virtual ~IStream();
+        virtual std::ostream& stream() const = 0;
+    };
+
+    auto makeStream( StringRef const &filename ) -> IStream const*;
+
+    class ReusableStringStream {
+        std::size_t m_index;
+        std::ostream* m_oss;
+    public:
+        ReusableStringStream();
+        ~ReusableStringStream();
+
+        auto str() const -> std::string;
+
+        template<typename T>
+        auto operator << ( T const& value ) -> ReusableStringStream& {
+            *m_oss << value;
+            return *this;
+        }
+        auto get() -> std::ostream& { return *m_oss; }
+    };
+}
+
+// end catch_stream.h
+
+#ifdef CATCH_CONFIG_CPP17_STRING_VIEW
+#include <string_view>
+#endif
+
+#ifdef __OBJC__
+// start catch_objc_arc.hpp
+
+#import <Foundation/Foundation.h>
+
+#ifdef __has_feature
+#define CATCH_ARC_ENABLED __has_feature(objc_arc)
+#else
+#define CATCH_ARC_ENABLED 0
+#endif
+
+void arcSafeRelease( NSObject* obj );
+id performOptionalSelector( id obj, SEL sel );
+
+#if !CATCH_ARC_ENABLED
+inline void arcSafeRelease( NSObject* obj ) {
+    [obj release];
+}
+inline id performOptionalSelector( id obj, SEL sel ) {
+    if( [obj respondsToSelector: sel] )
+        return [obj performSelector: sel];
+    return nil;
+}
+#define CATCH_UNSAFE_UNRETAINED
+#define CATCH_ARC_STRONG
+#else
+inline void arcSafeRelease( NSObject* ){}
+inline id performOptionalSelector( id obj, SEL sel ) {
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
+#endif
+    if( [obj respondsToSelector: sel] )
+        return [obj performSelector: sel];
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+    return nil;
+}
+#define CATCH_UNSAFE_UNRETAINED __unsafe_unretained
+#define CATCH_ARC_STRONG __strong
+#endif
+
+// end catch_objc_arc.hpp
+#endif
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable:4180) // We attempt to stream a function (address) by const&, which MSVC complains about but is harmless
+#endif
+
+namespace Catch {
+    namespace Detail {
+
+        extern const std::string unprintableString;
+
+        std::string rawMemoryToString( const void *object, std::size_t size );
+
+        template<typename T>
+        std::string rawMemoryToString( const T& object ) {
+          return rawMemoryToString( &object, sizeof(object) );
+        }
+
+        template<typename T>
+        class IsStreamInsertable {
+            template<typename SS, typename TT>
+            static auto test(int)
+                -> decltype(std::declval<SS&>() << std::declval<TT>(), std::true_type());
+
+            template<typename, typename>
+            static auto test(...)->std::false_type;
+
+        public:
+            static const bool value = decltype(test<std::ostream, const T&>(0))::value;
+        };
+
+        template<typename E>
+        std::string convertUnknownEnumToString( E e );
+
+        template<typename T>
+        typename std::enable_if<
+            !std::is_enum<T>::value && !std::is_base_of<std::exception, T>::value,
+        std::string>::type convertUnstreamable( T const& ) {
+            return Detail::unprintableString;
+        }
+        template<typename T>
+        typename std::enable_if<
+            !std::is_enum<T>::value && std::is_base_of<std::exception, T>::value,
+         std::string>::type convertUnstreamable(T const& ex) {
+            return ex.what();
+        }
+
+        template<typename T>
+        typename std::enable_if<
+            std::is_enum<T>::value
+        , std::string>::type convertUnstreamable( T const& value ) {
+            return convertUnknownEnumToString( value );
+        }
+
+#if defined(_MANAGED)
+        //! Convert a CLR string to a utf8 std::string
+        template<typename T>
+        std::string clrReferenceToString( T^ ref ) {
+            if (ref == nullptr)
+                return std::string("null");
+            auto bytes = System::Text::Encoding::UTF8->GetBytes(ref->ToString());
+            cli::pin_ptr<System::Byte> p = &bytes[0];
+            return std::string(reinterpret_cast<char const *>(p), bytes->Length);
+        }
+#endif
+
+    } // namespace Detail
+
+    // If we decide for C++14, change these to enable_if_ts
+    template <typename T, typename = void>
+    struct StringMaker {
+        template <typename Fake = T>
+        static
+        typename std::enable_if<::Catch::Detail::IsStreamInsertable<Fake>::value, std::string>::type
+            convert(const Fake& value) {
+                ReusableStringStream rss;
+                // NB: call using the function-like syntax to avoid ambiguity with
+                // user-defined templated operator<< under clang.
+                rss.operator<<(value);
+                return rss.str();
+        }
+
+        template <typename Fake = T>
+        static
+        typename std::enable_if<!::Catch::Detail::IsStreamInsertable<Fake>::value, std::string>::type
+            convert( const Fake& value ) {
+#if !defined(CATCH_CONFIG_FALLBACK_STRINGIFIER)
+            return Detail::convertUnstreamable(value);
+#else
+            return CATCH_CONFIG_FALLBACK_STRINGIFIER(value);
+#endif
+        }
+    };
+
+    namespace Detail {
+
+        // This function dispatches all stringification requests inside of Catch.
+        // Should be preferably called fully qualified, like ::Catch::Detail::stringify
+        template <typename T>
+        std::string stringify(const T& e) {
+            return ::Catch::StringMaker<typename std::remove_cv<typename std::remove_reference<T>::type>::type>::convert(e);
+        }
+
+        template<typename E>
+        std::string convertUnknownEnumToString( E e ) {
+            return ::Catch::Detail::stringify(static_cast<typename std::underlying_type<E>::type>(e));
+        }
+
+#if defined(_MANAGED)
+        template <typename T>
+        std::string stringify( T^ e ) {
+            return ::Catch::StringMaker<T^>::convert(e);
+        }
+#endif
+
+    } // namespace Detail
+
+    // Some predefined specializations
+
+    template<>
+    struct StringMaker<std::string> {
+        static std::string convert(const std::string& str);
+    };
+
+#ifdef CATCH_CONFIG_CPP17_STRING_VIEW
+    template<>
+    struct StringMaker<std::string_view> {
+        static std::string convert(std::string_view str);
+    };
+#endif
+
+    template<>
+    struct StringMaker<char const *> {
+        static std::string convert(char const * str);
+    };
+    template<>
+    struct StringMaker<char *> {
+        static std::string convert(char * str);
+    };
+
+#ifdef CATCH_CONFIG_WCHAR
+    template<>
+    struct StringMaker<std::wstring> {
+        static std::string convert(const std::wstring& wstr);
+    };
+
+# ifdef CATCH_CONFIG_CPP17_STRING_VIEW
+    template<>
+    struct StringMaker<std::wstring_view> {
+        static std::string convert(std::wstring_view str);
+    };
+# endif
+
+    template<>
+    struct StringMaker<wchar_t const *> {
+        static std::string convert(wchar_t const * str);
+    };
+    template<>
+    struct StringMaker<wchar_t *> {
+        static std::string convert(wchar_t * str);
+    };
+#endif
+
+    // TBD: Should we use `strnlen` to ensure that we don't go out of the buffer,
+    //      while keeping string semantics?
+    template<int SZ>
+    struct StringMaker<char[SZ]> {
+        static std::string convert(char const* str) {
+            return ::Catch::Detail::stringify(std::string{ str });
+        }
+    };
+    template<int SZ>
+    struct StringMaker<signed char[SZ]> {
+        static std::string convert(signed char const* str) {
+            return ::Catch::Detail::stringify(std::string{ reinterpret_cast<char const *>(str) });
+        }
+    };
+    template<int SZ>
+    struct StringMaker<unsigned char[SZ]> {
+        static std::string convert(unsigned char const* str) {
+            return ::Catch::Detail::stringify(std::string{ reinterpret_cast<char const *>(str) });
+        }
+    };
+
+    template<>
+    struct StringMaker<int> {
+        static std::string convert(int value);
+    };
+    template<>
+    struct StringMaker<long> {
+        static std::string convert(long value);
+    };
+    template<>
+    struct StringMaker<long long> {
+        static std::string convert(long long value);
+    };
+    template<>
+    struct StringMaker<unsigned int> {
+        static std::string convert(unsigned int value);
+    };
+    template<>
+    struct StringMaker<unsigned long> {
+        static std::string convert(unsigned long value);
+    };
+    template<>
+    struct StringMaker<unsigned long long> {
+        static std::string convert(unsigned long long value);
+    };
+
+    template<>
+    struct StringMaker<bool> {
+        static std::string convert(bool b);
+    };
+
+    template<>
+    struct StringMaker<char> {
+        static std::string convert(char c);
+    };
+    template<>
+    struct StringMaker<signed char> {
+        static std::string convert(signed char c);
+    };
+    template<>
+    struct StringMaker<unsigned char> {
+        static std::string convert(unsigned char c);
+    };
+
+    template<>
+    struct StringMaker<std::nullptr_t> {
+        static std::string convert(std::nullptr_t);
+    };
+
+    template<>
+    struct StringMaker<float> {
+        static std::string convert(float value);
+    };
+    template<>
+    struct StringMaker<double> {
+        static std::string convert(double value);
+    };
+
+    template <typename T>
+    struct StringMaker<T*> {
+        template <typename U>
+        static std::string convert(U* p) {
+            if (p) {
+                return ::Catch::Detail::rawMemoryToString(p);
+            } else {
+                return "nullptr";
+            }
+        }
+    };
+
+    template <typename R, typename C>
+    struct StringMaker<R C::*> {
+        static std::string convert(R C::* p) {
+            if (p) {
+                return ::Catch::Detail::rawMemoryToString(p);
+            } else {
+                return "nullptr";
+            }
+        }
+    };
+
+#if defined(_MANAGED)
+    template <typename T>
+    struct StringMaker<T^> {
+        static std::string convert( T^ ref ) {
+            return ::Catch::Detail::clrReferenceToString(ref);
+        }
+    };
+#endif
+
+    namespace Detail {
+        template<typename InputIterator>
+        std::string rangeToString(InputIterator first, InputIterator last) {
+            ReusableStringStream rss;
+            rss << "{ ";
+            if (first != last) {
+                rss << ::Catch::Detail::stringify(*first);
+                for (++first; first != last; ++first)
+                    rss << ", " << ::Catch::Detail::stringify(*first);
+            }
+            rss << " }";
+            return rss.str();
+        }
+    }
+
+#ifdef __OBJC__
+    template<>
+    struct StringMaker<NSString*> {
+        static std::string convert(NSString * nsstring) {
+            if (!nsstring)
+                return "nil";
+            return std::string("@") + [nsstring UTF8String];
+        }
+    };
+    template<>
+    struct StringMaker<NSObject*> {
+        static std::string convert(NSObject* nsObject) {
+            return ::Catch::Detail::stringify([nsObject description]);
+        }
+
+    };
+    namespace Detail {
+        inline std::string stringify( NSString* nsstring ) {
+            return StringMaker<NSString*>::convert( nsstring );
+        }
+
+    } // namespace Detail
+#endif // __OBJC__
+
+} // namespace Catch
+
+//////////////////////////////////////////////////////
+// Separate std-lib types stringification, so it can be selectively enabled
+// This means that we do not bring in
+
+#if defined(CATCH_CONFIG_ENABLE_ALL_STRINGMAKERS)
+#  define CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER
+#  define CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER
+#  define CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER
+#  define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER
+#endif
+
+// Separate std::pair specialization
+#if defined(CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER)
+#include <utility>
+namespace Catch {
+    template<typename T1, typename T2>
+    struct StringMaker<std::pair<T1, T2> > {
+        static std::string convert(const std::pair<T1, T2>& pair) {
+            ReusableStringStream rss;
+            rss << "{ "
+                << ::Catch::Detail::stringify(pair.first)
+                << ", "
+                << ::Catch::Detail::stringify(pair.second)
+                << " }";
+            return rss.str();
+        }
+    };
+}
+#endif // CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER
+
+// Separate std::tuple specialization
+#if defined(CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER)
+#include <tuple>
+namespace Catch {
+    namespace Detail {
+        template<
+            typename Tuple,
+            std::size_t N = 0,
+            bool = (N < std::tuple_size<Tuple>::value)
+            >
+            struct TupleElementPrinter {
+            static void print(const Tuple& tuple, std::ostream& os) {
+                os << (N ? ", " : " ")
+                    << ::Catch::Detail::stringify(std::get<N>(tuple));
+                TupleElementPrinter<Tuple, N + 1>::print(tuple, os);
+            }
+        };
+
+        template<
+            typename Tuple,
+            std::size_t N
+        >
+            struct TupleElementPrinter<Tuple, N, false> {
+            static void print(const Tuple&, std::ostream&) {}
+        };
+
+    }
+
+    template<typename ...Types>
+    struct StringMaker<std::tuple<Types...>> {
+        static std::string convert(const std::tuple<Types...>& tuple) {
+            ReusableStringStream rss;
+            rss << '{';
+            Detail::TupleElementPrinter<std::tuple<Types...>>::print(tuple, rss.get());
+            rss << " }";
+            return rss.str();
+        }
+    };
+}
+#endif // CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER
+
+#if defined(CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER) && defined(CATCH_CONFIG_CPP17_VARIANT)
+#include <variant>
+namespace Catch {
+    template<>
+    struct StringMaker<std::monostate> {
+        static std::string convert(const std::monostate&) {
+            return "{ }";
+        }
+    };
+
+    template<typename... Elements>
+    struct StringMaker<std::variant<Elements...>> {
+        static std::string convert(const std::variant<Elements...>& variant) {
+            if (variant.valueless_by_exception()) {
+                return "{valueless variant}";
+            } else {
+                return std::visit(
+                    [](const auto& value) {
+                        return ::Catch::Detail::stringify(value);
+                    },
+                    variant
+                );
+            }
+        }
+    };
+}
+#endif // CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER
+
+namespace Catch {
+    struct not_this_one {}; // Tag type for detecting which begin/ end are being selected
+
+    // Import begin/ end from std here so they are considered alongside the fallback (...) overloads in this namespace
+    using std::begin;
+    using std::end;
+
+    not_this_one begin( ... );
+    not_this_one end( ... );
+
+    template <typename T>
+    struct is_range {
+        static const bool value =
+            !std::is_same<decltype(begin(std::declval<T>())), not_this_one>::value &&
+            !std::is_same<decltype(end(std::declval<T>())), not_this_one>::value;
+    };
+
+#if defined(_MANAGED) // Managed types are never ranges
+    template <typename T>
+    struct is_range<T^> {
+        static const bool value = false;
+    };
+#endif
+
+    template<typename Range>
+    std::string rangeToString( Range const& range ) {
+        return ::Catch::Detail::rangeToString( begin( range ), end( range ) );
+    }
+
+    // Handle vector<bool> specially
+    template<typename Allocator>
+    std::string rangeToString( std::vector<bool, Allocator> const& v ) {
+        ReusableStringStream rss;
+        rss << "{ ";
+        bool first = true;
+        for( bool b : v ) {
+            if( first )
+                first = false;
+            else
+                rss << ", ";
+            rss << ::Catch::Detail::stringify( b );
+        }
+        rss << " }";
+        return rss.str();
+    }
+
+    template<typename R>
+    struct StringMaker<R, typename std::enable_if<is_range<R>::value && !::Catch::Detail::IsStreamInsertable<R>::value>::type> {
+        static std::string convert( R const& range ) {
+            return rangeToString( range );
+        }
+    };
+
+    template <typename T, int SZ>
+    struct StringMaker<T[SZ]> {
+        static std::string convert(T const(&arr)[SZ]) {
+            return rangeToString(arr);
+        }
+    };
+
+} // namespace Catch
+
+// Separate std::chrono::duration specialization
+#if defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER)
+#include <ctime>
+#include <ratio>
+#include <chrono>
+
+namespace Catch {
+
+template <class Ratio>
+struct ratio_string {
+    static std::string symbol();
+};
+
+template <class Ratio>
+std::string ratio_string<Ratio>::symbol() {
+    Catch::ReusableStringStream rss;
+    rss << '[' << Ratio::num << '/'
+        << Ratio::den << ']';
+    return rss.str();
+}
+template <>
+struct ratio_string<std::atto> {
+    static std::string symbol();
+};
+template <>
+struct ratio_string<std::femto> {
+    static std::string symbol();
+};
+template <>
+struct ratio_string<std::pico> {
+    static std::string symbol();
+};
+template <>
+struct ratio_string<std::nano> {
+    static std::string symbol();
+};
+template <>
+struct ratio_string<std::micro> {
+    static std::string symbol();
+};
+template <>
+struct ratio_string<std::milli> {
+    static std::string symbol();
+};
+
+    ////////////
+    // std::chrono::duration specializations
+    template<typename Value, typename Ratio>
+    struct StringMaker<std::chrono::duration<Value, Ratio>> {
+        static std::string convert(std::chrono::duration<Value, Ratio> const& duration) {
+            ReusableStringStream rss;
+            rss << duration.count() << ' ' << ratio_string<Ratio>::symbol() << 's';
+            return rss.str();
+        }
+    };
+    template<typename Value>
+    struct StringMaker<std::chrono::duration<Value, std::ratio<1>>> {
+        static std::string convert(std::chrono::duration<Value, std::ratio<1>> const& duration) {
+            ReusableStringStream rss;
+            rss << duration.count() << " s";
+            return rss.str();
+        }
+    };
+    template<typename Value>
+    struct StringMaker<std::chrono::duration<Value, std::ratio<60>>> {
+        static std::string convert(std::chrono::duration<Value, std::ratio<60>> const& duration) {
+            ReusableStringStream rss;
+            rss << duration.count() << " m";
+            return rss.str();
+        }
+    };
+    template<typename Value>
+    struct StringMaker<std::chrono::duration<Value, std::ratio<3600>>> {
+        static std::string convert(std::chrono::duration<Value, std::ratio<3600>> const& duration) {
+            ReusableStringStream rss;
+            rss << duration.count() << " h";
+            return rss.str();
+        }
+    };
+
+    ////////////
+    // std::chrono::time_point specialization
+    // Generic time_point cannot be specialized, only std::chrono::time_point<system_clock>
+    template<typename Clock, typename Duration>
+    struct StringMaker<std::chrono::time_point<Clock, Duration>> {
+        static std::string convert(std::chrono::time_point<Clock, Duration> const& time_point) {
+            return ::Catch::Detail::stringify(time_point.time_since_epoch()) + " since epoch";
+        }
+    };
+    // std::chrono::time_point<system_clock> specialization
+    template<typename Duration>
+    struct StringMaker<std::chrono::time_point<std::chrono::system_clock, Duration>> {
+        static std::string convert(std::chrono::time_point<std::chrono::system_clock, Duration> const& time_point) {
+            auto converted = std::chrono::system_clock::to_time_t(time_point);
+
+#ifdef _MSC_VER
+            std::tm timeInfo = {};
+            gmtime_s(&timeInfo, &converted);
+#else
+            std::tm* timeInfo = std::gmtime(&converted);
+#endif
+
+            auto const timeStampSize = sizeof("2017-01-16T17:06:45Z");
+            char timeStamp[timeStampSize];
+            const char * const fmt = "%Y-%m-%dT%H:%M:%SZ";
+
+#ifdef _MSC_VER
+            std::strftime(timeStamp, timeStampSize, fmt, &timeInfo);
+#else
+            std::strftime(timeStamp, timeStampSize, fmt, timeInfo);
+#endif
+            return std::string(timeStamp);
+        }
+    };
+}
+#endif // CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER
+
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
+// end catch_tostring.h
+#include <iosfwd>
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable:4389) // '==' : signed/unsigned mismatch
+#pragma warning(disable:4018) // more "signed/unsigned mismatch"
+#pragma warning(disable:4312) // Converting int to T* using reinterpret_cast (issue on x64 platform)
+#pragma warning(disable:4180) // qualifier applied to function type has no meaning
+#endif
+
+namespace Catch {
+
+    struct ITransientExpression {
+        auto isBinaryExpression() const -> bool { return m_isBinaryExpression; }
+        auto getResult() const -> bool { return m_result; }
+        virtual void streamReconstructedExpression( std::ostream &os ) const = 0;
+
+        ITransientExpression( bool isBinaryExpression, bool result )
+        :   m_isBinaryExpression( isBinaryExpression ),
+            m_result( result )
+        {}
+
+        // We don't actually need a virtual destructor, but many static analysers
+        // complain if it's not here :-(
+        virtual ~ITransientExpression();
+
+        bool m_isBinaryExpression;
+        bool m_result;
+
+    };
+
+    void formatReconstructedExpression( std::ostream &os, std::string const& lhs, StringRef op, std::string const& rhs );
+
+    template<typename LhsT, typename RhsT>
+    class BinaryExpr  : public ITransientExpression {
+        LhsT m_lhs;
+        StringRef m_op;
+        RhsT m_rhs;
+
+        void streamReconstructedExpression( std::ostream &os ) const override {
+            formatReconstructedExpression
+                    ( os, Catch::Detail::stringify( m_lhs ), m_op, Catch::Detail::stringify( m_rhs ) );
+        }
+
+    public:
+        BinaryExpr( bool comparisonResult, LhsT lhs, StringRef op, RhsT rhs )
+        :   ITransientExpression{ true, comparisonResult },
+            m_lhs( lhs ),
+            m_op( op ),
+            m_rhs( rhs )
+        {}
+    };
+
+    template<typename LhsT>
+    class UnaryExpr : public ITransientExpression {
+        LhsT m_lhs;
+
+        void streamReconstructedExpression( std::ostream &os ) const override {
+            os << Catch::Detail::stringify( m_lhs );
+        }
+
+    public:
+        explicit UnaryExpr( LhsT lhs )
+        :   ITransientExpression{ false, lhs ? true : false },
+            m_lhs( lhs )
+        {}
+    };
+
+    // Specialised comparison functions to handle equality comparisons between ints and pointers (NULL deduces as an int)
+    template<typename LhsT, typename RhsT>
+    auto compareEqual( LhsT const& lhs, RhsT const& rhs ) -> bool { return static_cast<bool>(lhs == rhs); }
+    template<typename T>
+    auto compareEqual( T* const& lhs, int rhs ) -> bool { return lhs == reinterpret_cast<void const*>( rhs ); }
+    template<typename T>
+    auto compareEqual( T* const& lhs, long rhs ) -> bool { return lhs == reinterpret_cast<void const*>( rhs ); }
+    template<typename T>
+    auto compareEqual( int lhs, T* const& rhs ) -> bool { return reinterpret_cast<void const*>( lhs ) == rhs; }
+    template<typename T>
+    auto compareEqual( long lhs, T* const& rhs ) -> bool { return reinterpret_cast<void const*>( lhs ) == rhs; }
+
+    template<typename LhsT, typename RhsT>
+    auto compareNotEqual( LhsT const& lhs, RhsT&& rhs ) -> bool { return static_cast<bool>(lhs != rhs); }
+    template<typename T>
+    auto compareNotEqual( T* const& lhs, int rhs ) -> bool { return lhs != reinterpret_cast<void const*>( rhs ); }
+    template<typename T>
+    auto compareNotEqual( T* const& lhs, long rhs ) -> bool { return lhs != reinterpret_cast<void const*>( rhs ); }
+    template<typename T>
+    auto compareNotEqual( int lhs, T* const& rhs ) -> bool { return reinterpret_cast<void const*>( lhs ) != rhs; }
+    template<typename T>
+    auto compareNotEqual( long lhs, T* const& rhs ) -> bool { return reinterpret_cast<void const*>( lhs ) != rhs; }
+
+    template<typename LhsT>
+    class ExprLhs {
+        LhsT m_lhs;
+    public:
+        explicit ExprLhs( LhsT lhs ) : m_lhs( lhs ) {}
+
+        template<typename RhsT>
+        auto operator == ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const {
+            return { compareEqual( m_lhs, rhs ), m_lhs, "==", rhs };
+        }
+        auto operator == ( bool rhs ) -> BinaryExpr<LhsT, bool> const {
+            return { m_lhs == rhs, m_lhs, "==", rhs };
+        }
+
+        template<typename RhsT>
+        auto operator != ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const {
+            return { compareNotEqual( m_lhs, rhs ), m_lhs, "!=", rhs };
+        }
+        auto operator != ( bool rhs ) -> BinaryExpr<LhsT, bool> const {
+            return { m_lhs != rhs, m_lhs, "!=", rhs };
+        }
+
+        template<typename RhsT>
+        auto operator > ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const {
+            return { static_cast<bool>(m_lhs > rhs), m_lhs, ">", rhs };
+        }
+        template<typename RhsT>
+        auto operator < ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const {
+            return { static_cast<bool>(m_lhs < rhs), m_lhs, "<", rhs };
+        }
+        template<typename RhsT>
+        auto operator >= ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const {
+            return { static_cast<bool>(m_lhs >= rhs), m_lhs, ">=", rhs };
+        }
+        template<typename RhsT>
+        auto operator <= ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const {
+            return { static_cast<bool>(m_lhs <= rhs), m_lhs, "<=", rhs };
+        }
+
+        auto makeUnaryExpr() const -> UnaryExpr<LhsT> {
+            return UnaryExpr<LhsT>{ m_lhs };
+        }
+    };
+
+    void handleExpression( ITransientExpression const& expr );
+
+    template<typename T>
+    void handleExpression( ExprLhs<T> const& expr ) {
+        handleExpression( expr.makeUnaryExpr() );
+    }
+
+    struct Decomposer {
+        template<typename T>
+        auto operator <= ( T const& lhs ) -> ExprLhs<T const&> {
+            return ExprLhs<T const&>{ lhs };
+        }
+
+        auto operator <=( bool value ) -> ExprLhs<bool> {
+            return ExprLhs<bool>{ value };
+        }
+    };
+
+} // end namespace Catch
+
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
+// end catch_decomposer.h
+// start catch_interfaces_capture.h
+
+#include <string>
+
+namespace Catch {
+
+    class AssertionResult;
+    struct AssertionInfo;
+    struct SectionInfo;
+    struct SectionEndInfo;
+    struct MessageInfo;
+    struct Counts;
+    struct BenchmarkInfo;
+    struct BenchmarkStats;
+    struct AssertionReaction;
+    struct SourceLineInfo;
+
+    struct ITransientExpression;
+    struct IGeneratorTracker;
+
+    struct IResultCapture {
+
+        virtual ~IResultCapture();
+
+        virtual bool sectionStarted(    SectionInfo const& sectionInfo,
+                                        Counts& assertions ) = 0;
+        virtual void sectionEnded( SectionEndInfo const& endInfo ) = 0;
+        virtual void sectionEndedEarly( SectionEndInfo const& endInfo ) = 0;
+
+        virtual auto acquireGeneratorTracker( SourceLineInfo const& lineInfo ) -> IGeneratorTracker& = 0;
+
+        virtual void benchmarkStarting( BenchmarkInfo const& info ) = 0;
+        virtual void benchmarkEnded( BenchmarkStats const& stats ) = 0;
+
+        virtual void pushScopedMessage( MessageInfo const& message ) = 0;
+        virtual void popScopedMessage( MessageInfo const& message ) = 0;
+
+        virtual void handleFatalErrorCondition( StringRef message ) = 0;
+
+        virtual void handleExpr
+                (   AssertionInfo const& info,
+                    ITransientExpression const& expr,
+                    AssertionReaction& reaction ) = 0;
+        virtual void handleMessage
+                (   AssertionInfo const& info,
+                    ResultWas::OfType resultType,
+                    StringRef const& message,
+                    AssertionReaction& reaction ) = 0;
+        virtual void handleUnexpectedExceptionNotThrown
+                (   AssertionInfo const& info,
+                    AssertionReaction& reaction ) = 0;
+        virtual void handleUnexpectedInflightException
+                (   AssertionInfo const& info,
+                    std::string const& message,
+                    AssertionReaction& reaction ) = 0;
+        virtual void handleIncomplete
+                (   AssertionInfo const& info ) = 0;
+        virtual void handleNonExpr
+                (   AssertionInfo const &info,
+                    ResultWas::OfType resultType,
+                    AssertionReaction &reaction ) = 0;
+
+        virtual bool lastAssertionPassed() = 0;
+        virtual void assertionPassed() = 0;
+
+        // Deprecated, do not use:
+        virtual std::string getCurrentTestName() const = 0;
+        virtual const AssertionResult* getLastResult() const = 0;
+        virtual void exceptionEarlyReported() = 0;
+    };
+
+    IResultCapture& getResultCapture();
+}
+
+// end catch_interfaces_capture.h
+namespace Catch {
+
+    struct TestFailureException{};
+    struct AssertionResultData;
+    struct IResultCapture;
+    class RunContext;
+
+    class LazyExpression {
+        friend class AssertionHandler;
+        friend struct AssertionStats;
+        friend class RunContext;
+
+        ITransientExpression const* m_transientExpression = nullptr;
+        bool m_isNegated;
+    public:
+        LazyExpression( bool isNegated );
+        LazyExpression( LazyExpression const& other );
+        LazyExpression& operator = ( LazyExpression const& ) = delete;
+
+        explicit operator bool() const;
+
+        friend auto operator << ( std::ostream& os, LazyExpression const& lazyExpr ) -> std::ostream&;
+    };
+
+    struct AssertionReaction {
+        bool shouldDebugBreak = false;
+        bool shouldThrow = false;
+    };
+
+    class AssertionHandler {
+        AssertionInfo m_assertionInfo;
+        AssertionReaction m_reaction;
+        bool m_completed = false;
+        IResultCapture& m_resultCapture;
+
+    public:
+        AssertionHandler
+            (   StringRef const& macroName,
+                SourceLineInfo const& lineInfo,
+                StringRef capturedExpression,
+                ResultDisposition::Flags resultDisposition );
+        ~AssertionHandler() {
+            if ( !m_completed ) {
+                m_resultCapture.handleIncomplete( m_assertionInfo );
+            }
+        }
+
+        template<typename T>
+        void handleExpr( ExprLhs<T> const& expr ) {
+            handleExpr( expr.makeUnaryExpr() );
+        }
+        void handleExpr( ITransientExpression const& expr );
+
+        void handleMessage(ResultWas::OfType resultType, StringRef const& message);
+
+        void handleExceptionThrownAsExpected();
+        void handleUnexpectedExceptionNotThrown();
+        void handleExceptionNotThrownAsExpected();
+        void handleThrowingCallSkipped();
+        void handleUnexpectedInflightException();
+
+        void complete();
+        void setCompleted();
+
+        // query
+        auto allowThrows() const -> bool;
+    };
+
+    void handleExceptionMatchExpr( AssertionHandler& handler, std::string const& str, StringRef const& matcherString );
+
+} // namespace Catch
+
+// end catch_assertionhandler.h
+// start catch_message.h
+
+#include <string>
+#include <vector>
+
+namespace Catch {
+
+    struct MessageInfo {
+        MessageInfo(    StringRef const& _macroName,
+                        SourceLineInfo const& _lineInfo,
+                        ResultWas::OfType _type );
+
+        StringRef macroName;
+        std::string message;
+        SourceLineInfo lineInfo;
+        ResultWas::OfType type;
+        unsigned int sequence;
+
+        bool operator == ( MessageInfo const& other ) const;
+        bool operator < ( MessageInfo const& other ) const;
+    private:
+        static unsigned int globalCount;
+    };
+
+    struct MessageStream {
+
+        template<typename T>
+        MessageStream& operator << ( T const& value ) {
+            m_stream << value;
+            return *this;
+        }
+
+        ReusableStringStream m_stream;
+    };
+
+    struct MessageBuilder : MessageStream {
+        MessageBuilder( StringRef const& macroName,
+                        SourceLineInfo const& lineInfo,
+                        ResultWas::OfType type );
+
+        template<typename T>
+        MessageBuilder& operator << ( T const& value ) {
+            m_stream << value;
+            return *this;
+        }
+
+        MessageInfo m_info;
+    };
+
+    class ScopedMessage {
+    public:
+        explicit ScopedMessage( MessageBuilder const& builder );
+        ~ScopedMessage();
+
+        MessageInfo m_info;
+    };
+
+    class Capturer {
+        std::vector<MessageInfo> m_messages;
+        IResultCapture& m_resultCapture = getResultCapture();
+        size_t m_captured = 0;
+    public:
+        Capturer( StringRef macroName, SourceLineInfo const& lineInfo, ResultWas::OfType resultType, StringRef names );
+        ~Capturer();
+
+        void captureValue( size_t index, std::string const& value );
+
+        template<typename T>
+        void captureValues( size_t index, T const& value ) {
+            captureValue( index, Catch::Detail::stringify( value ) );
+        }
+
+        template<typename T, typename... Ts>
+        void captureValues( size_t index, T const& value, Ts const&... values ) {
+            captureValue( index, Catch::Detail::stringify(value) );
+            captureValues( index+1, values... );
+        }
+    };
+
+} // end namespace Catch
+
+// end catch_message.h
+#if !defined(CATCH_CONFIG_DISABLE)
+
+#if !defined(CATCH_CONFIG_DISABLE_STRINGIFICATION)
+  #define CATCH_INTERNAL_STRINGIFY(...) #__VA_ARGS__
+#else
+  #define CATCH_INTERNAL_STRINGIFY(...) "Disabled by CATCH_CONFIG_DISABLE_STRINGIFICATION"
+#endif
+
+#if defined(CATCH_CONFIG_FAST_COMPILE) || defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)
+
+///////////////////////////////////////////////////////////////////////////////
+// Another way to speed-up compilation is to omit local try-catch for REQUIRE*
+// macros.
+#define INTERNAL_CATCH_TRY
+#define INTERNAL_CATCH_CATCH( capturer )
+
+#else // CATCH_CONFIG_FAST_COMPILE
+
+#define INTERNAL_CATCH_TRY try
+#define INTERNAL_CATCH_CATCH( handler ) catch(...) { handler.handleUnexpectedInflightException(); }
+
+#endif
+
+#define INTERNAL_CATCH_REACT( handler ) handler.complete();
+
+///////////////////////////////////////////////////////////////////////////////
+#define INTERNAL_CATCH_TEST( macroName, resultDisposition, ... ) \
+    do { \
+        Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition ); \
+        INTERNAL_CATCH_TRY { \
+            CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \
+            catchAssertionHandler.handleExpr( Catch::Decomposer() <= __VA_ARGS__ ); \
+            CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \
+        } INTERNAL_CATCH_CATCH( catchAssertionHandler ) \
+        INTERNAL_CATCH_REACT( catchAssertionHandler ) \
+    } while( (void)0, false && static_cast<bool>( !!(__VA_ARGS__) ) ) // the expression here is never evaluated at runtime but it forces the compiler to give it a look
+    // The double negation silences MSVC's C4800 warning, the static_cast forces short-circuit evaluation if the type has overloaded &&.
+
+///////////////////////////////////////////////////////////////////////////////
+#define INTERNAL_CATCH_IF( macroName, resultDisposition, ... ) \
+    INTERNAL_CATCH_TEST( macroName, resultDisposition, __VA_ARGS__ ); \
+    if( Catch::getResultCapture().lastAssertionPassed() )
+
+///////////////////////////////////////////////////////////////////////////////
+#define INTERNAL_CATCH_ELSE( macroName, resultDisposition, ... ) \
+    INTERNAL_CATCH_TEST( macroName, resultDisposition, __VA_ARGS__ ); \
+    if( !Catch::getResultCapture().lastAssertionPassed() )
+
+///////////////////////////////////////////////////////////////////////////////
+#define INTERNAL_CATCH_NO_THROW( macroName, resultDisposition, ... ) \
+    do { \
+        Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition ); \
+        try { \
+            static_cast<void>(__VA_ARGS__); \
+            catchAssertionHandler.handleExceptionNotThrownAsExpected(); \
+        } \
+        catch( ... ) { \
+            catchAssertionHandler.handleUnexpectedInflightException(); \
+        } \
+        INTERNAL_CATCH_REACT( catchAssertionHandler ) \
+    } while( false )
+
+///////////////////////////////////////////////////////////////////////////////
+#define INTERNAL_CATCH_THROWS( macroName, resultDisposition, ... ) \
+    do { \
+        Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition); \
+        if( catchAssertionHandler.allowThrows() ) \
+            try { \
+                static_cast<void>(__VA_ARGS__); \
+                catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \
+            } \
+            catch( ... ) { \
+                catchAssertionHandler.handleExceptionThrownAsExpected(); \
+            } \
+        else \
+            catchAssertionHandler.handleThrowingCallSkipped(); \
+        INTERNAL_CATCH_REACT( catchAssertionHandler ) \
+    } while( false )
+
+///////////////////////////////////////////////////////////////////////////////
+#define INTERNAL_CATCH_THROWS_AS( macroName, exceptionType, resultDisposition, expr ) \
+    do { \
+        Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(expr) ", " CATCH_INTERNAL_STRINGIFY(exceptionType), resultDisposition ); \
+        if( catchAssertionHandler.allowThrows() ) \
+            try { \
+                static_cast<void>(expr); \
+                catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \
+            } \
+            catch( exceptionType const& ) { \
+                catchAssertionHandler.handleExceptionThrownAsExpected(); \
+            } \
+            catch( ... ) { \
+                catchAssertionHandler.handleUnexpectedInflightException(); \
+            } \
+        else \
+            catchAssertionHandler.handleThrowingCallSkipped(); \
+        INTERNAL_CATCH_REACT( catchAssertionHandler ) \
+    } while( false )
+
+///////////////////////////////////////////////////////////////////////////////
+#define INTERNAL_CATCH_MSG( macroName, messageType, resultDisposition, ... ) \
+    do { \
+        Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::StringRef(), resultDisposition ); \
+        catchAssertionHandler.handleMessage( messageType, ( Catch::MessageStream() << __VA_ARGS__ + ::Catch::StreamEndStop() ).m_stream.str() ); \
+        INTERNAL_CATCH_REACT( catchAssertionHandler ) \
+    } while( false )
+
+///////////////////////////////////////////////////////////////////////////////
+#define INTERNAL_CATCH_CAPTURE( varName, macroName, ... ) \
+    auto varName = Catch::Capturer( macroName, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info, #__VA_ARGS__ ); \
+    varName.captureValues( 0, __VA_ARGS__ )
+
+///////////////////////////////////////////////////////////////////////////////
+#define INTERNAL_CATCH_INFO( macroName, log ) \
+    Catch::ScopedMessage INTERNAL_CATCH_UNIQUE_NAME( scopedMessage )( Catch::MessageBuilder( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log );
+
+///////////////////////////////////////////////////////////////////////////////
+// Although this is matcher-based, it can be used with just a string
+#define INTERNAL_CATCH_THROWS_STR_MATCHES( macroName, resultDisposition, matcher, ... ) \
+    do { \
+        Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \
+        if( catchAssertionHandler.allowThrows() ) \
+            try { \
+                static_cast<void>(__VA_ARGS__); \
+                catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \
+            } \
+            catch( ... ) { \
+                Catch::handleExceptionMatchExpr( catchAssertionHandler, matcher, #matcher##_catch_sr ); \
+            } \
+        else \
+            catchAssertionHandler.handleThrowingCallSkipped(); \
+        INTERNAL_CATCH_REACT( catchAssertionHandler ) \
+    } while( false )
+
+#endif // CATCH_CONFIG_DISABLE
+
+// end catch_capture.hpp
+// start catch_section.h
+
+// start catch_section_info.h
+
+// start catch_totals.h
+
+#include <cstddef>
+
+namespace Catch {
+
+    struct Counts {
+        Counts operator - ( Counts const& other ) const;
+        Counts& operator += ( Counts const& other );
+
+        std::size_t total() const;
+        bool allPassed() const;
+        bool allOk() const;
+
+        std::size_t passed = 0;
+        std::size_t failed = 0;
+        std::size_t failedButOk = 0;
+    };
+
+    struct Totals {
+
+        Totals operator - ( Totals const& other ) const;
+        Totals& operator += ( Totals const& other );
+
+        Totals delta( Totals const& prevTotals ) const;
+
+        int error = 0;
+        Counts assertions;
+        Counts testCases;
+    };
+}
+
+// end catch_totals.h
+#include <string>
+
+namespace Catch {
+
+    struct SectionInfo {
+        SectionInfo
+            (   SourceLineInfo const& _lineInfo,
+                std::string const& _name );
+
+        // Deprecated
+        SectionInfo
+            (   SourceLineInfo const& _lineInfo,
+                std::string const& _name,
+                std::string const& ) : SectionInfo( _lineInfo, _name ) {}
+
+        std::string name;
+        std::string description; // !Deprecated: this will always be empty
+        SourceLineInfo lineInfo;
+    };
+
+    struct SectionEndInfo {
+        SectionInfo sectionInfo;
+        Counts prevAssertions;
+        double durationInSeconds;
+    };
+
+} // end namespace Catch
+
+// end catch_section_info.h
+// start catch_timer.h
+
+#include <cstdint>
+
+namespace Catch {
+
+    auto getCurrentNanosecondsSinceEpoch() -> uint64_t;
+    auto getEstimatedClockResolution() -> uint64_t;
+
+    class Timer {
+        uint64_t m_nanoseconds = 0;
+    public:
+        void start();
+        auto getElapsedNanoseconds() const -> uint64_t;
+        auto getElapsedMicroseconds() const -> uint64_t;
+        auto getElapsedMilliseconds() const -> unsigned int;
+        auto getElapsedSeconds() const -> double;
+    };
+
+} // namespace Catch
+
+// end catch_timer.h
+#include <string>
+
+namespace Catch {
+
+    class Section : NonCopyable {
+    public:
+        Section( SectionInfo const& info );
+        ~Section();
+
+        // This indicates whether the section should be executed or not
+        explicit operator bool() const;
+
+    private:
+        SectionInfo m_info;
+
+        std::string m_name;
+        Counts m_assertions;
+        bool m_sectionIncluded;
+        Timer m_timer;
+    };
+
+} // end namespace Catch
+
+#define INTERNAL_CATCH_SECTION( ... ) \
+    CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \
+    if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, __VA_ARGS__ ) ) \
+    CATCH_INTERNAL_UNSUPPRESS_UNUSED_WARNINGS
+
+#define INTERNAL_CATCH_DYNAMIC_SECTION( ... ) \
+    CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \
+    if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, (Catch::ReusableStringStream() << __VA_ARGS__).str() ) ) \
+    CATCH_INTERNAL_UNSUPPRESS_UNUSED_WARNINGS
+
+// end catch_section.h
+// start catch_benchmark.h
+
+#include <cstdint>
+#include <string>
+
+namespace Catch {
+
+    class BenchmarkLooper {
+
+        std::string m_name;
+        std::size_t m_count = 0;
+        std::size_t m_iterationsToRun = 1;
+        uint64_t m_resolution;
+        Timer m_timer;
+
+        static auto getResolution() -> uint64_t;
+    public:
+        // Keep most of this inline as it's on the code path that is being timed
+        BenchmarkLooper( StringRef name )
+        :   m_name( name ),
+            m_resolution( getResolution() )
+        {
+            reportStart();
+            m_timer.start();
+        }
+
+        explicit operator bool() {
+            if( m_count < m_iterationsToRun )
+                return true;
+            return needsMoreIterations();
+        }
+
+        void increment() {
+            ++m_count;
+        }
+
+        void reportStart();
+        auto needsMoreIterations() -> bool;
+    };
+
+} // end namespace Catch
+
+#define BENCHMARK( name ) \
+    for( Catch::BenchmarkLooper looper( name ); looper; looper.increment() )
+
+// end catch_benchmark.h
+// start catch_interfaces_exception.h
+
+// start catch_interfaces_registry_hub.h
+
+#include <string>
+#include <memory>
+
+namespace Catch {
+
+    class TestCase;
+    struct ITestCaseRegistry;
+    struct IExceptionTranslatorRegistry;
+    struct IExceptionTranslator;
+    struct IReporterRegistry;
+    struct IReporterFactory;
+    struct ITagAliasRegistry;
+    class StartupExceptionRegistry;
+
+    using IReporterFactoryPtr = std::shared_ptr<IReporterFactory>;
+
+    struct IRegistryHub {
+        virtual ~IRegistryHub();
+
+        virtual IReporterRegistry const& getReporterRegistry() const = 0;
+        virtual ITestCaseRegistry const& getTestCaseRegistry() const = 0;
+        virtual ITagAliasRegistry const& getTagAliasRegistry() const = 0;
+
+        virtual IExceptionTranslatorRegistry const& getExceptionTranslatorRegistry() const = 0;
+
+        virtual StartupExceptionRegistry const& getStartupExceptionRegistry() const = 0;
+    };
+
+    struct IMutableRegistryHub {
+        virtual ~IMutableRegistryHub();
+        virtual void registerReporter( std::string const& name, IReporterFactoryPtr const& factory ) = 0;
+        virtual void registerListener( IReporterFactoryPtr const& factory ) = 0;
+        virtual void registerTest( TestCase const& testInfo ) = 0;
+        virtual void registerTranslator( const IExceptionTranslator* translator ) = 0;
+        virtual void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) = 0;
+        virtual void registerStartupException() noexcept = 0;
+    };
+
+    IRegistryHub const& getRegistryHub();
+    IMutableRegistryHub& getMutableRegistryHub();
+    void cleanUp();
+    std::string translateActiveException();
+
+}
+
+// end catch_interfaces_registry_hub.h
+#if defined(CATCH_CONFIG_DISABLE)
+    #define INTERNAL_CATCH_TRANSLATE_EXCEPTION_NO_REG( translatorName, signature) \
+        static std::string translatorName( signature )
+#endif
+
+#include <exception>
+#include <string>
+#include <vector>
+
+namespace Catch {
+    using exceptionTranslateFunction = std::string(*)();
+
+    struct IExceptionTranslator;
+    using ExceptionTranslators = std::vector<std::unique_ptr<IExceptionTranslator const>>;
+
+    struct IExceptionTranslator {
+        virtual ~IExceptionTranslator();
+        virtual std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const = 0;
+    };
+
+    struct IExceptionTranslatorRegistry {
+        virtual ~IExceptionTranslatorRegistry();
+
+        virtual std::string translateActiveException() const = 0;
+    };
+
+    class ExceptionTranslatorRegistrar {
+        template<typename T>
+        class ExceptionTranslator : public IExceptionTranslator {
+        public:
+
+            ExceptionTranslator( std::string(*translateFunction)( T& ) )
+            : m_translateFunction( translateFunction )
+            {}
+
+            std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const override {
+                try {
+                    if( it == itEnd )
+                        std::rethrow_exception(std::current_exception());
+                    else
+                        return (*it)->translate( it+1, itEnd );
+                }
+                catch( T& ex ) {
+                    return m_translateFunction( ex );
+                }
+            }
+
+        protected:
+            std::string(*m_translateFunction)( T& );
+        };
+
+    public:
+        template<typename T>
+        ExceptionTranslatorRegistrar( std::string(*translateFunction)( T& ) ) {
+            getMutableRegistryHub().registerTranslator
+                ( new ExceptionTranslator<T>( translateFunction ) );
+        }
+    };
+}
+
+///////////////////////////////////////////////////////////////////////////////
+#define INTERNAL_CATCH_TRANSLATE_EXCEPTION2( translatorName, signature ) \
+    static std::string translatorName( signature ); \
+    CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \
+    namespace{ Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionRegistrar )( &translatorName ); } \
+    CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \
+    static std::string translatorName( signature )
+
+#define INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION2( INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ), signature )
+
+// end catch_interfaces_exception.h
+// start catch_approx.h
+
+#include <type_traits>
+
+namespace Catch {
+namespace Detail {
+
+    class Approx {
+    private:
+        bool equalityComparisonImpl(double other) const;
+        // Validates the new margin (margin >= 0)
+        // out-of-line to avoid including stdexcept in the header
+        void setMargin(double margin);
+        // Validates the new epsilon (0 < epsilon < 1)
+        // out-of-line to avoid including stdexcept in the header
+        void setEpsilon(double epsilon);
+
+    public:
+        explicit Approx ( double value );
+
+        static Approx custom();
+
+        Approx operator-() const;
+
+        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
+        Approx operator()( T const& value ) {
+            Approx approx( static_cast<double>(value) );
+            approx.m_epsilon = m_epsilon;
+            approx.m_margin = m_margin;
+            approx.m_scale = m_scale;
+            return approx;
+        }
+
+        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
+        explicit Approx( T const& value ): Approx(static_cast<double>(value))
+        {}
+
+        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
+        friend bool operator == ( const T& lhs, Approx const& rhs ) {
+            auto lhs_v = static_cast<double>(lhs);
+            return rhs.equalityComparisonImpl(lhs_v);
+        }
+
+        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
+        friend bool operator == ( Approx const& lhs, const T& rhs ) {
+            return operator==( rhs, lhs );
+        }
+
+        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
+        friend bool operator != ( T const& lhs, Approx const& rhs ) {
+            return !operator==( lhs, rhs );
+        }
+
+        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
+        friend bool operator != ( Approx const& lhs, T const& rhs ) {
+            return !operator==( rhs, lhs );
+        }
+
+        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
+        friend bool operator <= ( T const& lhs, Approx const& rhs ) {
+            return static_cast<double>(lhs) < rhs.m_value || lhs == rhs;
+        }
+
+        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
+        friend bool operator <= ( Approx const& lhs, T const& rhs ) {
+            return lhs.m_value < static_cast<double>(rhs) || lhs == rhs;
+        }
+
+        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
+        friend bool operator >= ( T const& lhs, Approx const& rhs ) {
+            return static_cast<double>(lhs) > rhs.m_value || lhs == rhs;
+        }
+
+        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
+        friend bool operator >= ( Approx const& lhs, T const& rhs ) {
+            return lhs.m_value > static_cast<double>(rhs) || lhs == rhs;
+        }
+
+        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
+        Approx& epsilon( T const& newEpsilon ) {
+            double epsilonAsDouble = static_cast<double>(newEpsilon);
+            setEpsilon(epsilonAsDouble);
+            return *this;
+        }
+
+        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
+        Approx& margin( T const& newMargin ) {
+            double marginAsDouble = static_cast<double>(newMargin);
+            setMargin(marginAsDouble);
+            return *this;
+        }
+
+        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
+        Approx& scale( T const& newScale ) {
+            m_scale = static_cast<double>(newScale);
+            return *this;
+        }
+
+        std::string toString() const;
+
+    private:
+        double m_epsilon;
+        double m_margin;
+        double m_scale;
+        double m_value;
+    };
+} // end namespace Detail
+
+namespace literals {
+    Detail::Approx operator "" _a(long double val);
+    Detail::Approx operator "" _a(unsigned long long val);
+} // end namespace literals
+
+template<>
+struct StringMaker<Catch::Detail::Approx> {
+    static std::string convert(Catch::Detail::Approx const& value);
+};
+
+} // end namespace Catch
+
+// end catch_approx.h
+// start catch_string_manip.h
+
+#include <string>
+#include <iosfwd>
+
+namespace Catch {
+
+    bool startsWith( std::string const& s, std::string const& prefix );
+    bool startsWith( std::string const& s, char prefix );
+    bool endsWith( std::string const& s, std::string const& suffix );
+    bool endsWith( std::string const& s, char suffix );
+    bool contains( std::string const& s, std::string const& infix );
+    void toLowerInPlace( std::string& s );
+    std::string toLower( std::string const& s );
+    std::string trim( std::string const& str );
+    bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis );
+
+    struct pluralise {
+        pluralise( std::size_t count, std::string const& label );
+
+        friend std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser );
+
+        std::size_t m_count;
+        std::string m_label;
+    };
+}
+
+// end catch_string_manip.h
+#ifndef CATCH_CONFIG_DISABLE_MATCHERS
+// start catch_capture_matchers.h
+
+// start catch_matchers.h
+
+#include <string>
+#include <vector>
+
+namespace Catch {
+namespace Matchers {
+    namespace Impl {
+
+        template<typename ArgT> struct MatchAllOf;
+        template<typename ArgT> struct MatchAnyOf;
+        template<typename ArgT> struct MatchNotOf;
+
+        class MatcherUntypedBase {
+        public:
+            MatcherUntypedBase() = default;
+            MatcherUntypedBase ( MatcherUntypedBase const& ) = default;
+            MatcherUntypedBase& operator = ( MatcherUntypedBase const& ) = delete;
+            std::string toString() const;
+
+        protected:
+            virtual ~MatcherUntypedBase();
+            virtual std::string describe() const = 0;
+            mutable std::string m_cachedToString;
+        };
+
+#ifdef __clang__
+#    pragma clang diagnostic push
+#    pragma clang diagnostic ignored "-Wnon-virtual-dtor"
+#endif
+
+        template<typename ObjectT>
+        struct MatcherMethod {
+            virtual bool match( ObjectT const& arg ) const = 0;
+        };
+
+#ifdef __clang__
+#    pragma clang diagnostic pop
+#endif
+
+        template<typename T>
+        struct MatcherBase : MatcherUntypedBase, MatcherMethod<T> {
+
+            MatchAllOf<T> operator && ( MatcherBase const& other ) const;
+            MatchAnyOf<T> operator || ( MatcherBase const& other ) const;
+            MatchNotOf<T> operator ! () const;
+        };
+
+        template<typename ArgT>
+        struct MatchAllOf : MatcherBase<ArgT> {
+            bool match( ArgT const& arg ) const override {
+                for( auto matcher : m_matchers ) {
+                    if (!matcher->match(arg))
+                        return false;
+                }
+                return true;
+            }
+            std::string describe() const override {
+                std::string description;
+                description.reserve( 4 + m_matchers.size()*32 );
+                description += "( ";
+                bool first = true;
+                for( auto matcher : m_matchers ) {
+                    if( first )
+                        first = false;
+                    else
+                        description += " and ";
+                    description += matcher->toString();
+                }
+                description += " )";
+                return description;
+            }
+
+            MatchAllOf<ArgT>& operator && ( MatcherBase<ArgT> const& other ) {
+                m_matchers.push_back( &other );
+                return *this;
+            }
+
+            std::vector<MatcherBase<ArgT> const*> m_matchers;
+        };
+        template<typename ArgT>
+        struct MatchAnyOf : MatcherBase<ArgT> {
+
+            bool match( ArgT const& arg ) const override {
+                for( auto matcher : m_matchers ) {
+                    if (matcher->match(arg))
+                        return true;
+                }
+                return false;
+            }
+            std::string describe() const override {
+                std::string description;
+                description.reserve( 4 + m_matchers.size()*32 );
+                description += "( ";
+                bool first = true;
+                for( auto matcher : m_matchers ) {
+                    if( first )
+                        first = false;
+                    else
+                        description += " or ";
+                    description += matcher->toString();
+                }
+                description += " )";
+                return description;
+            }
+
+            MatchAnyOf<ArgT>& operator || ( MatcherBase<ArgT> const& other ) {
+                m_matchers.push_back( &other );
+                return *this;
+            }
+
+            std::vector<MatcherBase<ArgT> const*> m_matchers;
+        };
+
+        template<typename ArgT>
+        struct MatchNotOf : MatcherBase<ArgT> {
+
+            MatchNotOf( MatcherBase<ArgT> const& underlyingMatcher ) : m_underlyingMatcher( underlyingMatcher ) {}
+
+            bool match( ArgT const& arg ) const override {
+                return !m_underlyingMatcher.match( arg );
+            }
+
+            std::string describe() const override {
+                return "not " + m_underlyingMatcher.toString();
+            }
+            MatcherBase<ArgT> const& m_underlyingMatcher;
+        };
+
+        template<typename T>
+        MatchAllOf<T> MatcherBase<T>::operator && ( MatcherBase const& other ) const {
+            return MatchAllOf<T>() && *this && other;
+        }
+        template<typename T>
+        MatchAnyOf<T> MatcherBase<T>::operator || ( MatcherBase const& other ) const {
+            return MatchAnyOf<T>() || *this || other;
+        }
+        template<typename T>
+        MatchNotOf<T> MatcherBase<T>::operator ! () const {
+            return MatchNotOf<T>( *this );
+        }
+
+    } // namespace Impl
+
+} // namespace Matchers
+
+using namespace Matchers;
+using Matchers::Impl::MatcherBase;
+
+} // namespace Catch
+
+// end catch_matchers.h
+// start catch_matchers_floating.h
+
+#include <type_traits>
+#include <cmath>
+
+namespace Catch {
+namespace Matchers {
+
+    namespace Floating {
+
+        enum class FloatingPointKind : uint8_t;
+
+        struct WithinAbsMatcher : MatcherBase<double> {
+            WithinAbsMatcher(double target, double margin);
+            bool match(double const& matchee) const override;
+            std::string describe() const override;
+        private:
+            double m_target;
+            double m_margin;
+        };
+
+        struct WithinUlpsMatcher : MatcherBase<double> {
+            WithinUlpsMatcher(double target, int ulps, FloatingPointKind baseType);
+            bool match(double const& matchee) const override;
+            std::string describe() const override;
+        private:
+            double m_target;
+            int m_ulps;
+            FloatingPointKind m_type;
+        };
+
+    } // namespace Floating
+
+    // The following functions create the actual matcher objects.
+    // This allows the types to be inferred
+    Floating::WithinUlpsMatcher WithinULP(double target, int maxUlpDiff);
+    Floating::WithinUlpsMatcher WithinULP(float target, int maxUlpDiff);
+    Floating::WithinAbsMatcher WithinAbs(double target, double margin);
+
+} // namespace Matchers
+} // namespace Catch
+
+// end catch_matchers_floating.h
+// start catch_matchers_generic.hpp
+
+#include <functional>
+#include <string>
+
+namespace Catch {
+namespace Matchers {
+namespace Generic {
+
+namespace Detail {
+    std::string finalizeDescription(const std::string& desc);
+}
+
+template <typename T>
+class PredicateMatcher : public MatcherBase<T> {
+    std::function<bool(T const&)> m_predicate;
+    std::string m_description;
+public:
+
+    PredicateMatcher(std::function<bool(T const&)> const& elem, std::string const& descr)
+        :m_predicate(std::move(elem)),
+        m_description(Detail::finalizeDescription(descr))
+    {}
+
+    bool match( T const& item ) const override {
+        return m_predicate(item);
+    }
+
+    std::string describe() const override {
+        return m_description;
+    }
+};
+
+} // namespace Generic
+
+    // The following functions create the actual matcher objects.
+    // The user has to explicitly specify type to the function, because
+    // infering std::function<bool(T const&)> is hard (but possible) and
+    // requires a lot of TMP.
+    template<typename T>
+    Generic::PredicateMatcher<T> Predicate(std::function<bool(T const&)> const& predicate, std::string const& description = "") {
+        return Generic::PredicateMatcher<T>(predicate, description);
+    }
+
+} // namespace Matchers
+} // namespace Catch
+
+// end catch_matchers_generic.hpp
+// start catch_matchers_string.h
+
+#include <string>
+
+namespace Catch {
+namespace Matchers {
+
+    namespace StdString {
+
+        struct CasedString
+        {
+            CasedString( std::string const& str, CaseSensitive::Choice caseSensitivity );
+            std::string adjustString( std::string const& str ) const;
+            std::string caseSensitivitySuffix() const;
+
+            CaseSensitive::Choice m_caseSensitivity;
+            std::string m_str;
+        };
+
+        struct StringMatcherBase : MatcherBase<std::string> {
+            StringMatcherBase( std::string const& operation, CasedString const& comparator );
+            std::string describe() const override;
+
+            CasedString m_comparator;
+            std::string m_operation;
+        };
+
+        struct EqualsMatcher : StringMatcherBase {
+            EqualsMatcher( CasedString const& comparator );
+            bool match( std::string const& source ) const override;
+        };
+        struct ContainsMatcher : StringMatcherBase {
+            ContainsMatcher( CasedString const& comparator );
+            bool match( std::string const& source ) const override;
+        };
+        struct StartsWithMatcher : StringMatcherBase {
+            StartsWithMatcher( CasedString const& comparator );
+            bool match( std::string const& source ) const override;
+        };
+        struct EndsWithMatcher : StringMatcherBase {
+            EndsWithMatcher( CasedString const& comparator );
+            bool match( std::string const& source ) const override;
+        };
+
+        struct RegexMatcher : MatcherBase<std::string> {
+            RegexMatcher( std::string regex, CaseSensitive::Choice caseSensitivity );
+            bool match( std::string const& matchee ) const override;
+            std::string describe() const override;
+
+        private:
+            std::string m_regex;
+            CaseSensitive::Choice m_caseSensitivity;
+        };
+
+    } // namespace StdString
+
+    // The following functions create the actual matcher objects.
+    // This allows the types to be inferred
+
+    StdString::EqualsMatcher Equals( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes );
+    StdString::ContainsMatcher Contains( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes );
+    StdString::EndsWithMatcher EndsWith( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes );
+    StdString::StartsWithMatcher StartsWith( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes );
+    StdString::RegexMatcher Matches( std::string const& regex, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes );
+
+} // namespace Matchers
+} // namespace Catch
+
+// end catch_matchers_string.h
+// start catch_matchers_vector.h
+
+#include <algorithm>
+
+namespace Catch {
+namespace Matchers {
+
+    namespace Vector {
+        namespace Detail {
+            template <typename InputIterator, typename T>
+            size_t count(InputIterator first, InputIterator last, T const& item) {
+                size_t cnt = 0;
+                for (; first != last; ++first) {
+                    if (*first == item) {
+                        ++cnt;
+                    }
+                }
+                return cnt;
+            }
+            template <typename InputIterator, typename T>
+            bool contains(InputIterator first, InputIterator last, T const& item) {
+                for (; first != last; ++first) {
+                    if (*first == item) {
+                        return true;
+                    }
+                }
+                return false;
+            }
+        }
+
+        template<typename T>
+        struct ContainsElementMatcher : MatcherBase<std::vector<T>> {
+
+            ContainsElementMatcher(T const &comparator) : m_comparator( comparator) {}
+
+            bool match(std::vector<T> const &v) const override {
+                for (auto const& el : v) {
+                    if (el == m_comparator) {
+                        return true;
+                    }
+                }
+                return false;
+            }
+
+            std::string describe() const override {
+                return "Contains: " + ::Catch::Detail::stringify( m_comparator );
+            }
+
+            T const& m_comparator;
+        };
+
+        template<typename T>
+        struct ContainsMatcher : MatcherBase<std::vector<T>> {
+
+            ContainsMatcher(std::vector<T> const &comparator) : m_comparator( comparator ) {}
+
+            bool match(std::vector<T> const &v) const override {
+                // !TBD: see note in EqualsMatcher
+                if (m_comparator.size() > v.size())
+                    return false;
+                for (auto const& comparator : m_comparator) {
+                    auto present = false;
+                    for (const auto& el : v) {
+                        if (el == comparator) {
+                            present = true;
+                            break;
+                        }
+                    }
+                    if (!present) {
+                        return false;
+                    }
+                }
+                return true;
+            }
+            std::string describe() const override {
+                return "Contains: " + ::Catch::Detail::stringify( m_comparator );
+            }
+
+            std::vector<T> const& m_comparator;
+        };
+
+        template<typename T>
+        struct EqualsMatcher : MatcherBase<std::vector<T>> {
+
+            EqualsMatcher(std::vector<T> const &comparator) : m_comparator( comparator ) {}
+
+            bool match(std::vector<T> const &v) const override {
+                // !TBD: This currently works if all elements can be compared using !=
+                // - a more general approach would be via a compare template that defaults
+                // to using !=. but could be specialised for, e.g. std::vector<T> etc
+                // - then just call that directly
+                if (m_comparator.size() != v.size())
+                    return false;
+                for (std::size_t i = 0; i < v.size(); ++i)
+                    if (m_comparator[i] != v[i])
+                        return false;
+                return true;
+            }
+            std::string describe() const override {
+                return "Equals: " + ::Catch::Detail::stringify( m_comparator );
+            }
+            std::vector<T> const& m_comparator;
+        };
+
+        template<typename T>
+        struct UnorderedEqualsMatcher : MatcherBase<std::vector<T>> {
+            UnorderedEqualsMatcher(std::vector<T> const& target) : m_target(target) {}
+            bool match(std::vector<T> const& vec) const override {
+                // Note: This is a reimplementation of std::is_permutation,
+                //       because I don't want to include <algorithm> inside the common path
+                if (m_target.size() != vec.size()) {
+                    return false;
+                }
+                auto lfirst = m_target.begin(), llast = m_target.end();
+                auto rfirst = vec.begin(), rlast = vec.end();
+                // Cut common prefix to optimize checking of permuted parts
+                while (lfirst != llast && *lfirst == *rfirst) {
+                    ++lfirst; ++rfirst;
+                }
+                if (lfirst == llast) {
+                    return true;
+                }
+
+                for (auto mid = lfirst; mid != llast; ++mid) {
+                    // Skip already counted items
+                    if (Detail::contains(lfirst, mid, *mid)) {
+                        continue;
+                    }
+                    size_t num_vec = Detail::count(rfirst, rlast, *mid);
+                    if (num_vec == 0 || Detail::count(lfirst, llast, *mid) != num_vec) {
+                        return false;
+                    }
+                }
+
+                return true;
+            }
+
+            std::string describe() const override {
+                return "UnorderedEquals: " + ::Catch::Detail::stringify(m_target);
+            }
+        private:
+            std::vector<T> const& m_target;
+        };
+
+    } // namespace Vector
+
+    // The following functions create the actual matcher objects.
+    // This allows the types to be inferred
+
+    template<typename T>
+    Vector::ContainsMatcher<T> Contains( std::vector<T> const& comparator ) {
+        return Vector::ContainsMatcher<T>( comparator );
+    }
+
+    template<typename T>
+    Vector::ContainsElementMatcher<T> VectorContains( T const& comparator ) {
+        return Vector::ContainsElementMatcher<T>( comparator );
+    }
+
+    template<typename T>
+    Vector::EqualsMatcher<T> Equals( std::vector<T> const& comparator ) {
+        return Vector::EqualsMatcher<T>( comparator );
+    }
+
+    template<typename T>
+    Vector::UnorderedEqualsMatcher<T> UnorderedEquals(std::vector<T> const& target) {
+        return Vector::UnorderedEqualsMatcher<T>(target);
+    }
+
+} // namespace Matchers
+} // namespace Catch
+
+// end catch_matchers_vector.h
+namespace Catch {
+
+    template<typename ArgT, typename MatcherT>
+    class MatchExpr : public ITransientExpression {
+        ArgT const& m_arg;
+        MatcherT m_matcher;
+        StringRef m_matcherString;
+    public:
+        MatchExpr( ArgT const& arg, MatcherT const& matcher, StringRef const& matcherString )
+        :   ITransientExpression{ true, matcher.match( arg ) },
+            m_arg( arg ),
+            m_matcher( matcher ),
+            m_matcherString( matcherString )
+        {}
+
+        void streamReconstructedExpression( std::ostream &os ) const override {
+            auto matcherAsString = m_matcher.toString();
+            os << Catch::Detail::stringify( m_arg ) << ' ';
+            if( matcherAsString == Detail::unprintableString )
+                os << m_matcherString;
+            else
+                os << matcherAsString;
+        }
+    };
+
+    using StringMatcher = Matchers::Impl::MatcherBase<std::string>;
+
+    void handleExceptionMatchExpr( AssertionHandler& handler, StringMatcher const& matcher, StringRef const& matcherString  );
+
+    template<typename ArgT, typename MatcherT>
+    auto makeMatchExpr( ArgT const& arg, MatcherT const& matcher, StringRef const& matcherString  ) -> MatchExpr<ArgT, MatcherT> {
+        return MatchExpr<ArgT, MatcherT>( arg, matcher, matcherString );
+    }
+
+} // namespace Catch
+
+///////////////////////////////////////////////////////////////////////////////
+#define INTERNAL_CHECK_THAT( macroName, matcher, resultDisposition, arg ) \
+    do { \
+        Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(arg) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \
+        INTERNAL_CATCH_TRY { \
+            catchAssertionHandler.handleExpr( Catch::makeMatchExpr( arg, matcher, #matcher##_catch_sr ) ); \
+        } INTERNAL_CATCH_CATCH( catchAssertionHandler ) \
+        INTERNAL_CATCH_REACT( catchAssertionHandler ) \
+    } while( false )
+
+///////////////////////////////////////////////////////////////////////////////
+#define INTERNAL_CATCH_THROWS_MATCHES( macroName, exceptionType, resultDisposition, matcher, ... ) \
+    do { \
+        Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__) ", " CATCH_INTERNAL_STRINGIFY(exceptionType) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \
+        if( catchAssertionHandler.allowThrows() ) \
+            try { \
+                static_cast<void>(__VA_ARGS__ ); \
+                catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \
+            } \
+            catch( exceptionType const& ex ) { \
+                catchAssertionHandler.handleExpr( Catch::makeMatchExpr( ex, matcher, #matcher##_catch_sr ) ); \
+            } \
+            catch( ... ) { \
+                catchAssertionHandler.handleUnexpectedInflightException(); \
+            } \
+        else \
+            catchAssertionHandler.handleThrowingCallSkipped(); \
+        INTERNAL_CATCH_REACT( catchAssertionHandler ) \
+    } while( false )
+
+// end catch_capture_matchers.h
+#endif
+// start catch_generators.hpp
+
+// start catch_interfaces_generatortracker.h
+
+
+#include <memory>
+
+namespace Catch {
+
+    namespace Generators {
+        class GeneratorBase {
+        protected:
+            size_t m_size = 0;
+
+        public:
+            GeneratorBase( size_t size ) : m_size( size ) {}
+            virtual ~GeneratorBase();
+            auto size() const -> size_t { return m_size; }
+        };
+        using GeneratorBasePtr = std::unique_ptr<GeneratorBase>;
+
+    } // namespace Generators
+
+    struct IGeneratorTracker {
+        virtual ~IGeneratorTracker();
+        virtual auto hasGenerator() const -> bool = 0;
+        virtual auto getGenerator() const -> Generators::GeneratorBasePtr const& = 0;
+        virtual void setGenerator( Generators::GeneratorBasePtr&& generator ) = 0;
+        virtual auto getIndex() const -> std::size_t = 0;
+    };
+
+} // namespace Catch
+
+// end catch_interfaces_generatortracker.h
+// start catch_enforce.h
+
+#include <stdexcept>
+
+namespace Catch {
+#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)
+    template <typename Ex>
+    [[noreturn]]
+    void throw_exception(Ex const& e) {
+        throw e;
+    }
+#else // ^^ Exceptions are enabled //  Exceptions are disabled vv
+    [[noreturn]]
+    void throw_exception(std::exception const& e);
+#endif
+} // namespace Catch;
+
+#define CATCH_PREPARE_EXCEPTION( type, msg ) \
+    type( ( Catch::ReusableStringStream() << msg ).str() )
+#define CATCH_INTERNAL_ERROR( msg ) \
+    Catch::throw_exception(CATCH_PREPARE_EXCEPTION( std::logic_error, CATCH_INTERNAL_LINEINFO << ": Internal Catch error: " << msg))
+#define CATCH_ERROR( msg ) \
+    Catch::throw_exception(CATCH_PREPARE_EXCEPTION( std::domain_error, msg ))
+#define CATCH_RUNTIME_ERROR( msg ) \
+    Catch::throw_exception(CATCH_PREPARE_EXCEPTION( std::runtime_error, msg ))
+#define CATCH_ENFORCE( condition, msg ) \
+    do{ if( !(condition) ) CATCH_ERROR( msg ); } while(false)
+
+// end catch_enforce.h
+#include <memory>
+#include <vector>
+#include <cassert>
+
+#include <utility>
+
+namespace Catch {
+namespace Generators {
+
+    // !TBD move this into its own location?
+    namespace pf{
+        template<typename T, typename... Args>
+        std::unique_ptr<T> make_unique( Args&&... args ) {
+            return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
+        }
+    }
+
+    template<typename T>
+    struct IGenerator {
+        virtual ~IGenerator() {}
+        virtual auto get( size_t index ) const -> T = 0;
+    };
+
+    template<typename T>
+    class SingleValueGenerator : public IGenerator<T> {
+        T m_value;
+    public:
+        SingleValueGenerator( T const& value ) : m_value( value ) {}
+
+        auto get( size_t ) const -> T override {
+            return m_value;
+        }
+    };
+
+    template<typename T>
+    class FixedValuesGenerator : public IGenerator<T> {
+        std::vector<T> m_values;
+
+    public:
+        FixedValuesGenerator( std::initializer_list<T> values ) : m_values( values ) {}
+
+        auto get( size_t index ) const -> T override {
+            return m_values[index];
+        }
+    };
+
+    template<typename T>
+    class RangeGenerator : public IGenerator<T> {
+        T const m_first;
+        T const m_last;
+
+    public:
+        RangeGenerator( T const& first, T const& last ) : m_first( first ), m_last( last ) {
+            assert( m_last > m_first );
+        }
+
+        auto get( size_t index ) const -> T override {
+            // ToDo:: introduce a safe cast to catch potential overflows
+            return static_cast<T>(m_first+index);
+        }
+    };
+
+    template<typename T>
+    struct NullGenerator : IGenerator<T> {
+        auto get( size_t ) const -> T override {
+            CATCH_INTERNAL_ERROR("A Null Generator is always empty");
+        }
+    };
+
+    template<typename T>
+    class Generator {
+        std::unique_ptr<IGenerator<T>> m_generator;
+        size_t m_size;
+
+    public:
+        Generator( size_t size, std::unique_ptr<IGenerator<T>> generator )
+        :   m_generator( std::move( generator ) ),
+            m_size( size )
+        {}
+
+        auto size() const -> size_t { return m_size; }
+        auto operator[]( size_t index ) const -> T {
+            assert( index < m_size );
+            return m_generator->get( index );
+        }
+    };
+
+    std::vector<size_t> randomiseIndices( size_t selectionSize, size_t sourceSize );
+
+    template<typename T>
+    class GeneratorRandomiser : public IGenerator<T> {
+        Generator<T> m_baseGenerator;
+
+        std::vector<size_t> m_indices;
+    public:
+        GeneratorRandomiser( Generator<T>&& baseGenerator, size_t numberOfItems )
+        :   m_baseGenerator( std::move( baseGenerator ) ),
+            m_indices( randomiseIndices( numberOfItems, m_baseGenerator.size() ) )
+        {}
+
+        auto get( size_t index ) const -> T override {
+            return m_baseGenerator[m_indices[index]];
+        }
+    };
+
+    template<typename T>
+    struct RequiresASpecialisationFor;
+
+    template<typename T>
+    auto all() -> Generator<T> { return RequiresASpecialisationFor<T>(); }
+
+    template<>
+    auto all<int>() -> Generator<int>;
+
+    template<typename T>
+    auto range( T const& first, T const& last ) -> Generator<T> {
+        return Generator<T>( (last-first), pf::make_unique<RangeGenerator<T>>( first, last ) );
+    }
+
+    template<typename T>
+    auto random( T const& first, T const& last ) -> Generator<T> {
+        auto gen = range( first, last );
+        auto size = gen.size();
+
+        return Generator<T>( size, pf::make_unique<GeneratorRandomiser<T>>( std::move( gen ), size ) );
+    }
+    template<typename T>
+    auto random( size_t size ) -> Generator<T> {
+        return Generator<T>( size, pf::make_unique<GeneratorRandomiser<T>>( all<T>(), size ) );
+    }
+
+    template<typename T>
+    auto values( std::initializer_list<T> values ) -> Generator<T> {
+        return Generator<T>( values.size(), pf::make_unique<FixedValuesGenerator<T>>( values ) );
+    }
+    template<typename T>
+    auto value( T const& val ) -> Generator<T> {
+        return Generator<T>( 1, pf::make_unique<SingleValueGenerator<T>>( val ) );
+    }
+
+    template<typename T>
+    auto as() -> Generator<T> {
+        return Generator<T>( 0, pf::make_unique<NullGenerator<T>>() );
+    }
+
+    template<typename... Ts>
+    auto table( std::initializer_list<std::tuple<Ts...>>&& tuples ) -> Generator<std::tuple<Ts...>> {
+        return values<std::tuple<Ts...>>( std::forward<std::initializer_list<std::tuple<Ts...>>>( tuples ) );
+    }
+
+    template<typename T>
+    struct Generators : GeneratorBase {
+        std::vector<Generator<T>> m_generators;
+
+        using type = T;
+
+        Generators() : GeneratorBase( 0 ) {}
+
+        void populate( T&& val ) {
+            m_size += 1;
+            m_generators.emplace_back( value( std::move( val ) ) );
+        }
+        template<typename U>
+        void populate( U&& val ) {
+            populate( T( std::move( val ) ) );
+        }
+        void populate( Generator<T>&& generator ) {
+            m_size += generator.size();
+            m_generators.emplace_back( std::move( generator ) );
+        }
+
+        template<typename U, typename... Gs>
+        void populate( U&& valueOrGenerator, Gs... moreGenerators ) {
+            populate( std::forward<U>( valueOrGenerator ) );
+            populate( std::forward<Gs>( moreGenerators )... );
+        }
+
+        auto operator[]( size_t index ) const -> T {
+            size_t sizes = 0;
+            for( auto const& gen : m_generators ) {
+                auto localIndex = index-sizes;
+                sizes += gen.size();
+                if( index < sizes )
+                    return gen[localIndex];
+            }
+            CATCH_INTERNAL_ERROR("Index '" << index << "' is out of range (" << sizes << ')');
+        }
+    };
+
+    template<typename T, typename... Gs>
+    auto makeGenerators( Generator<T>&& generator, Gs... moreGenerators ) -> Generators<T> {
+        Generators<T> generators;
+        generators.m_generators.reserve( 1+sizeof...(Gs) );
+        generators.populate( std::move( generator ), std::forward<Gs>( moreGenerators )... );
+        return generators;
+    }
+    template<typename T>
+    auto makeGenerators( Generator<T>&& generator ) -> Generators<T> {
+        Generators<T> generators;
+        generators.populate( std::move( generator ) );
+        return generators;
+    }
+    template<typename T, typename... Gs>
+    auto makeGenerators( T&& val, Gs... moreGenerators ) -> Generators<T> {
+        return makeGenerators( value( std::forward<T>( val ) ), std::forward<Gs>( moreGenerators )... );
+    }
+    template<typename T, typename U, typename... Gs>
+    auto makeGenerators( U&& val, Gs... moreGenerators ) -> Generators<T> {
+        return makeGenerators( value( T( std::forward<U>( val ) ) ), std::forward<Gs>( moreGenerators )... );
+    }
+
+    auto acquireGeneratorTracker( SourceLineInfo const& lineInfo ) -> IGeneratorTracker&;
+
+    template<typename L>
+    // Note: The type after -> is weird, because VS2015 cannot parse
+    //       the expression used in the typedef inside, when it is in
+    //       return type. Yeah, ¯\_(ツ)_/¯
+    auto generate( SourceLineInfo const& lineInfo, L const& generatorExpression ) -> decltype(std::declval<decltype(generatorExpression())>()[0]) {
+        using UnderlyingType = typename decltype(generatorExpression())::type;
+
+        IGeneratorTracker& tracker = acquireGeneratorTracker( lineInfo );
+        if( !tracker.hasGenerator() )
+            tracker.setGenerator( pf::make_unique<Generators<UnderlyingType>>( generatorExpression() ) );
+
+        auto const& generator = static_cast<Generators<UnderlyingType> const&>( *tracker.getGenerator() );
+        return generator[tracker.getIndex()];
+    }
+
+} // namespace Generators
+} // namespace Catch
+
+#define GENERATE( ... ) \
+    Catch::Generators::generate( CATCH_INTERNAL_LINEINFO, []{ using namespace Catch::Generators; return makeGenerators( __VA_ARGS__ ); } )
+
+// end catch_generators.hpp
+
+// These files are included here so the single_include script doesn't put them
+// in the conditionally compiled sections
+// start catch_test_case_info.h
+
+#include <string>
+#include <vector>
+#include <memory>
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wpadded"
+#endif
+
+namespace Catch {
+
+    struct ITestInvoker;
+
+    struct TestCaseInfo {
+        enum SpecialProperties{
+            None = 0,
+            IsHidden = 1 << 1,
+            ShouldFail = 1 << 2,
+            MayFail = 1 << 3,
+            Throws = 1 << 4,
+            NonPortable = 1 << 5,
+            Benchmark = 1 << 6
+        };
+
+        TestCaseInfo(   std::string const& _name,
+                        std::string const& _className,
+                        std::string const& _description,
+                        std::vector<std::string> const& _tags,
+                        SourceLineInfo const& _lineInfo );
+
+        friend void setTags( TestCaseInfo& testCaseInfo, std::vector<std::string> tags );
+
+        bool isHidden() const;
+        bool throws() const;
+        bool okToFail() const;
+        bool expectedToFail() const;
+
+        std::string tagsAsString() const;
+
+        std::string name;
+        std::string className;
+        std::string description;
+        std::vector<std::string> tags;
+        std::vector<std::string> lcaseTags;
+        SourceLineInfo lineInfo;
+        SpecialProperties properties;
+    };
+
+    class TestCase : public TestCaseInfo {
+    public:
+
+        TestCase( ITestInvoker* testCase, TestCaseInfo&& info );
+
+        TestCase withName( std::string const& _newName ) const;
+
+        void invoke() const;
+
+        TestCaseInfo const& getTestCaseInfo() const;
+
+        bool operator == ( TestCase const& other ) const;
+        bool operator < ( TestCase const& other ) const;
+
+    private:
+        std::shared_ptr<ITestInvoker> test;
+    };
+
+    TestCase makeTestCase(  ITestInvoker* testCase,
+                            std::string const& className,
+                            NameAndTags const& nameAndTags,
+                            SourceLineInfo const& lineInfo );
+}
+
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+// end catch_test_case_info.h
+// start catch_interfaces_runner.h
+
+namespace Catch {
+
+    struct IRunner {
+        virtual ~IRunner();
+        virtual bool aborting() const = 0;
+    };
+}
+
+// end catch_interfaces_runner.h
+
+#ifdef __OBJC__
+// start catch_objc.hpp
+
+#import <objc/runtime.h>
+
+#include <string>
+
+// NB. Any general catch headers included here must be included
+// in catch.hpp first to make sure they are included by the single
+// header for non obj-usage
+
+///////////////////////////////////////////////////////////////////////////////
+// This protocol is really only here for (self) documenting purposes, since
+// all its methods are optional.
+@protocol OcFixture
+
+@optional
+
+-(void) setUp;
+-(void) tearDown;
+
+@end
+
+namespace Catch {
+
+    class OcMethod : public ITestInvoker {
+
+    public:
+        OcMethod( Class cls, SEL sel ) : m_cls( cls ), m_sel( sel ) {}
+
+        virtual void invoke() const {
+            id obj = [[m_cls alloc] init];
+
+            performOptionalSelector( obj, @selector(setUp)  );
+            performOptionalSelector( obj, m_sel );
+            performOptionalSelector( obj, @selector(tearDown)  );
+
+            arcSafeRelease( obj );
+        }
+    private:
+        virtual ~OcMethod() {}
+
+        Class m_cls;
+        SEL m_sel;
+    };
+
+    namespace Detail{
+
+        inline std::string getAnnotation(   Class cls,
+                                            std::string const& annotationName,
+                                            std::string const& testCaseName ) {
+            NSString* selStr = [[NSString alloc] initWithFormat:@"Catch_%s_%s", annotationName.c_str(), testCaseName.c_str()];
+            SEL sel = NSSelectorFromString( selStr );
+            arcSafeRelease( selStr );
+            id value = performOptionalSelector( cls, sel );
+            if( value )
+                return [(NSString*)value UTF8String];
+            return "";
+        }
+    }
+
+    inline std::size_t registerTestMethods() {
+        std::size_t noTestMethods = 0;
+        int noClasses = objc_getClassList( nullptr, 0 );
+
+        Class* classes = (CATCH_UNSAFE_UNRETAINED Class *)malloc( sizeof(Class) * noClasses);
+        objc_getClassList( classes, noClasses );
+
+        for( int c = 0; c < noClasses; c++ ) {
+            Class cls = classes[c];
+            {
+                u_int count;
+                Method* methods = class_copyMethodList( cls, &count );
+                for( u_int m = 0; m < count ; m++ ) {
+                    SEL selector = method_getName(methods[m]);
+                    std::string methodName = sel_getName(selector);
+                    if( startsWith( methodName, "Catch_TestCase_" ) ) {
+                        std::string testCaseName = methodName.substr( 15 );
+                        std::string name = Detail::getAnnotation( cls, "Name", testCaseName );
+                        std::string desc = Detail::getAnnotation( cls, "Description", testCaseName );
+                        const char* className = class_getName( cls );
+
+                        getMutableRegistryHub().registerTest( makeTestCase( new OcMethod( cls, selector ), className, NameAndTags( name.c_str(), desc.c_str() ), SourceLineInfo("",0) ) );
+                        noTestMethods++;
+                    }
+                }
+                free(methods);
+            }
+        }
+        return noTestMethods;
+    }
+
+#if !defined(CATCH_CONFIG_DISABLE_MATCHERS)
+
+    namespace Matchers {
+        namespace Impl {
+        namespace NSStringMatchers {
+
+            struct StringHolder : MatcherBase<NSString*>{
+                StringHolder( NSString* substr ) : m_substr( [substr copy] ){}
+                StringHolder( StringHolder const& other ) : m_substr( [other.m_substr copy] ){}
+                StringHolder() {
+                    arcSafeRelease( m_substr );
+                }
+
+                bool match( NSString* arg ) const override {
+                    return false;
+                }
+
+                NSString* CATCH_ARC_STRONG m_substr;
+            };
+
+            struct Equals : StringHolder {
+                Equals( NSString* substr ) : StringHolder( substr ){}
+
+                bool match( NSString* str ) const override {
+                    return  (str != nil || m_substr == nil ) &&
+                            [str isEqualToString:m_substr];
+                }
+
+                std::string describe() const override {
+                    return "equals string: " + Catch::Detail::stringify( m_substr );
+                }
+            };
+
+            struct Contains : StringHolder {
+                Contains( NSString* substr ) : StringHolder( substr ){}
+
+                bool match( NSString* str ) const {
+                    return  (str != nil || m_substr == nil ) &&
+                            [str rangeOfString:m_substr].location != NSNotFound;
+                }
+
+                std::string describe() const override {
+                    return "contains string: " + Catch::Detail::stringify( m_substr );
+                }
+            };
+
+            struct StartsWith : StringHolder {
+                StartsWith( NSString* substr ) : StringHolder( substr ){}
+
+                bool match( NSString* str ) const override {
+                    return  (str != nil || m_substr == nil ) &&
+                            [str rangeOfString:m_substr].location == 0;
+                }
+
+                std::string describe() const override {
+                    return "starts with: " + Catch::Detail::stringify( m_substr );
+                }
+            };
+            struct EndsWith : StringHolder {
+                EndsWith( NSString* substr ) : StringHolder( substr ){}
+
+                bool match( NSString* str ) const override {
+                    return  (str != nil || m_substr == nil ) &&
+                            [str rangeOfString:m_substr].location == [str length] - [m_substr length];
+                }
+
+                std::string describe() const override {
+                    return "ends with: " + Catch::Detail::stringify( m_substr );
+                }
+            };
+
+        } // namespace NSStringMatchers
+        } // namespace Impl
+
+        inline Impl::NSStringMatchers::Equals
+            Equals( NSString* substr ){ return Impl::NSStringMatchers::Equals( substr ); }
+
+        inline Impl::NSStringMatchers::Contains
+            Contains( NSString* substr ){ return Impl::NSStringMatchers::Contains( substr ); }
+
+        inline Impl::NSStringMatchers::StartsWith
+            StartsWith( NSString* substr ){ return Impl::NSStringMatchers::StartsWith( substr ); }
+
+        inline Impl::NSStringMatchers::EndsWith
+            EndsWith( NSString* substr ){ return Impl::NSStringMatchers::EndsWith( substr ); }
+
+    } // namespace Matchers
+
+    using namespace Matchers;
+
+#endif // CATCH_CONFIG_DISABLE_MATCHERS
+
+} // namespace Catch
+
+///////////////////////////////////////////////////////////////////////////////
+#define OC_MAKE_UNIQUE_NAME( root, uniqueSuffix ) root##uniqueSuffix
+#define OC_TEST_CASE2( name, desc, uniqueSuffix ) \
++(NSString*) OC_MAKE_UNIQUE_NAME( Catch_Name_test_, uniqueSuffix ) \
+{ \
+return @ name; \
+} \
++(NSString*) OC_MAKE_UNIQUE_NAME( Catch_Description_test_, uniqueSuffix ) \
+{ \
+return @ desc; \
+} \
+-(void) OC_MAKE_UNIQUE_NAME( Catch_TestCase_test_, uniqueSuffix )
+
+#define OC_TEST_CASE( name, desc ) OC_TEST_CASE2( name, desc, __LINE__ )
+
+// end catch_objc.hpp
+#endif
+
+#ifdef CATCH_CONFIG_EXTERNAL_INTERFACES
+// start catch_external_interfaces.h
+
+// start catch_reporter_bases.hpp
+
+// start catch_interfaces_reporter.h
+
+// start catch_config.hpp
+
+// start catch_test_spec_parser.h
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wpadded"
+#endif
+
+// start catch_test_spec.h
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wpadded"
+#endif
+
+// start catch_wildcard_pattern.h
+
+namespace Catch
+{
+    class WildcardPattern {
+        enum WildcardPosition {
+            NoWildcard = 0,
+            WildcardAtStart = 1,
+            WildcardAtEnd = 2,
+            WildcardAtBothEnds = WildcardAtStart | WildcardAtEnd
+        };
+
+    public:
+
+        WildcardPattern( std::string const& pattern, CaseSensitive::Choice caseSensitivity );
+        virtual ~WildcardPattern() = default;
+        virtual bool matches( std::string const& str ) const;
+
+    private:
+        std::string adjustCase( std::string const& str ) const;
+        CaseSensitive::Choice m_caseSensitivity;
+        WildcardPosition m_wildcard = NoWildcard;
+        std::string m_pattern;
+    };
+}
+
+// end catch_wildcard_pattern.h
+#include <string>
+#include <vector>
+#include <memory>
+
+namespace Catch {
+
+    class TestSpec {
+        struct Pattern {
+            virtual ~Pattern();
+            virtual bool matches( TestCaseInfo const& testCase ) const = 0;
+        };
+        using PatternPtr = std::shared_ptr<Pattern>;
+
+        class NamePattern : public Pattern {
+        public:
+            NamePattern( std::string const& name );
+            virtual ~NamePattern();
+            virtual bool matches( TestCaseInfo const& testCase ) const override;
+        private:
+            WildcardPattern m_wildcardPattern;
+        };
+
+        class TagPattern : public Pattern {
+        public:
+            TagPattern( std::string const& tag );
+            virtual ~TagPattern();
+            virtual bool matches( TestCaseInfo const& testCase ) const override;
+        private:
+            std::string m_tag;
+        };
+
+        class ExcludedPattern : public Pattern {
+        public:
+            ExcludedPattern( PatternPtr const& underlyingPattern );
+            virtual ~ExcludedPattern();
+            virtual bool matches( TestCaseInfo const& testCase ) const override;
+        private:
+            PatternPtr m_underlyingPattern;
+        };
+
+        struct Filter {
+            std::vector<PatternPtr> m_patterns;
+
+            bool matches( TestCaseInfo const& testCase ) const;
+        };
+
+    public:
+        bool hasFilters() const;
+        bool matches( TestCaseInfo const& testCase ) const;
+
+    private:
+        std::vector<Filter> m_filters;
+
+        friend class TestSpecParser;
+    };
+}
+
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+// end catch_test_spec.h
+// start catch_interfaces_tag_alias_registry.h
+
+#include <string>
+
+namespace Catch {
+
+    struct TagAlias;
+
+    struct ITagAliasRegistry {
+        virtual ~ITagAliasRegistry();
+        // Nullptr if not present
+        virtual TagAlias const* find( std::string const& alias ) const = 0;
+        virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const = 0;
+
+        static ITagAliasRegistry const& get();
+    };
+
+} // end namespace Catch
+
+// end catch_interfaces_tag_alias_registry.h
+namespace Catch {
+
+    class TestSpecParser {
+        enum Mode{ None, Name, QuotedName, Tag, EscapedName };
+        Mode m_mode = None;
+        bool m_exclusion = false;
+        std::size_t m_start = std::string::npos, m_pos = 0;
+        std::string m_arg;
+        std::vector<std::size_t> m_escapeChars;
+        TestSpec::Filter m_currentFilter;
+        TestSpec m_testSpec;
+        ITagAliasRegistry const* m_tagAliases = nullptr;
+
+    public:
+        TestSpecParser( ITagAliasRegistry const& tagAliases );
+
+        TestSpecParser& parse( std::string const& arg );
+        TestSpec testSpec();
+
+    private:
+        void visitChar( char c );
+        void startNewMode( Mode mode, std::size_t start );
+        void escape();
+        std::string subString() const;
+
+        template<typename T>
+        void addPattern() {
+            std::string token = subString();
+            for( std::size_t i = 0; i < m_escapeChars.size(); ++i )
+                token = token.substr( 0, m_escapeChars[i]-m_start-i ) + token.substr( m_escapeChars[i]-m_start-i+1 );
+            m_escapeChars.clear();
+            if( startsWith( token, "exclude:" ) ) {
+                m_exclusion = true;
+                token = token.substr( 8 );
+            }
+            if( !token.empty() ) {
+                TestSpec::PatternPtr pattern = std::make_shared<T>( token );
+                if( m_exclusion )
+                    pattern = std::make_shared<TestSpec::ExcludedPattern>( pattern );
+                m_currentFilter.m_patterns.push_back( pattern );
+            }
+            m_exclusion = false;
+            m_mode = None;
+        }
+
+        void addFilter();
+    };
+    TestSpec parseTestSpec( std::string const& arg );
+
+} // namespace Catch
+
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+// end catch_test_spec_parser.h
+// start catch_interfaces_config.h
+
+#include <iosfwd>
+#include <string>
+#include <vector>
+#include <memory>
+
+namespace Catch {
+
+    enum class Verbosity {
+        Quiet = 0,
+        Normal,
+        High
+    };
+
+    struct WarnAbout { enum What {
+        Nothing = 0x00,
+        NoAssertions = 0x01,
+        NoTests = 0x02
+    }; };
+
+    struct ShowDurations { enum OrNot {
+        DefaultForReporter,
+        Always,
+        Never
+    }; };
+    struct RunTests { enum InWhatOrder {
+        InDeclarationOrder,
+        InLexicographicalOrder,
+        InRandomOrder
+    }; };
+    struct UseColour { enum YesOrNo {
+        Auto,
+        Yes,
+        No
+    }; };
+    struct WaitForKeypress { enum When {
+        Never,
+        BeforeStart = 1,
+        BeforeExit = 2,
+        BeforeStartAndExit = BeforeStart | BeforeExit
+    }; };
+
+    class TestSpec;
+
+    struct IConfig : NonCopyable {
+
+        virtual ~IConfig();
+
+        virtual bool allowThrows() const = 0;
+        virtual std::ostream& stream() const = 0;
+        virtual std::string name() const = 0;
+        virtual bool includeSuccessfulResults() const = 0;
+        virtual bool shouldDebugBreak() const = 0;
+        virtual bool warnAboutMissingAssertions() const = 0;
+        virtual bool warnAboutNoTests() const = 0;
+        virtual int abortAfter() const = 0;
+        virtual bool showInvisibles() const = 0;
+        virtual ShowDurations::OrNot showDurations() const = 0;
+        virtual TestSpec const& testSpec() const = 0;
+        virtual bool hasTestFilters() const = 0;
+        virtual RunTests::InWhatOrder runOrder() const = 0;
+        virtual unsigned int rngSeed() const = 0;
+        virtual int benchmarkResolutionMultiple() const = 0;
+        virtual UseColour::YesOrNo useColour() const = 0;
+        virtual std::vector<std::string> const& getSectionsToRun() const = 0;
+        virtual Verbosity verbosity() const = 0;
+    };
+
+    using IConfigPtr = std::shared_ptr<IConfig const>;
+}
+
+// end catch_interfaces_config.h
+// Libstdc++ doesn't like incomplete classes for unique_ptr
+
+#include <memory>
+#include <vector>
+#include <string>
+
+#ifndef CATCH_CONFIG_CONSOLE_WIDTH
+#define CATCH_CONFIG_CONSOLE_WIDTH 80
+#endif
+
+namespace Catch {
+
+    struct IStream;
+
+    struct ConfigData {
+        bool listTests = false;
+        bool listTags = false;
+        bool listReporters = false;
+        bool listTestNamesOnly = false;
+
+        bool showSuccessfulTests = false;
+        bool shouldDebugBreak = false;
+        bool noThrow = false;
+        bool showHelp = false;
+        bool showInvisibles = false;
+        bool filenamesAsTags = false;
+        bool libIdentify = false;
+
+        int abortAfter = -1;
+        unsigned int rngSeed = 0;
+        int benchmarkResolutionMultiple = 100;
+
+        Verbosity verbosity = Verbosity::Normal;
+        WarnAbout::What warnings = WarnAbout::Nothing;
+        ShowDurations::OrNot showDurations = ShowDurations::DefaultForReporter;
+        RunTests::InWhatOrder runOrder = RunTests::InDeclarationOrder;
+        UseColour::YesOrNo useColour = UseColour::Auto;
+        WaitForKeypress::When waitForKeypress = WaitForKeypress::Never;
+
+        std::string outputFilename;
+        std::string name;
+        std::string processName;
+#ifndef CATCH_CONFIG_DEFAULT_REPORTER
+#define CATCH_CONFIG_DEFAULT_REPORTER "console"
+#endif
+        std::string reporterName = CATCH_CONFIG_DEFAULT_REPORTER;
+#undef CATCH_CONFIG_DEFAULT_REPORTER
+
+        std::vector<std::string> testsOrTags;
+        std::vector<std::string> sectionsToRun;
+    };
+
+    class Config : public IConfig {
+    public:
+
+        Config() = default;
+        Config( ConfigData const& data );
+        virtual ~Config() = default;
+
+        std::string const& getFilename() const;
+
+        bool listTests() const;
+        bool listTestNamesOnly() const;
+        bool listTags() const;
+        bool listReporters() const;
+
+        std::string getProcessName() const;
+        std::string const& getReporterName() const;
+
+        std::vector<std::string> const& getTestsOrTags() const;
+        std::vector<std::string> const& getSectionsToRun() const override;
+
+        virtual TestSpec const& testSpec() const override;
+        bool hasTestFilters() const override;
+
+        bool showHelp() const;
+
+        // IConfig interface
+        bool allowThrows() const override;
+        std::ostream& stream() const override;
+        std::string name() const override;
+        bool includeSuccessfulResults() const override;
+        bool warnAboutMissingAssertions() const override;
+        bool warnAboutNoTests() const override;
+        ShowDurations::OrNot showDurations() const override;
+        RunTests::InWhatOrder runOrder() const override;
+        unsigned int rngSeed() const override;
+        int benchmarkResolutionMultiple() const override;
+        UseColour::YesOrNo useColour() const override;
+        bool shouldDebugBreak() const override;
+        int abortAfter() const override;
+        bool showInvisibles() const override;
+        Verbosity verbosity() const override;
+
+    private:
+
+        IStream const* openStream();
+        ConfigData m_data;
+
+        std::unique_ptr<IStream const> m_stream;
+        TestSpec m_testSpec;
+        bool m_hasTestFilters = false;
+    };
+
+} // end namespace Catch
+
+// end catch_config.hpp
+// start catch_assertionresult.h
+
+#include <string>
+
+namespace Catch {
+
+    struct AssertionResultData
+    {
+        AssertionResultData() = delete;
+
+        AssertionResultData( ResultWas::OfType _resultType, LazyExpression const& _lazyExpression );
+
+        std::string message;
+        mutable std::string reconstructedExpression;
+        LazyExpression lazyExpression;
+        ResultWas::OfType resultType;
+
+        std::string reconstructExpression() const;
+    };
+
+    class AssertionResult {
+    public:
+        AssertionResult() = delete;
+        AssertionResult( AssertionInfo const& info, AssertionResultData const& data );
+
+        bool isOk() const;
+        bool succeeded() const;
+        ResultWas::OfType getResultType() const;
+        bool hasExpression() const;
+        bool hasMessage() const;
+        std::string getExpression() const;
+        std::string getExpressionInMacro() const;
+        bool hasExpandedExpression() const;
+        std::string getExpandedExpression() const;
+        std::string getMessage() const;
+        SourceLineInfo getSourceInfo() const;
+        StringRef getTestMacroName() const;
+
+    //protected:
+        AssertionInfo m_info;
+        AssertionResultData m_resultData;
+    };
+
+} // end namespace Catch
+
+// end catch_assertionresult.h
+// start catch_option.hpp
+
+namespace Catch {
+
+    // An optional type
+    template<typename T>
+    class Option {
+    public:
+        Option() : nullableValue( nullptr ) {}
+        Option( T const& _value )
+        : nullableValue( new( storage ) T( _value ) )
+        {}
+        Option( Option const& _other )
+        : nullableValue( _other ? new( storage ) T( *_other ) : nullptr )
+        {}
+
+        ~Option() {
+            reset();
+        }
+
+        Option& operator= ( Option const& _other ) {
+            if( &_other != this ) {
+                reset();
+                if( _other )
+                    nullableValue = new( storage ) T( *_other );
+            }
+            return *this;
+        }
+        Option& operator = ( T const& _value ) {
+            reset();
+            nullableValue = new( storage ) T( _value );
+            return *this;
+        }
+
+        void reset() {
+            if( nullableValue )
+                nullableValue->~T();
+            nullableValue = nullptr;
+        }
+
+        T& operator*() { return *nullableValue; }
+        T const& operator*() const { return *nullableValue; }
+        T* operator->() { return nullableValue; }
+        const T* operator->() const { return nullableValue; }
+
+        T valueOr( T const& defaultValue ) const {
+            return nullableValue ? *nullableValue : defaultValue;
+        }
+
+        bool some() const { return nullableValue != nullptr; }
+        bool none() const { return nullableValue == nullptr; }
+
+        bool operator !() const { return nullableValue == nullptr; }
+        explicit operator bool() const {
+            return some();
+        }
+
+    private:
+        T *nullableValue;
+        alignas(alignof(T)) char storage[sizeof(T)];
+    };
+
+} // end namespace Catch
+
+// end catch_option.hpp
+#include <string>
+#include <iosfwd>
+#include <map>
+#include <set>
+#include <memory>
+
+namespace Catch {
+
+    struct ReporterConfig {
+        explicit ReporterConfig( IConfigPtr const& _fullConfig );
+
+        ReporterConfig( IConfigPtr const& _fullConfig, std::ostream& _stream );
+
+        std::ostream& stream() const;
+        IConfigPtr fullConfig() const;
+
+    private:
+        std::ostream* m_stream;
+        IConfigPtr m_fullConfig;
+    };
+
+    struct ReporterPreferences {
+        bool shouldRedirectStdOut = false;
+        bool shouldReportAllAssertions = false;
+    };
+
+    template<typename T>
+    struct LazyStat : Option<T> {
+        LazyStat& operator=( T const& _value ) {
+            Option<T>::operator=( _value );
+            used = false;
+            return *this;
+        }
+        void reset() {
+            Option<T>::reset();
+            used = false;
+        }
+        bool used = false;
+    };
+
+    struct TestRunInfo {
+        TestRunInfo( std::string const& _name );
+        std::string name;
+    };
+    struct GroupInfo {
+        GroupInfo(  std::string const& _name,
+                    std::size_t _groupIndex,
+                    std::size_t _groupsCount );
+
+        std::string name;
+        std::size_t groupIndex;
+        std::size_t groupsCounts;
+    };
+
+    struct AssertionStats {
+        AssertionStats( AssertionResult const& _assertionResult,
+                        std::vector<MessageInfo> const& _infoMessages,
+                        Totals const& _totals );
+
+        AssertionStats( AssertionStats const& )              = default;
+        AssertionStats( AssertionStats && )                  = default;
+        AssertionStats& operator = ( AssertionStats const& ) = default;
+        AssertionStats& operator = ( AssertionStats && )     = default;
+        virtual ~AssertionStats();
+
+        AssertionResult assertionResult;
+        std::vector<MessageInfo> infoMessages;
+        Totals totals;
+    };
+
+    struct SectionStats {
+        SectionStats(   SectionInfo const& _sectionInfo,
+                        Counts const& _assertions,
+                        double _durationInSeconds,
+                        bool _missingAssertions );
+        SectionStats( SectionStats const& )              = default;
+        SectionStats( SectionStats && )                  = default;
+        SectionStats& operator = ( SectionStats const& ) = default;
+        SectionStats& operator = ( SectionStats && )     = default;
+        virtual ~SectionStats();
+
+        SectionInfo sectionInfo;
+        Counts assertions;
+        double durationInSeconds;
+        bool missingAssertions;
+    };
+
+    struct TestCaseStats {
+        TestCaseStats(  TestCaseInfo const& _testInfo,
+                        Totals const& _totals,
+                        std::string const& _stdOut,
+                        std::string const& _stdErr,
+                        bool _aborting );
+
+        TestCaseStats( TestCaseStats const& )              = default;
+        TestCaseStats( TestCaseStats && )                  = default;
+        TestCaseStats& operator = ( TestCaseStats const& ) = default;
+        TestCaseStats& operator = ( TestCaseStats && )     = default;
+        virtual ~TestCaseStats();
+
+        TestCaseInfo testInfo;
+        Totals totals;
+        std::string stdOut;
+        std::string stdErr;
+        bool aborting;
+    };
+
+    struct TestGroupStats {
+        TestGroupStats( GroupInfo const& _groupInfo,
+                        Totals const& _totals,
+                        bool _aborting );
+        TestGroupStats( GroupInfo const& _groupInfo );
+
+        TestGroupStats( TestGroupStats const& )              = default;
+        TestGroupStats( TestGroupStats && )                  = default;
+        TestGroupStats& operator = ( TestGroupStats const& ) = default;
+        TestGroupStats& operator = ( TestGroupStats && )     = default;
+        virtual ~TestGroupStats();
+
+        GroupInfo groupInfo;
+        Totals totals;
+        bool aborting;
+    };
+
+    struct TestRunStats {
+        TestRunStats(   TestRunInfo const& _runInfo,
+                        Totals const& _totals,
+                        bool _aborting );
+
+        TestRunStats( TestRunStats const& )              = default;
+        TestRunStats( TestRunStats && )                  = default;
+        TestRunStats& operator = ( TestRunStats const& ) = default;
+        TestRunStats& operator = ( TestRunStats && )     = default;
+        virtual ~TestRunStats();
+
+        TestRunInfo runInfo;
+        Totals totals;
+        bool aborting;
+    };
+
+    struct BenchmarkInfo {
+        std::string name;
+    };
+    struct BenchmarkStats {
+        BenchmarkInfo info;
+        std::size_t iterations;
+        uint64_t elapsedTimeInNanoseconds;
+    };
+
+    struct IStreamingReporter {
+        virtual ~IStreamingReporter() = default;
+
+        // Implementing class must also provide the following static methods:
+        // static std::string getDescription();
+        // static std::set<Verbosity> getSupportedVerbosities()
+
+        virtual ReporterPreferences getPreferences() const = 0;
+
+        virtual void noMatchingTestCases( std::string const& spec ) = 0;
+
+        virtual void testRunStarting( TestRunInfo const& testRunInfo ) = 0;
+        virtual void testGroupStarting( GroupInfo const& groupInfo ) = 0;
+
+        virtual void testCaseStarting( TestCaseInfo const& testInfo ) = 0;
+        virtual void sectionStarting( SectionInfo const& sectionInfo ) = 0;
+
+        // *** experimental ***
+        virtual void benchmarkStarting( BenchmarkInfo const& ) {}
+
+        virtual void assertionStarting( AssertionInfo const& assertionInfo ) = 0;
+
+        // The return value indicates if the messages buffer should be cleared:
+        virtual bool assertionEnded( AssertionStats const& assertionStats ) = 0;
+
+        // *** experimental ***
+        virtual void benchmarkEnded( BenchmarkStats const& ) {}
+
+        virtual void sectionEnded( SectionStats const& sectionStats ) = 0;
+        virtual void testCaseEnded( TestCaseStats const& testCaseStats ) = 0;
+        virtual void testGroupEnded( TestGroupStats const& testGroupStats ) = 0;
+        virtual void testRunEnded( TestRunStats const& testRunStats ) = 0;
+
+        virtual void skipTest( TestCaseInfo const& testInfo ) = 0;
+
+        // Default empty implementation provided
+        virtual void fatalErrorEncountered( StringRef name );
+
+        virtual bool isMulti() const;
+    };
+    using IStreamingReporterPtr = std::unique_ptr<IStreamingReporter>;
+
+    struct IReporterFactory {
+        virtual ~IReporterFactory();
+        virtual IStreamingReporterPtr create( ReporterConfig const& config ) const = 0;
+        virtual std::string getDescription() const = 0;
+    };
+    using IReporterFactoryPtr = std::shared_ptr<IReporterFactory>;
+
+    struct IReporterRegistry {
+        using FactoryMap = std::map<std::string, IReporterFactoryPtr>;
+        using Listeners = std::vector<IReporterFactoryPtr>;
+
+        virtual ~IReporterRegistry();
+        virtual IStreamingReporterPtr create( std::string const& name, IConfigPtr const& config ) const = 0;
+        virtual FactoryMap const& getFactories() const = 0;
+        virtual Listeners const& getListeners() const = 0;
+    };
+
+} // end namespace Catch
+
+// end catch_interfaces_reporter.h
+#include <algorithm>
+#include <cstring>
+#include <cfloat>
+#include <cstdio>
+#include <cassert>
+#include <memory>
+#include <ostream>
+
+namespace Catch {
+    void prepareExpandedExpression(AssertionResult& result);
+
+    // Returns double formatted as %.3f (format expected on output)
+    std::string getFormattedDuration( double duration );
+
+    template<typename DerivedT>
+    struct StreamingReporterBase : IStreamingReporter {
+
+        StreamingReporterBase( ReporterConfig const& _config )
+        :   m_config( _config.fullConfig() ),
+            stream( _config.stream() )
+        {
+            m_reporterPrefs.shouldRedirectStdOut = false;
+            if( !DerivedT::getSupportedVerbosities().count( m_config->verbosity() ) )
+                CATCH_ERROR( "Verbosity level not supported by this reporter" );
+        }
+
+        ReporterPreferences getPreferences() const override {
+            return m_reporterPrefs;
+        }
+
+        static std::set<Verbosity> getSupportedVerbosities() {
+            return { Verbosity::Normal };
+        }
+
+        ~StreamingReporterBase() override = default;
+
+        void noMatchingTestCases(std::string const&) override {}
+
+        void testRunStarting(TestRunInfo const& _testRunInfo) override {
+            currentTestRunInfo = _testRunInfo;
+        }
+        void testGroupStarting(GroupInfo const& _groupInfo) override {
+            currentGroupInfo = _groupInfo;
+        }
+
+        void testCaseStarting(TestCaseInfo const& _testInfo) override  {
+            currentTestCaseInfo = _testInfo;
+        }
+        void sectionStarting(SectionInfo const& _sectionInfo) override {
+            m_sectionStack.push_back(_sectionInfo);
+        }
+
+        void sectionEnded(SectionStats const& /* _sectionStats */) override {
+            m_sectionStack.pop_back();
+        }
+        void testCaseEnded(TestCaseStats const& /* _testCaseStats */) override {
+            currentTestCaseInfo.reset();
+        }
+        void testGroupEnded(TestGroupStats const& /* _testGroupStats */) override {
+            currentGroupInfo.reset();
+        }
+        void testRunEnded(TestRunStats const& /* _testRunStats */) override {
+            currentTestCaseInfo.reset();
+            currentGroupInfo.reset();
+            currentTestRunInfo.reset();
+        }
+
+        void skipTest(TestCaseInfo const&) override {
+            // Don't do anything with this by default.
+            // It can optionally be overridden in the derived class.
+        }
+
+        IConfigPtr m_config;
+        std::ostream& stream;
+
+        LazyStat<TestRunInfo> currentTestRunInfo;
+        LazyStat<GroupInfo> currentGroupInfo;
+        LazyStat<TestCaseInfo> currentTestCaseInfo;
+
+        std::vector<SectionInfo> m_sectionStack;
+        ReporterPreferences m_reporterPrefs;
+    };
+
+    template<typename DerivedT>
+    struct CumulativeReporterBase : IStreamingReporter {
+        template<typename T, typename ChildNodeT>
+        struct Node {
+            explicit Node( T const& _value ) : value( _value ) {}
+            virtual ~Node() {}
+
+            using ChildNodes = std::vector<std::shared_ptr<ChildNodeT>>;
+            T value;
+            ChildNodes children;
+        };
+        struct SectionNode {
+            explicit SectionNode(SectionStats const& _stats) : stats(_stats) {}
+            virtual ~SectionNode() = default;
+
+            bool operator == (SectionNode const& other) const {
+                return stats.sectionInfo.lineInfo == other.stats.sectionInfo.lineInfo;
+            }
+            bool operator == (std::shared_ptr<SectionNode> const& other) const {
+                return operator==(*other);
+            }
+
+            SectionStats stats;
+            using ChildSections = std::vector<std::shared_ptr<SectionNode>>;
+            using Assertions = std::vector<AssertionStats>;
+            ChildSections childSections;
+            Assertions assertions;
+            std::string stdOut;
+            std::string stdErr;
+        };
+
+        struct BySectionInfo {
+            BySectionInfo( SectionInfo const& other ) : m_other( other ) {}
+            BySectionInfo( BySectionInfo const& other ) : m_other( other.m_other ) {}
+            bool operator() (std::shared_ptr<SectionNode> const& node) const {
+                return ((node->stats.sectionInfo.name == m_other.name) &&
+                        (node->stats.sectionInfo.lineInfo == m_other.lineInfo));
+            }
+            void operator=(BySectionInfo const&) = delete;
+
+        private:
+            SectionInfo const& m_other;
+        };
+
+        using TestCaseNode = Node<TestCaseStats, SectionNode>;
+        using TestGroupNode = Node<TestGroupStats, TestCaseNode>;
+        using TestRunNode = Node<TestRunStats, TestGroupNode>;
+
+        CumulativeReporterBase( ReporterConfig const& _config )
+        :   m_config( _config.fullConfig() ),
+            stream( _config.stream() )
+        {
+            m_reporterPrefs.shouldRedirectStdOut = false;
+            if( !DerivedT::getSupportedVerbosities().count( m_config->verbosity() ) )
+                CATCH_ERROR( "Verbosity level not supported by this reporter" );
+        }
+        ~CumulativeReporterBase() override = default;
+
+        ReporterPreferences getPreferences() const override {
+            return m_reporterPrefs;
+        }
+
+        static std::set<Verbosity> getSupportedVerbosities() {
+            return { Verbosity::Normal };
+        }
+
+        void testRunStarting( TestRunInfo const& ) override {}
+        void testGroupStarting( GroupInfo const& ) override {}
+
+        void testCaseStarting( TestCaseInfo const& ) override {}
+
+        void sectionStarting( SectionInfo const& sectionInfo ) override {
+            SectionStats incompleteStats( sectionInfo, Counts(), 0, false );
+            std::shared_ptr<SectionNode> node;
+            if( m_sectionStack.empty() ) {
+                if( !m_rootSection )
+                    m_rootSection = std::make_shared<SectionNode>( incompleteStats );
+                node = m_rootSection;
+            }
+            else {
+                SectionNode& parentNode = *m_sectionStack.back();
+                auto it =
+                    std::find_if(   parentNode.childSections.begin(),
+                                    parentNode.childSections.end(),
+                                    BySectionInfo( sectionInfo ) );
+                if( it == parentNode.childSections.end() ) {
+                    node = std::make_shared<SectionNode>( incompleteStats );
+                    parentNode.childSections.push_back( node );
+                }
+                else
+                    node = *it;
+            }
+            m_sectionStack.push_back( node );
+            m_deepestSection = std::move(node);
+        }
+
+        void assertionStarting(AssertionInfo const&) override {}
+
+        bool assertionEnded(AssertionStats const& assertionStats) override {
+            assert(!m_sectionStack.empty());
+            // AssertionResult holds a pointer to a temporary DecomposedExpression,
+            // which getExpandedExpression() calls to build the expression string.
+            // Our section stack copy of the assertionResult will likely outlive the
+            // temporary, so it must be expanded or discarded now to avoid calling
+            // a destroyed object later.
+            prepareExpandedExpression(const_cast<AssertionResult&>( assertionStats.assertionResult ) );
+            SectionNode& sectionNode = *m_sectionStack.back();
+            sectionNode.assertions.push_back(assertionStats);
+            return true;
+        }
+        void sectionEnded(SectionStats const& sectionStats) override {
+            assert(!m_sectionStack.empty());
+            SectionNode& node = *m_sectionStack.back();
+            node.stats = sectionStats;
+            m_sectionStack.pop_back();
+        }
+        void testCaseEnded(TestCaseStats const& testCaseStats) override {
+            auto node = std::make_shared<TestCaseNode>(testCaseStats);
+            assert(m_sectionStack.size() == 0);
+            node->children.push_back(m_rootSection);
+            m_testCases.push_back(node);
+            m_rootSection.reset();
+
+            assert(m_deepestSection);
+            m_deepestSection->stdOut = testCaseStats.stdOut;
+            m_deepestSection->stdErr = testCaseStats.stdErr;
+        }
+        void testGroupEnded(TestGroupStats const& testGroupStats) override {
+            auto node = std::make_shared<TestGroupNode>(testGroupStats);
+            node->children.swap(m_testCases);
+            m_testGroups.push_back(node);
+        }
+        void testRunEnded(TestRunStats const& testRunStats) override {
+            auto node = std::make_shared<TestRunNode>(testRunStats);
+            node->children.swap(m_testGroups);
+            m_testRuns.push_back(node);
+            testRunEndedCumulative();
+        }
+        virtual void testRunEndedCumulative() = 0;
+
+        void skipTest(TestCaseInfo const&) override {}
+
+        IConfigPtr m_config;
+        std::ostream& stream;
+        std::vector<AssertionStats> m_assertions;
+        std::vector<std::vector<std::shared_ptr<SectionNode>>> m_sections;
+        std::vector<std::shared_ptr<TestCaseNode>> m_testCases;
+        std::vector<std::shared_ptr<TestGroupNode>> m_testGroups;
+
+        std::vector<std::shared_ptr<TestRunNode>> m_testRuns;
+
+        std::shared_ptr<SectionNode> m_rootSection;
+        std::shared_ptr<SectionNode> m_deepestSection;
+        std::vector<std::shared_ptr<SectionNode>> m_sectionStack;
+        ReporterPreferences m_reporterPrefs;
+    };
+
+    template<char C>
+    char const* getLineOfChars() {
+        static char line[CATCH_CONFIG_CONSOLE_WIDTH] = {0};
+        if( !*line ) {
+            std::memset( line, C, CATCH_CONFIG_CONSOLE_WIDTH-1 );
+            line[CATCH_CONFIG_CONSOLE_WIDTH-1] = 0;
+        }
+        return line;
+    }
+
+    struct TestEventListenerBase : StreamingReporterBase<TestEventListenerBase> {
+        TestEventListenerBase( ReporterConfig const& _config );
+
+        static std::set<Verbosity> getSupportedVerbosities();
+
+        void assertionStarting(AssertionInfo const&) override;
+        bool assertionEnded(AssertionStats const&) override;
+    };
+
+} // end namespace Catch
+
+// end catch_reporter_bases.hpp
+// start catch_console_colour.h
+
+namespace Catch {
+
+    struct Colour {
+        enum Code {
+            None = 0,
+
+            White,
+            Red,
+            Green,
+            Blue,
+            Cyan,
+            Yellow,
+            Grey,
+
+            Bright = 0x10,
+
+            BrightRed = Bright | Red,
+            BrightGreen = Bright | Green,
+            LightGrey = Bright | Grey,
+            BrightWhite = Bright | White,
+            BrightYellow = Bright | Yellow,
+
+            // By intention
+            FileName = LightGrey,
+            Warning = BrightYellow,
+            ResultError = BrightRed,
+            ResultSuccess = BrightGreen,
+            ResultExpectedFailure = Warning,
+
+            Error = BrightRed,
+            Success = Green,
+
+            OriginalExpression = Cyan,
+            ReconstructedExpression = BrightYellow,
+
+            SecondaryText = LightGrey,
+            Headers = White
+        };
+
+        // Use constructed object for RAII guard
+        Colour( Code _colourCode );
+        Colour( Colour&& other ) noexcept;
+        Colour& operator=( Colour&& other ) noexcept;
+        ~Colour();
+
+        // Use static method for one-shot changes
+        static void use( Code _colourCode );
+
+    private:
+        bool m_moved = false;
+    };
+
+    std::ostream& operator << ( std::ostream& os, Colour const& );
+
+} // end namespace Catch
+
+// end catch_console_colour.h
+// start catch_reporter_registrars.hpp
+
+
+namespace Catch {
+
+    template<typename T>
+    class ReporterRegistrar {
+
+        class ReporterFactory : public IReporterFactory {
+
+            virtual IStreamingReporterPtr create( ReporterConfig const& config ) const override {
+                return std::unique_ptr<T>( new T( config ) );
+            }
+
+            virtual std::string getDescription() const override {
+                return T::getDescription();
+            }
+        };
+
+    public:
+
+        explicit ReporterRegistrar( std::string const& name ) {
+            getMutableRegistryHub().registerReporter( name, std::make_shared<ReporterFactory>() );
+        }
+    };
+
+    template<typename T>
+    class ListenerRegistrar {
+
+        class ListenerFactory : public IReporterFactory {
+
+            virtual IStreamingReporterPtr create( ReporterConfig const& config ) const override {
+                return std::unique_ptr<T>( new T( config ) );
+            }
+            virtual std::string getDescription() const override {
+                return std::string();
+            }
+        };
+
+    public:
+
+        ListenerRegistrar() {
+            getMutableRegistryHub().registerListener( std::make_shared<ListenerFactory>() );
+        }
+    };
+}
+
+#if !defined(CATCH_CONFIG_DISABLE)
+
+#define CATCH_REGISTER_REPORTER( name, reporterType ) \
+    CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS          \
+    namespace{ Catch::ReporterRegistrar<reporterType> catch_internal_RegistrarFor##reporterType( name ); } \
+    CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS
+
+#define CATCH_REGISTER_LISTENER( listenerType ) \
+     CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS   \
+     namespace{ Catch::ListenerRegistrar<listenerType> catch_internal_RegistrarFor##listenerType; } \
+     CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS
+#else // CATCH_CONFIG_DISABLE
+
+#define CATCH_REGISTER_REPORTER(name, reporterType)
+#define CATCH_REGISTER_LISTENER(listenerType)
+
+#endif // CATCH_CONFIG_DISABLE
+
+// end catch_reporter_registrars.hpp
+// Allow users to base their work off existing reporters
+// start catch_reporter_compact.h
+
+namespace Catch {
+
+    struct CompactReporter : StreamingReporterBase<CompactReporter> {
+
+        using StreamingReporterBase::StreamingReporterBase;
+
+        ~CompactReporter() override;
+
+        static std::string getDescription();
+
+        ReporterPreferences getPreferences() const override;
+
+        void noMatchingTestCases(std::string const& spec) override;
+
+        void assertionStarting(AssertionInfo const&) override;
+
+        bool assertionEnded(AssertionStats const& _assertionStats) override;
+
+        void sectionEnded(SectionStats const& _sectionStats) override;
+
+        void testRunEnded(TestRunStats const& _testRunStats) override;
+
+    };
+
+} // end namespace Catch
+
+// end catch_reporter_compact.h
+// start catch_reporter_console.h
+
+#if defined(_MSC_VER)
+#pragma warning(push)
+#pragma warning(disable:4061) // Not all labels are EXPLICITLY handled in switch
+                              // Note that 4062 (not all labels are handled
+                              // and default is missing) is enabled
+#endif
+
+namespace Catch {
+    // Fwd decls
+    struct SummaryColumn;
+    class TablePrinter;
+
+    struct ConsoleReporter : StreamingReporterBase<ConsoleReporter> {
+        std::unique_ptr<TablePrinter> m_tablePrinter;
+
+        ConsoleReporter(ReporterConfig const& config);
+        ~ConsoleReporter() override;
+        static std::string getDescription();
+
+        void noMatchingTestCases(std::string const& spec) override;
+
+        void assertionStarting(AssertionInfo const&) override;
+
+        bool assertionEnded(AssertionStats const& _assertionStats) override;
+
+        void sectionStarting(SectionInfo const& _sectionInfo) override;
+        void sectionEnded(SectionStats const& _sectionStats) override;
+
+        void benchmarkStarting(BenchmarkInfo const& info) override;
+        void benchmarkEnded(BenchmarkStats const& stats) override;
+
+        void testCaseEnded(TestCaseStats const& _testCaseStats) override;
+        void testGroupEnded(TestGroupStats const& _testGroupStats) override;
+        void testRunEnded(TestRunStats const& _testRunStats) override;
+
+    private:
+
+        void lazyPrint();
+
+        void lazyPrintWithoutClosingBenchmarkTable();
+        void lazyPrintRunInfo();
+        void lazyPrintGroupInfo();
+        void printTestCaseAndSectionHeader();
+
+        void printClosedHeader(std::string const& _name);
+        void printOpenHeader(std::string const& _name);
+
+        // if string has a : in first line will set indent to follow it on
+        // subsequent lines
+        void printHeaderString(std::string const& _string, std::size_t indent = 0);
+
+        void printTotals(Totals const& totals);
+        void printSummaryRow(std::string const& label, std::vector<SummaryColumn> const& cols, std::size_t row);
+
+        void printTotalsDivider(Totals const& totals);
+        void printSummaryDivider();
+
+    private:
+        bool m_headerPrinted = false;
+    };
+
+} // end namespace Catch
+
+#if defined(_MSC_VER)
+#pragma warning(pop)
+#endif
+
+// end catch_reporter_console.h
+// start catch_reporter_junit.h
+
+// start catch_xmlwriter.h
+
+#include <vector>
+
+namespace Catch {
+
+    class XmlEncode {
+    public:
+        enum ForWhat { ForTextNodes, ForAttributes };
+
+        XmlEncode( std::string const& str, ForWhat forWhat = ForTextNodes );
+
+        void encodeTo( std::ostream& os ) const;
+
+        friend std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode );
+
+    private:
+        std::string m_str;
+        ForWhat m_forWhat;
+    };
+
+    class XmlWriter {
+    public:
+
+        class ScopedElement {
+        public:
+            ScopedElement( XmlWriter* writer );
+
+            ScopedElement( ScopedElement&& other ) noexcept;
+            ScopedElement& operator=( ScopedElement&& other ) noexcept;
+
+            ~ScopedElement();
+
+            ScopedElement& writeText( std::string const& text, bool indent = true );
+
+            template<typename T>
+            ScopedElement& writeAttribute( std::string const& name, T const& attribute ) {
+                m_writer->writeAttribute( name, attribute );
+                return *this;
+            }
+
+        private:
+            mutable XmlWriter* m_writer = nullptr;
+        };
+
+        XmlWriter( std::ostream& os = Catch::cout() );
+        ~XmlWriter();
+
+        XmlWriter( XmlWriter const& ) = delete;
+        XmlWriter& operator=( XmlWriter const& ) = delete;
+
+        XmlWriter& startElement( std::string const& name );
+
+        ScopedElement scopedElement( std::string const& name );
+
+        XmlWriter& endElement();
+
+        XmlWriter& writeAttribute( std::string const& name, std::string const& attribute );
+
+        XmlWriter& writeAttribute( std::string const& name, bool attribute );
+
+        template<typename T>
+        XmlWriter& writeAttribute( std::string const& name, T const& attribute ) {
+            ReusableStringStream rss;
+            rss << attribute;
+            return writeAttribute( name, rss.str() );
+        }
+
+        XmlWriter& writeText( std::string const& text, bool indent = true );
+
+        XmlWriter& writeComment( std::string const& text );
+
+        void writeStylesheetRef( std::string const& url );
+
+        XmlWriter& writeBlankLine();
+
+        void ensureTagClosed();
+
+    private:
+
+        void writeDeclaration();
+
+        void newlineIfNecessary();
+
+        bool m_tagIsOpen = false;
+        bool m_needsNewline = false;
+        std::vector<std::string> m_tags;
+        std::string m_indent;
+        std::ostream& m_os;
+    };
+
+}
+
+// end catch_xmlwriter.h
+namespace Catch {
+
+    class JunitReporter : public CumulativeReporterBase<JunitReporter> {
+    public:
+        JunitReporter(ReporterConfig const& _config);
+
+        ~JunitReporter() override;
+
+        static std::string getDescription();
+
+        void noMatchingTestCases(std::string const& /*spec*/) override;
+
+        void testRunStarting(TestRunInfo const& runInfo) override;
+
+        void testGroupStarting(GroupInfo const& groupInfo) override;
+
+        void testCaseStarting(TestCaseInfo const& testCaseInfo) override;
+        bool assertionEnded(AssertionStats const& assertionStats) override;
+
+        void testCaseEnded(TestCaseStats const& testCaseStats) override;
+
+        void testGroupEnded(TestGroupStats const& testGroupStats) override;
+
+        void testRunEndedCumulative() override;
+
+        void writeGroup(TestGroupNode const& groupNode, double suiteTime);
+
+        void writeTestCase(TestCaseNode const& testCaseNode);
+
+        void writeSection(std::string const& className,
+                          std::string const& rootName,
+                          SectionNode const& sectionNode);
+
+        void writeAssertions(SectionNode const& sectionNode);
+        void writeAssertion(AssertionStats const& stats);
+
+        XmlWriter xml;
+        Timer suiteTimer;
+        std::string stdOutForSuite;
+        std::string stdErrForSuite;
+        unsigned int unexpectedExceptions = 0;
+        bool m_okToFail = false;
+    };
+
+} // end namespace Catch
+
+// end catch_reporter_junit.h
+// start catch_reporter_xml.h
+
+namespace Catch {
+    class XmlReporter : public StreamingReporterBase<XmlReporter> {
+    public:
+        XmlReporter(ReporterConfig const& _config);
+
+        ~XmlReporter() override;
+
+        static std::string getDescription();
+
+        virtual std::string getStylesheetRef() const;
+
+        void writeSourceInfo(SourceLineInfo const& sourceInfo);
+
+    public: // StreamingReporterBase
+
+        void noMatchingTestCases(std::string const& s) override;
+
+        void testRunStarting(TestRunInfo const& testInfo) override;
+
+        void testGroupStarting(GroupInfo const& groupInfo) override;
+
+        void testCaseStarting(TestCaseInfo const& testInfo) override;
+
+        void sectionStarting(SectionInfo const& sectionInfo) override;
+
+        void assertionStarting(AssertionInfo const&) override;
+
+        bool assertionEnded(AssertionStats const& assertionStats) override;
+
+        void sectionEnded(SectionStats const& sectionStats) override;
+
+        void testCaseEnded(TestCaseStats const& testCaseStats) override;
+
+        void testGroupEnded(TestGroupStats const& testGroupStats) override;
+
+        void testRunEnded(TestRunStats const& testRunStats) override;
+
+    private:
+        Timer m_testCaseTimer;
+        XmlWriter m_xml;
+        int m_sectionDepth = 0;
+    };
+
+} // end namespace Catch
+
+// end catch_reporter_xml.h
+
+// end catch_external_interfaces.h
+#endif
+
+#endif // ! CATCH_CONFIG_IMPL_ONLY
+
+#ifdef CATCH_IMPL
+// start catch_impl.hpp
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wweak-vtables"
+#endif
+
+// Keep these here for external reporters
+// start catch_test_case_tracker.h
+
+#include <string>
+#include <vector>
+#include <memory>
+
+namespace Catch {
+namespace TestCaseTracking {
+
+    struct NameAndLocation {
+        std::string name;
+        SourceLineInfo location;
+
+        NameAndLocation( std::string const& _name, SourceLineInfo const& _location );
+    };
+
+    struct ITracker;
+
+    using ITrackerPtr = std::shared_ptr<ITracker>;
+
+    struct ITracker {
+        virtual ~ITracker();
+
+        // static queries
+        virtual NameAndLocation const& nameAndLocation() const = 0;
+
+        // dynamic queries
+        virtual bool isComplete() const = 0; // Successfully completed or failed
+        virtual bool isSuccessfullyCompleted() const = 0;
+        virtual bool isOpen() const = 0; // Started but not complete
+        virtual bool hasChildren() const = 0;
+
+        virtual ITracker& parent() = 0;
+
+        // actions
+        virtual void close() = 0; // Successfully complete
+        virtual void fail() = 0;
+        virtual void markAsNeedingAnotherRun() = 0;
+
+        virtual void addChild( ITrackerPtr const& child ) = 0;
+        virtual ITrackerPtr findChild( NameAndLocation const& nameAndLocation ) = 0;
+        virtual void openChild() = 0;
+
+        // Debug/ checking
+        virtual bool isSectionTracker() const = 0;
+        virtual bool isIndexTracker() const = 0;
+    };
+
+    class TrackerContext {
+
+        enum RunState {
+            NotStarted,
+            Executing,
+            CompletedCycle
+        };
+
+        ITrackerPtr m_rootTracker;
+        ITracker* m_currentTracker = nullptr;
+        RunState m_runState = NotStarted;
+
+    public:
+
+        static TrackerContext& instance();
+
+        ITracker& startRun();
+        void endRun();
+
+        void startCycle();
+        void completeCycle();
+
+        bool completedCycle() const;
+        ITracker& currentTracker();
+        void setCurrentTracker( ITracker* tracker );
+    };
+
+    class TrackerBase : public ITracker {
+    protected:
+        enum CycleState {
+            NotStarted,
+            Executing,
+            ExecutingChildren,
+            NeedsAnotherRun,
+            CompletedSuccessfully,
+            Failed
+        };
+
+        using Children = std::vector<ITrackerPtr>;
+        NameAndLocation m_nameAndLocation;
+        TrackerContext& m_ctx;
+        ITracker* m_parent;
+        Children m_children;
+        CycleState m_runState = NotStarted;
+
+    public:
+        TrackerBase( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent );
+
+        NameAndLocation const& nameAndLocation() const override;
+        bool isComplete() const override;
+        bool isSuccessfullyCompleted() const override;
+        bool isOpen() const override;
+        bool hasChildren() const override;
+
+        void addChild( ITrackerPtr const& child ) override;
+
+        ITrackerPtr findChild( NameAndLocation const& nameAndLocation ) override;
+        ITracker& parent() override;
+
+        void openChild() override;
+
+        bool isSectionTracker() const override;
+        bool isIndexTracker() const override;
+
+        void open();
+
+        void close() override;
+        void fail() override;
+        void markAsNeedingAnotherRun() override;
+
+    private:
+        void moveToParent();
+        void moveToThis();
+    };
+
+    class SectionTracker : public TrackerBase {
+        std::vector<std::string> m_filters;
+    public:
+        SectionTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent );
+
+        bool isSectionTracker() const override;
+
+        static SectionTracker& acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation );
+
+        void tryOpen();
+
+        void addInitialFilters( std::vector<std::string> const& filters );
+        void addNextFilters( std::vector<std::string> const& filters );
+    };
+
+    class IndexTracker : public TrackerBase {
+        int m_size;
+        int m_index = -1;
+    public:
+        IndexTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent, int size );
+
+        bool isIndexTracker() const override;
+        void close() override;
+
+        static IndexTracker& acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation, int size );
+
+        int index() const;
+
+        void moveNext();
+    };
+
+} // namespace TestCaseTracking
+
+using TestCaseTracking::ITracker;
+using TestCaseTracking::TrackerContext;
+using TestCaseTracking::SectionTracker;
+using TestCaseTracking::IndexTracker;
+
+} // namespace Catch
+
+// end catch_test_case_tracker.h
+
+// start catch_leak_detector.h
+
+namespace Catch {
+
+    struct LeakDetector {
+        LeakDetector();
+        ~LeakDetector();
+    };
+
+}
+// end catch_leak_detector.h
+// Cpp files will be included in the single-header file here
+// start catch_approx.cpp
+
+#include <cmath>
+#include <limits>
+
+namespace {
+
+// Performs equivalent check of std::fabs(lhs - rhs) <= margin
+// But without the subtraction to allow for INFINITY in comparison
+bool marginComparison(double lhs, double rhs, double margin) {
+    return (lhs + margin >= rhs) && (rhs + margin >= lhs);
+}
+
+}
+
+namespace Catch {
+namespace Detail {
+
+    Approx::Approx ( double value )
+    :   m_epsilon( std::numeric_limits<float>::epsilon()*100 ),
+        m_margin( 0.0 ),
+        m_scale( 0.0 ),
+        m_value( value )
+    {}
+
+    Approx Approx::custom() {
+        return Approx( 0 );
+    }
+
+    Approx Approx::operator-() const {
+        auto temp(*this);
+        temp.m_value = -temp.m_value;
+        return temp;
+    }
+
+    std::string Approx::toString() const {
+        ReusableStringStream rss;
+        rss << "Approx( " << ::Catch::Detail::stringify( m_value ) << " )";
+        return rss.str();
+    }
+
+    bool Approx::equalityComparisonImpl(const double other) const {
+        // First try with fixed margin, then compute margin based on epsilon, scale and Approx's value
+        // Thanks to Richard Harris for his help refining the scaled margin value
+        return marginComparison(m_value, other, m_margin) || marginComparison(m_value, other, m_epsilon * (m_scale + std::fabs(m_value)));
+    }
+
+    void Approx::setMargin(double margin) {
+        CATCH_ENFORCE(margin >= 0,
+            "Invalid Approx::margin: " << margin << '.'
+            << " Approx::Margin has to be non-negative.");
+        m_margin = margin;
+    }
+
+    void Approx::setEpsilon(double epsilon) {
+        CATCH_ENFORCE(epsilon >= 0 && epsilon <= 1.0,
+            "Invalid Approx::epsilon: " << epsilon << '.'
+            << " Approx::epsilon has to be in [0, 1]");
+        m_epsilon = epsilon;
+    }
+
+} // end namespace Detail
+
+namespace literals {
+    Detail::Approx operator "" _a(long double val) {
+        return Detail::Approx(val);
+    }
+    Detail::Approx operator "" _a(unsigned long long val) {
+        return Detail::Approx(val);
+    }
+} // end namespace literals
+
+std::string StringMaker<Catch::Detail::Approx>::convert(Catch::Detail::Approx const& value) {
+    return value.toString();
+}
+
+} // end namespace Catch
+// end catch_approx.cpp
+// start catch_assertionhandler.cpp
+
+// start catch_context.h
+
+#include <memory>
+
+namespace Catch {
+
+    struct IResultCapture;
+    struct IRunner;
+    struct IConfig;
+    struct IMutableContext;
+
+    using IConfigPtr = std::shared_ptr<IConfig const>;
+
+    struct IContext
+    {
+        virtual ~IContext();
+
+        virtual IResultCapture* getResultCapture() = 0;
+        virtual IRunner* getRunner() = 0;
+        virtual IConfigPtr const& getConfig() const = 0;
+    };
+
+    struct IMutableContext : IContext
+    {
+        virtual ~IMutableContext();
+        virtual void setResultCapture( IResultCapture* resultCapture ) = 0;
+        virtual void setRunner( IRunner* runner ) = 0;
+        virtual void setConfig( IConfigPtr const& config ) = 0;
+
+    private:
+        static IMutableContext *currentContext;
+        friend IMutableContext& getCurrentMutableContext();
+        friend void cleanUpContext();
+        static void createContext();
+    };
+
+    inline IMutableContext& getCurrentMutableContext()
+    {
+        if( !IMutableContext::currentContext )
+            IMutableContext::createContext();
+        return *IMutableContext::currentContext;
+    }
+
+    inline IContext& getCurrentContext()
+    {
+        return getCurrentMutableContext();
+    }
+
+    void cleanUpContext();
+}
+
+// end catch_context.h
+// start catch_debugger.h
+
+namespace Catch {
+    bool isDebuggerActive();
+}
+
+#ifdef CATCH_PLATFORM_MAC
+
+    #define CATCH_TRAP() __asm__("int $3\n" : : ) /* NOLINT */
+
+#elif defined(CATCH_PLATFORM_LINUX)
+    // If we can use inline assembler, do it because this allows us to break
+    // directly at the location of the failing check instead of breaking inside
+    // raise() called from it, i.e. one stack frame below.
+    #if defined(__GNUC__) && (defined(__i386) || defined(__x86_64))
+        #define CATCH_TRAP() asm volatile ("int $3") /* NOLINT */
+    #else // Fall back to the generic way.
+        #include <signal.h>
+
+        #define CATCH_TRAP() raise(SIGTRAP)
+    #endif
+#elif defined(_MSC_VER)
+    #define CATCH_TRAP() __debugbreak()
+#elif defined(__MINGW32__)
+    extern "C" __declspec(dllimport) void __stdcall DebugBreak();
+    #define CATCH_TRAP() DebugBreak()
+#endif
+
+#ifdef CATCH_TRAP
+    #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) { CATCH_TRAP(); }
+#else
+    namespace Catch {
+        inline void doNothing() {}
+    }
+    #define CATCH_BREAK_INTO_DEBUGGER() Catch::doNothing()
+#endif
+
+// end catch_debugger.h
+// start catch_run_context.h
+
+// start catch_fatal_condition.h
+
+// start catch_windows_h_proxy.h
+
+
+#if defined(CATCH_PLATFORM_WINDOWS)
+
+#if !defined(NOMINMAX) && !defined(CATCH_CONFIG_NO_NOMINMAX)
+#  define CATCH_DEFINED_NOMINMAX
+#  define NOMINMAX
+#endif
+#if !defined(WIN32_LEAN_AND_MEAN) && !defined(CATCH_CONFIG_NO_WIN32_LEAN_AND_MEAN)
+#  define CATCH_DEFINED_WIN32_LEAN_AND_MEAN
+#  define WIN32_LEAN_AND_MEAN
+#endif
+
+#ifdef __AFXDLL
+#include <AfxWin.h>
+#else
+#include <windows.h>
+#endif
+
+#ifdef CATCH_DEFINED_NOMINMAX
+#  undef NOMINMAX
+#endif
+#ifdef CATCH_DEFINED_WIN32_LEAN_AND_MEAN
+#  undef WIN32_LEAN_AND_MEAN
+#endif
+
+#endif // defined(CATCH_PLATFORM_WINDOWS)
+
+// end catch_windows_h_proxy.h
+#if defined( CATCH_CONFIG_WINDOWS_SEH )
+
+namespace Catch {
+
+    struct FatalConditionHandler {
+
+        static LONG CALLBACK handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo);
+        FatalConditionHandler();
+        static void reset();
+        ~FatalConditionHandler();
+
+    private:
+        static bool isSet;
+        static ULONG guaranteeSize;
+        static PVOID exceptionHandlerHandle;
+    };
+
+} // namespace Catch
+
+#elif defined ( CATCH_CONFIG_POSIX_SIGNALS )
+
+#include <signal.h>
+
+namespace Catch {
+
+    struct FatalConditionHandler {
+
+        static bool isSet;
+        static struct sigaction oldSigActions[];
+        static stack_t oldSigStack;
+        static char altStackMem[];
+
+        static void handleSignal( int sig );
+
+        FatalConditionHandler();
+        ~FatalConditionHandler();
+        static void reset();
+    };
+
+} // namespace Catch
+
+#else
+
+namespace Catch {
+    struct FatalConditionHandler {
+        void reset();
+    };
+}
+
+#endif
+
+// end catch_fatal_condition.h
+#include <string>
+
+namespace Catch {
+
+    struct IMutableContext;
+
+    ///////////////////////////////////////////////////////////////////////////
+
+    class RunContext : public IResultCapture, public IRunner {
+
+    public:
+        RunContext( RunContext const& ) = delete;
+        RunContext& operator =( RunContext const& ) = delete;
+
+        explicit RunContext( IConfigPtr const& _config, IStreamingReporterPtr&& reporter );
+
+        ~RunContext() override;
+
+        void testGroupStarting( std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount );
+        void testGroupEnded( std::string const& testSpec, Totals const& totals, std::size_t groupIndex, std::size_t groupsCount );
+
+        Totals runTest(TestCase const& testCase);
+
+        IConfigPtr config() const;
+        IStreamingReporter& reporter() const;
+
+    public: // IResultCapture
+
+        // Assertion handlers
+        void handleExpr
+                (   AssertionInfo const& info,
+                    ITransientExpression const& expr,
+                    AssertionReaction& reaction ) override;
+        void handleMessage
+                (   AssertionInfo const& info,
+                    ResultWas::OfType resultType,
+                    StringRef const& message,
+                    AssertionReaction& reaction ) override;
+        void handleUnexpectedExceptionNotThrown
+                (   AssertionInfo const& info,
+                    AssertionReaction& reaction ) override;
+        void handleUnexpectedInflightException
+                (   AssertionInfo const& info,
+                    std::string const& message,
+                    AssertionReaction& reaction ) override;
+        void handleIncomplete
+                (   AssertionInfo const& info ) override;
+        void handleNonExpr
+                (   AssertionInfo const &info,
+                    ResultWas::OfType resultType,
+                    AssertionReaction &reaction ) override;
+
+        bool sectionStarted( SectionInfo const& sectionInfo, Counts& assertions ) override;
+
+        void sectionEnded( SectionEndInfo const& endInfo ) override;
+        void sectionEndedEarly( SectionEndInfo const& endInfo ) override;
+
+        auto acquireGeneratorTracker( SourceLineInfo const& lineInfo ) -> IGeneratorTracker& override;
+
+        void benchmarkStarting( BenchmarkInfo const& info ) override;
+        void benchmarkEnded( BenchmarkStats const& stats ) override;
+
+        void pushScopedMessage( MessageInfo const& message ) override;
+        void popScopedMessage( MessageInfo const& message ) override;
+
+        std::string getCurrentTestName() const override;
+
+        const AssertionResult* getLastResult() const override;
+
+        void exceptionEarlyReported() override;
+
+        void handleFatalErrorCondition( StringRef message ) override;
+
+        bool lastAssertionPassed() override;
+
+        void assertionPassed() override;
+
+    public:
+        // !TBD We need to do this another way!
+        bool aborting() const final;
+
+    private:
+
+        void runCurrentTest( std::string& redirectedCout, std::string& redirectedCerr );
+        void invokeActiveTestCase();
+
+        void resetAssertionInfo();
+        bool testForMissingAssertions( Counts& assertions );
+
+        void assertionEnded( AssertionResult const& result );
+        void reportExpr
+                (   AssertionInfo const &info,
+                    ResultWas::OfType resultType,
+                    ITransientExpression const *expr,
+                    bool negated );
+
+        void populateReaction( AssertionReaction& reaction );
+
+    private:
+
+        void handleUnfinishedSections();
+
+        TestRunInfo m_runInfo;
+        IMutableContext& m_context;
+        TestCase const* m_activeTestCase = nullptr;
+        ITracker* m_testCaseTracker;
+        Option<AssertionResult> m_lastResult;
+
+        IConfigPtr m_config;
+        Totals m_totals;
+        IStreamingReporterPtr m_reporter;
+        std::vector<MessageInfo> m_messages;
+        AssertionInfo m_lastAssertionInfo;
+        std::vector<SectionEndInfo> m_unfinishedSections;
+        std::vector<ITracker*> m_activeSections;
+        TrackerContext m_trackerContext;
+        bool m_lastAssertionPassed = false;
+        bool m_shouldReportUnexpected = true;
+        bool m_includeSuccessfulResults;
+    };
+
+} // end namespace Catch
+
+// end catch_run_context.h
+namespace Catch {
+
+    namespace {
+        auto operator <<( std::ostream& os, ITransientExpression const& expr ) -> std::ostream& {
+            expr.streamReconstructedExpression( os );
+            return os;
+        }
+    }
+
+    LazyExpression::LazyExpression( bool isNegated )
+    :   m_isNegated( isNegated )
+    {}
+
+    LazyExpression::LazyExpression( LazyExpression const& other ) : m_isNegated( other.m_isNegated ) {}
+
+    LazyExpression::operator bool() const {
+        return m_transientExpression != nullptr;
+    }
+
+    auto operator << ( std::ostream& os, LazyExpression const& lazyExpr ) -> std::ostream& {
+        if( lazyExpr.m_isNegated )
+            os << "!";
+
+        if( lazyExpr ) {
+            if( lazyExpr.m_isNegated && lazyExpr.m_transientExpression->isBinaryExpression() )
+                os << "(" << *lazyExpr.m_transientExpression << ")";
+            else
+                os << *lazyExpr.m_transientExpression;
+        }
+        else {
+            os << "{** error - unchecked empty expression requested **}";
+        }
+        return os;
+    }
+
+    AssertionHandler::AssertionHandler
+        (   StringRef const& macroName,
+            SourceLineInfo const& lineInfo,
+            StringRef capturedExpression,
+            ResultDisposition::Flags resultDisposition )
+    :   m_assertionInfo{ macroName, lineInfo, capturedExpression, resultDisposition },
+        m_resultCapture( getResultCapture() )
+    {}
+
+    void AssertionHandler::handleExpr( ITransientExpression const& expr ) {
+        m_resultCapture.handleExpr( m_assertionInfo, expr, m_reaction );
+    }
+    void AssertionHandler::handleMessage(ResultWas::OfType resultType, StringRef const& message) {
+        m_resultCapture.handleMessage( m_assertionInfo, resultType, message, m_reaction );
+    }
+
+    auto AssertionHandler::allowThrows() const -> bool {
+        return getCurrentContext().getConfig()->allowThrows();
+    }
+
+    void AssertionHandler::complete() {
+        setCompleted();
+        if( m_reaction.shouldDebugBreak ) {
+
+            // If you find your debugger stopping you here then go one level up on the
+            // call-stack for the code that caused it (typically a failed assertion)
+
+            // (To go back to the test and change execution, jump over the throw, next)
+            CATCH_BREAK_INTO_DEBUGGER();
+        }
+        if (m_reaction.shouldThrow) {
+#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)
+            throw Catch::TestFailureException();
+#else
+            CATCH_ERROR( "Test failure requires aborting test!" );
+#endif
+        }
+    }
+    void AssertionHandler::setCompleted() {
+        m_completed = true;
+    }
+
+    void AssertionHandler::handleUnexpectedInflightException() {
+        m_resultCapture.handleUnexpectedInflightException( m_assertionInfo, Catch::translateActiveException(), m_reaction );
+    }
+
+    void AssertionHandler::handleExceptionThrownAsExpected() {
+        m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction);
+    }
+    void AssertionHandler::handleExceptionNotThrownAsExpected() {
+        m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction);
+    }
+
+    void AssertionHandler::handleUnexpectedExceptionNotThrown() {
+        m_resultCapture.handleUnexpectedExceptionNotThrown( m_assertionInfo, m_reaction );
+    }
+
+    void AssertionHandler::handleThrowingCallSkipped() {
+        m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction);
+    }
+
+    // This is the overload that takes a string and infers the Equals matcher from it
+    // The more general overload, that takes any string matcher, is in catch_capture_matchers.cpp
+    void handleExceptionMatchExpr( AssertionHandler& handler, std::string const& str, StringRef const& matcherString  ) {
+        handleExceptionMatchExpr( handler, Matchers::Equals( str ), matcherString );
+    }
+
+} // namespace Catch
+// end catch_assertionhandler.cpp
+// start catch_assertionresult.cpp
+
+namespace Catch {
+    AssertionResultData::AssertionResultData(ResultWas::OfType _resultType, LazyExpression const & _lazyExpression):
+        lazyExpression(_lazyExpression),
+        resultType(_resultType) {}
+
+    std::string AssertionResultData::reconstructExpression() const {
+
+        if( reconstructedExpression.empty() ) {
+            if( lazyExpression ) {
+                ReusableStringStream rss;
+                rss << lazyExpression;
+                reconstructedExpression = rss.str();
+            }
+        }
+        return reconstructedExpression;
+    }
+
+    AssertionResult::AssertionResult( AssertionInfo const& info, AssertionResultData const& data )
+    :   m_info( info ),
+        m_resultData( data )
+    {}
+
+    // Result was a success
+    bool AssertionResult::succeeded() const {
+        return Catch::isOk( m_resultData.resultType );
+    }
+
+    // Result was a success, or failure is suppressed
+    bool AssertionResult::isOk() const {
+        return Catch::isOk( m_resultData.resultType ) || shouldSuppressFailure( m_info.resultDisposition );
+    }
+
+    ResultWas::OfType AssertionResult::getResultType() const {
+        return m_resultData.resultType;
+    }
+
+    bool AssertionResult::hasExpression() const {
+        return m_info.capturedExpression[0] != 0;
+    }
+
+    bool AssertionResult::hasMessage() const {
+        return !m_resultData.message.empty();
+    }
+
+    std::string AssertionResult::getExpression() const {
+        if( isFalseTest( m_info.resultDisposition ) )
+            return "!(" + m_info.capturedExpression + ")";
+        else
+            return m_info.capturedExpression;
+    }
+
+    std::string AssertionResult::getExpressionInMacro() const {
+        std::string expr;
+        if( m_info.macroName[0] == 0 )
+            expr = m_info.capturedExpression;
+        else {
+            expr.reserve( m_info.macroName.size() + m_info.capturedExpression.size() + 4 );
+            expr += m_info.macroName;
+            expr += "( ";
+            expr += m_info.capturedExpression;
+            expr += " )";
+        }
+        return expr;
+    }
+
+    bool AssertionResult::hasExpandedExpression() const {
+        return hasExpression() && getExpandedExpression() != getExpression();
+    }
+
+    std::string AssertionResult::getExpandedExpression() const {
+        std::string expr = m_resultData.reconstructExpression();
+        return expr.empty()
+                ? getExpression()
+                : expr;
+    }
+
+    std::string AssertionResult::getMessage() const {
+        return m_resultData.message;
+    }
+    SourceLineInfo AssertionResult::getSourceInfo() const {
+        return m_info.lineInfo;
+    }
+
+    StringRef AssertionResult::getTestMacroName() const {
+        return m_info.macroName;
+    }
+
+} // end namespace Catch
+// end catch_assertionresult.cpp
+// start catch_benchmark.cpp
+
+namespace Catch {
+
+    auto BenchmarkLooper::getResolution() -> uint64_t {
+        return getEstimatedClockResolution() * getCurrentContext().getConfig()->benchmarkResolutionMultiple();
+    }
+
+    void BenchmarkLooper::reportStart() {
+        getResultCapture().benchmarkStarting( { m_name } );
+    }
+    auto BenchmarkLooper::needsMoreIterations() -> bool {
+        auto elapsed = m_timer.getElapsedNanoseconds();
+
+        // Exponentially increasing iterations until we're confident in our timer resolution
+        if( elapsed < m_resolution ) {
+            m_iterationsToRun *= 10;
+            return true;
+        }
+
+        getResultCapture().benchmarkEnded( { { m_name }, m_count, elapsed } );
+        return false;
+    }
+
+} // end namespace Catch
+// end catch_benchmark.cpp
+// start catch_capture_matchers.cpp
+
+namespace Catch {
+
+    using StringMatcher = Matchers::Impl::MatcherBase<std::string>;
+
+    // This is the general overload that takes a any string matcher
+    // There is another overload, in catch_assertionhandler.h/.cpp, that only takes a string and infers
+    // the Equals matcher (so the header does not mention matchers)
+    void handleExceptionMatchExpr( AssertionHandler& handler, StringMatcher const& matcher, StringRef const& matcherString  ) {
+        std::string exceptionMessage = Catch::translateActiveException();
+        MatchExpr<std::string, StringMatcher const&> expr( exceptionMessage, matcher, matcherString );
+        handler.handleExpr( expr );
+    }
+
+} // namespace Catch
+// end catch_capture_matchers.cpp
+// start catch_commandline.cpp
+
+// start catch_commandline.h
+
+// start catch_clara.h
+
+// Use Catch's value for console width (store Clara's off to the side, if present)
+#ifdef CLARA_CONFIG_CONSOLE_WIDTH
+#define CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH
+#undef CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH
+#endif
+#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH-1
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wweak-vtables"
+#pragma clang diagnostic ignored "-Wexit-time-destructors"
+#pragma clang diagnostic ignored "-Wshadow"
+#endif
+
+// start clara.hpp
+// Copyright 2017 Two Blue Cubes Ltd. All rights reserved.
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+// See https://github.com/philsquared/Clara for more details
+
+// Clara v1.1.5
+
+
+#ifndef CATCH_CLARA_CONFIG_CONSOLE_WIDTH
+#define CATCH_CLARA_CONFIG_CONSOLE_WIDTH 80
+#endif
+
+#ifndef CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH
+#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CATCH_CLARA_CONFIG_CONSOLE_WIDTH
+#endif
+
+#ifndef CLARA_CONFIG_OPTIONAL_TYPE
+#ifdef __has_include
+#if __has_include(<optional>) && __cplusplus >= 201703L
+#include <optional>
+#define CLARA_CONFIG_OPTIONAL_TYPE std::optional
+#endif
+#endif
+#endif
+
+// ----------- #included from clara_textflow.hpp -----------
+
+// TextFlowCpp
+//
+// A single-header library for wrapping and laying out basic text, by Phil Nash
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+// This project is hosted at https://github.com/philsquared/textflowcpp
+
+
+#include <cassert>
+#include <ostream>
+#include <sstream>
+#include <vector>
+
+#ifndef CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH
+#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH 80
+#endif
+
+namespace Catch {
+namespace clara {
+namespace TextFlow {
+
+inline auto isWhitespace(char c) -> bool {
+	static std::string chars = " \t\n\r";
+	return chars.find(c) != std::string::npos;
+}
+inline auto isBreakableBefore(char c) -> bool {
+	static std::string chars = "[({<|";
+	return chars.find(c) != std::string::npos;
+}
+inline auto isBreakableAfter(char c) -> bool {
+	static std::string chars = "])}>.,:;*+-=&/\\";
+	return chars.find(c) != std::string::npos;
+}
+
+class Columns;
+
+class Column {
+	std::vector<std::string> m_strings;
+	size_t m_width = CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH;
+	size_t m_indent = 0;
+	size_t m_initialIndent = std::string::npos;
+
+public:
+	class iterator {
+		friend Column;
+
+		Column const& m_column;
+		size_t m_stringIndex = 0;
+		size_t m_pos = 0;
+
+		size_t m_len = 0;
+		size_t m_end = 0;
+		bool m_suffix = false;
+
+		iterator(Column const& column, size_t stringIndex)
+			: m_column(column),
+			m_stringIndex(stringIndex) {}
+
+		auto line() const -> std::string const& { return m_column.m_strings[m_stringIndex]; }
+
+		auto isBoundary(size_t at) const -> bool {
+			assert(at > 0);
+			assert(at <= line().size());
+
+			return at == line().size() ||
+				(isWhitespace(line()[at]) && !isWhitespace(line()[at - 1])) ||
+				isBreakableBefore(line()[at]) ||
+				isBreakableAfter(line()[at - 1]);
+		}
+
+		void calcLength() {
+			assert(m_stringIndex < m_column.m_strings.size());
+
+			m_suffix = false;
+			auto width = m_column.m_width - indent();
+			m_end = m_pos;
+			while (m_end < line().size() && line()[m_end] != '\n')
+				++m_end;
+
+			if (m_end < m_pos + width) {
+				m_len = m_end - m_pos;
+			} else {
+				size_t len = width;
+				while (len > 0 && !isBoundary(m_pos + len))
+					--len;
+				while (len > 0 && isWhitespace(line()[m_pos + len - 1]))
+					--len;
+
+				if (len > 0) {
+					m_len = len;
+				} else {
+					m_suffix = true;
+					m_len = width - 1;
+				}
+			}
+		}
+
+		auto indent() const -> size_t {
+			auto initial = m_pos == 0 && m_stringIndex == 0 ? m_column.m_initialIndent : std::string::npos;
+			return initial == std::string::npos ? m_column.m_indent : initial;
+		}
+
+		auto addIndentAndSuffix(std::string const &plain) const -> std::string {
+			return std::string(indent(), ' ') + (m_suffix ? plain + "-" : plain);
+		}
+
+	public:
+		using difference_type = std::ptrdiff_t;
+		using value_type = std::string;
+		using pointer = value_type * ;
+		using reference = value_type & ;
+		using iterator_category = std::forward_iterator_tag;
+
+		explicit iterator(Column const& column) : m_column(column) {
+			assert(m_column.m_width > m_column.m_indent);
+			assert(m_column.m_initialIndent == std::string::npos || m_column.m_width > m_column.m_initialIndent);
+			calcLength();
+			if (m_len == 0)
+				m_stringIndex++; // Empty string
+		}
+
+		auto operator *() const -> std::string {
+			assert(m_stringIndex < m_column.m_strings.size());
+			assert(m_pos <= m_end);
+			return addIndentAndSuffix(line().substr(m_pos, m_len));
+		}
+
+		auto operator ++() -> iterator& {
+			m_pos += m_len;
+			if (m_pos < line().size() && line()[m_pos] == '\n')
+				m_pos += 1;
+			else
+				while (m_pos < line().size() && isWhitespace(line()[m_pos]))
+					++m_pos;
+
+			if (m_pos == line().size()) {
+				m_pos = 0;
+				++m_stringIndex;
+			}
+			if (m_stringIndex < m_column.m_strings.size())
+				calcLength();
+			return *this;
+		}
+		auto operator ++(int) -> iterator {
+			iterator prev(*this);
+			operator++();
+			return prev;
+		}
+
+		auto operator ==(iterator const& other) const -> bool {
+			return
+				m_pos == other.m_pos &&
+				m_stringIndex == other.m_stringIndex &&
+				&m_column == &other.m_column;
+		}
+		auto operator !=(iterator const& other) const -> bool {
+			return !operator==(other);
+		}
+	};
+	using const_iterator = iterator;
+
+	explicit Column(std::string const& text) { m_strings.push_back(text); }
+
+	auto width(size_t newWidth) -> Column& {
+		assert(newWidth > 0);
+		m_width = newWidth;
+		return *this;
+	}
+	auto indent(size_t newIndent) -> Column& {
+		m_indent = newIndent;
+		return *this;
+	}
+	auto initialIndent(size_t newIndent) -> Column& {
+		m_initialIndent = newIndent;
+		return *this;
+	}
+
+	auto width() const -> size_t { return m_width; }
+	auto begin() const -> iterator { return iterator(*this); }
+	auto end() const -> iterator { return { *this, m_strings.size() }; }
+
+	inline friend std::ostream& operator << (std::ostream& os, Column const& col) {
+		bool first = true;
+		for (auto line : col) {
+			if (first)
+				first = false;
+			else
+				os << "\n";
+			os << line;
+		}
+		return os;
+	}
+
+	auto operator + (Column const& other)->Columns;
+
+	auto toString() const -> std::string {
+		std::ostringstream oss;
+		oss << *this;
+		return oss.str();
+	}
+};
+
+class Spacer : public Column {
+
+public:
+	explicit Spacer(size_t spaceWidth) : Column("") {
+		width(spaceWidth);
+	}
+};
+
+class Columns {
+	std::vector<Column> m_columns;
+
+public:
+
+	class iterator {
+		friend Columns;
+		struct EndTag {};
+
+		std::vector<Column> const& m_columns;
+		std::vector<Column::iterator> m_iterators;
+		size_t m_activeIterators;
+
+		iterator(Columns const& columns, EndTag)
+			: m_columns(columns.m_columns),
+			m_activeIterators(0) {
+			m_iterators.reserve(m_columns.size());
+
+			for (auto const& col : m_columns)
+				m_iterators.push_back(col.end());
+		}
+
+	public:
+		using difference_type = std::ptrdiff_t;
+		using value_type = std::string;
+		using pointer = value_type * ;
+		using reference = value_type & ;
+		using iterator_category = std::forward_iterator_tag;
+
+		explicit iterator(Columns const& columns)
+			: m_columns(columns.m_columns),
+			m_activeIterators(m_columns.size()) {
+			m_iterators.reserve(m_columns.size());
+
+			for (auto const& col : m_columns)
+				m_iterators.push_back(col.begin());
+		}
+
+		auto operator ==(iterator const& other) const -> bool {
+			return m_iterators == other.m_iterators;
+		}
+		auto operator !=(iterator const& other) const -> bool {
+			return m_iterators != other.m_iterators;
+		}
+		auto operator *() const -> std::string {
+			std::string row, padding;
+
+			for (size_t i = 0; i < m_columns.size(); ++i) {
+				auto width = m_columns[i].width();
+				if (m_iterators[i] != m_columns[i].end()) {
+					std::string col = *m_iterators[i];
+					row += padding + col;
+					if (col.size() < width)
+						padding = std::string(width - col.size(), ' ');
+					else
+						padding = "";
+				} else {
+					padding += std::string(width, ' ');
+				}
+			}
+			return row;
+		}
+		auto operator ++() -> iterator& {
+			for (size_t i = 0; i < m_columns.size(); ++i) {
+				if (m_iterators[i] != m_columns[i].end())
+					++m_iterators[i];
+			}
+			return *this;
+		}
+		auto operator ++(int) -> iterator {
+			iterator prev(*this);
+			operator++();
+			return prev;
+		}
+	};
+	using const_iterator = iterator;
+
+	auto begin() const -> iterator { return iterator(*this); }
+	auto end() const -> iterator { return { *this, iterator::EndTag() }; }
+
+	auto operator += (Column const& col) -> Columns& {
+		m_columns.push_back(col);
+		return *this;
+	}
+	auto operator + (Column const& col) -> Columns {
+		Columns combined = *this;
+		combined += col;
+		return combined;
+	}
+
+	inline friend std::ostream& operator << (std::ostream& os, Columns const& cols) {
+
+		bool first = true;
+		for (auto line : cols) {
+			if (first)
+				first = false;
+			else
+				os << "\n";
+			os << line;
+		}
+		return os;
+	}
+
+	auto toString() const -> std::string {
+		std::ostringstream oss;
+		oss << *this;
+		return oss.str();
+	}
+};
+
+inline auto Column::operator + (Column const& other) -> Columns {
+	Columns cols;
+	cols += *this;
+	cols += other;
+	return cols;
+}
+}
+
+}
+}
+
+// ----------- end of #include from clara_textflow.hpp -----------
+// ........... back in clara.hpp
+
+#include <string>
+#include <memory>
+#include <set>
+#include <algorithm>
+
+#if !defined(CATCH_PLATFORM_WINDOWS) && ( defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) )
+#define CATCH_PLATFORM_WINDOWS
+#endif
+
+namespace Catch { namespace clara {
+namespace detail {
+
+    // Traits for extracting arg and return type of lambdas (for single argument lambdas)
+    template<typename L>
+    struct UnaryLambdaTraits : UnaryLambdaTraits<decltype( &L::operator() )> {};
+
+    template<typename ClassT, typename ReturnT, typename... Args>
+    struct UnaryLambdaTraits<ReturnT( ClassT::* )( Args... ) const> {
+        static const bool isValid = false;
+    };
+
+    template<typename ClassT, typename ReturnT, typename ArgT>
+    struct UnaryLambdaTraits<ReturnT( ClassT::* )( ArgT ) const> {
+        static const bool isValid = true;
+        using ArgType = typename std::remove_const<typename std::remove_reference<ArgT>::type>::type;
+        using ReturnType = ReturnT;
+    };
+
+    class TokenStream;
+
+    // Transport for raw args (copied from main args, or supplied via init list for testing)
+    class Args {
+        friend TokenStream;
+        std::string m_exeName;
+        std::vector<std::string> m_args;
+
+    public:
+        Args( int argc, char const* const* argv )
+            : m_exeName(argv[0]),
+              m_args(argv + 1, argv + argc) {}
+
+        Args( std::initializer_list<std::string> args )
+        :   m_exeName( *args.begin() ),
+            m_args( args.begin()+1, args.end() )
+        {}
+
+        auto exeName() const -> std::string {
+            return m_exeName;
+        }
+    };
+
+    // Wraps a token coming from a token stream. These may not directly correspond to strings as a single string
+    // may encode an option + its argument if the : or = form is used
+    enum class TokenType {
+        Option, Argument
+    };
+    struct Token {
+        TokenType type;
+        std::string token;
+    };
+
+    inline auto isOptPrefix( char c ) -> bool {
+        return c == '-'
+#ifdef CATCH_PLATFORM_WINDOWS
+            || c == '/'
+#endif
+        ;
+    }
+
+    // Abstracts iterators into args as a stream of tokens, with option arguments uniformly handled
+    class TokenStream {
+        using Iterator = std::vector<std::string>::const_iterator;
+        Iterator it;
+        Iterator itEnd;
+        std::vector<Token> m_tokenBuffer;
+
+        void loadBuffer() {
+            m_tokenBuffer.resize( 0 );
+
+            // Skip any empty strings
+            while( it != itEnd && it->empty() )
+                ++it;
+
+            if( it != itEnd ) {
+                auto const &next = *it;
+                if( isOptPrefix( next[0] ) ) {
+                    auto delimiterPos = next.find_first_of( " :=" );
+                    if( delimiterPos != std::string::npos ) {
+                        m_tokenBuffer.push_back( { TokenType::Option, next.substr( 0, delimiterPos ) } );
+                        m_tokenBuffer.push_back( { TokenType::Argument, next.substr( delimiterPos + 1 ) } );
+                    } else {
+                        if( next[1] != '-' && next.size() > 2 ) {
+                            std::string opt = "- ";
+                            for( size_t i = 1; i < next.size(); ++i ) {
+                                opt[1] = next[i];
+                                m_tokenBuffer.push_back( { TokenType::Option, opt } );
+                            }
+                        } else {
+                            m_tokenBuffer.push_back( { TokenType::Option, next } );
+                        }
+                    }
+                } else {
+                    m_tokenBuffer.push_back( { TokenType::Argument, next } );
+                }
+            }
+        }
+
+    public:
+        explicit TokenStream( Args const &args ) : TokenStream( args.m_args.begin(), args.m_args.end() ) {}
+
+        TokenStream( Iterator it, Iterator itEnd ) : it( it ), itEnd( itEnd ) {
+            loadBuffer();
+        }
+
+        explicit operator bool() const {
+            return !m_tokenBuffer.empty() || it != itEnd;
+        }
+
+        auto count() const -> size_t { return m_tokenBuffer.size() + (itEnd - it); }
+
+        auto operator*() const -> Token {
+            assert( !m_tokenBuffer.empty() );
+            return m_tokenBuffer.front();
+        }
+
+        auto operator->() const -> Token const * {
+            assert( !m_tokenBuffer.empty() );
+            return &m_tokenBuffer.front();
+        }
+
+        auto operator++() -> TokenStream & {
+            if( m_tokenBuffer.size() >= 2 ) {
+                m_tokenBuffer.erase( m_tokenBuffer.begin() );
+            } else {
+                if( it != itEnd )
+                    ++it;
+                loadBuffer();
+            }
+            return *this;
+        }
+    };
+
+    class ResultBase {
+    public:
+        enum Type {
+            Ok, LogicError, RuntimeError
+        };
+
+    protected:
+        ResultBase( Type type ) : m_type( type ) {}
+        virtual ~ResultBase() = default;
+
+        virtual void enforceOk() const = 0;
+
+        Type m_type;
+    };
+
+    template<typename T>
+    class ResultValueBase : public ResultBase {
+    public:
+        auto value() const -> T const & {
+            enforceOk();
+            return m_value;
+        }
+
+    protected:
+        ResultValueBase( Type type ) : ResultBase( type ) {}
+
+        ResultValueBase( ResultValueBase const &other ) : ResultBase( other ) {
+            if( m_type == ResultBase::Ok )
+                new( &m_value ) T( other.m_value );
+        }
+
+        ResultValueBase( Type, T const &value ) : ResultBase( Ok ) {
+            new( &m_value ) T( value );
+        }
+
+        auto operator=( ResultValueBase const &other ) -> ResultValueBase & {
+            if( m_type == ResultBase::Ok )
+                m_value.~T();
+            ResultBase::operator=(other);
+            if( m_type == ResultBase::Ok )
+                new( &m_value ) T( other.m_value );
+            return *this;
+        }
+
+        ~ResultValueBase() override {
+            if( m_type == Ok )
+                m_value.~T();
+        }
+
+        union {
+            T m_value;
+        };
+    };
+
+    template<>
+    class ResultValueBase<void> : public ResultBase {
+    protected:
+        using ResultBase::ResultBase;
+    };
+
+    template<typename T = void>
+    class BasicResult : public ResultValueBase<T> {
+    public:
+        template<typename U>
+        explicit BasicResult( BasicResult<U> const &other )
+        :   ResultValueBase<T>( other.type() ),
+            m_errorMessage( other.errorMessage() )
+        {
+            assert( type() != ResultBase::Ok );
+        }
+
+        template<typename U>
+        static auto ok( U const &value ) -> BasicResult { return { ResultBase::Ok, value }; }
+        static auto ok() -> BasicResult { return { ResultBase::Ok }; }
+        static auto logicError( std::string const &message ) -> BasicResult { return { ResultBase::LogicError, message }; }
+        static auto runtimeError( std::string const &message ) -> BasicResult { return { ResultBase::RuntimeError, message }; }
+
+        explicit operator bool() const { return m_type == ResultBase::Ok; }
+        auto type() const -> ResultBase::Type { return m_type; }
+        auto errorMessage() const -> std::string { return m_errorMessage; }
+
+    protected:
+        void enforceOk() const override {
+
+            // Errors shouldn't reach this point, but if they do
+            // the actual error message will be in m_errorMessage
+            assert( m_type != ResultBase::LogicError );
+            assert( m_type != ResultBase::RuntimeError );
+            if( m_type != ResultBase::Ok )
+                std::abort();
+        }
+
+        std::string m_errorMessage; // Only populated if resultType is an error
+
+        BasicResult( ResultBase::Type type, std::string const &message )
+        :   ResultValueBase<T>(type),
+            m_errorMessage(message)
+        {
+            assert( m_type != ResultBase::Ok );
+        }
+
+        using ResultValueBase<T>::ResultValueBase;
+        using ResultBase::m_type;
+    };
+
+    enum class ParseResultType {
+        Matched, NoMatch, ShortCircuitAll, ShortCircuitSame
+    };
+
+    class ParseState {
+    public:
+
+        ParseState( ParseResultType type, TokenStream const &remainingTokens )
+        : m_type(type),
+          m_remainingTokens( remainingTokens )
+        {}
+
+        auto type() const -> ParseResultType { return m_type; }
+        auto remainingTokens() const -> TokenStream { return m_remainingTokens; }
+
+    private:
+        ParseResultType m_type;
+        TokenStream m_remainingTokens;
+    };
+
+    using Result = BasicResult<void>;
+    using ParserResult = BasicResult<ParseResultType>;
+    using InternalParseResult = BasicResult<ParseState>;
+
+    struct HelpColumns {
+        std::string left;
+        std::string right;
+    };
+
+    template<typename T>
+    inline auto convertInto( std::string const &source, T& target ) -> ParserResult {
+        std::stringstream ss;
+        ss << source;
+        ss >> target;
+        if( ss.fail() )
+            return ParserResult::runtimeError( "Unable to convert '" + source + "' to destination type" );
+        else
+            return ParserResult::ok( ParseResultType::Matched );
+    }
+    inline auto convertInto( std::string const &source, std::string& target ) -> ParserResult {
+        target = source;
+        return ParserResult::ok( ParseResultType::Matched );
+    }
+    inline auto convertInto( std::string const &source, bool &target ) -> ParserResult {
+        std::string srcLC = source;
+        std::transform( srcLC.begin(), srcLC.end(), srcLC.begin(), []( char c ) { return static_cast<char>( ::tolower(c) ); } );
+        if (srcLC == "y" || srcLC == "1" || srcLC == "true" || srcLC == "yes" || srcLC == "on")
+            target = true;
+        else if (srcLC == "n" || srcLC == "0" || srcLC == "false" || srcLC == "no" || srcLC == "off")
+            target = false;
+        else
+            return ParserResult::runtimeError( "Expected a boolean value but did not recognise: '" + source + "'" );
+        return ParserResult::ok( ParseResultType::Matched );
+    }
+#ifdef CLARA_CONFIG_OPTIONAL_TYPE
+    template<typename T>
+    inline auto convertInto( std::string const &source, CLARA_CONFIG_OPTIONAL_TYPE<T>& target ) -> ParserResult {
+        T temp;
+        auto result = convertInto( source, temp );
+        if( result )
+            target = std::move(temp);
+        return result;
+    }
+#endif // CLARA_CONFIG_OPTIONAL_TYPE
+
+    struct NonCopyable {
+        NonCopyable() = default;
+        NonCopyable( NonCopyable const & ) = delete;
+        NonCopyable( NonCopyable && ) = delete;
+        NonCopyable &operator=( NonCopyable const & ) = delete;
+        NonCopyable &operator=( NonCopyable && ) = delete;
+    };
+
+    struct BoundRef : NonCopyable {
+        virtual ~BoundRef() = default;
+        virtual auto isContainer() const -> bool { return false; }
+        virtual auto isFlag() const -> bool { return false; }
+    };
+    struct BoundValueRefBase : BoundRef {
+        virtual auto setValue( std::string const &arg ) -> ParserResult = 0;
+    };
+    struct BoundFlagRefBase : BoundRef {
+        virtual auto setFlag( bool flag ) -> ParserResult = 0;
+        virtual auto isFlag() const -> bool { return true; }
+    };
+
+    template<typename T>
+    struct BoundValueRef : BoundValueRefBase {
+        T &m_ref;
+
+        explicit BoundValueRef( T &ref ) : m_ref( ref ) {}
+
+        auto setValue( std::string const &arg ) -> ParserResult override {
+            return convertInto( arg, m_ref );
+        }
+    };
+
+    template<typename T>
+    struct BoundValueRef<std::vector<T>> : BoundValueRefBase {
+        std::vector<T> &m_ref;
+
+        explicit BoundValueRef( std::vector<T> &ref ) : m_ref( ref ) {}
+
+        auto isContainer() const -> bool override { return true; }
+
+        auto setValue( std::string const &arg ) -> ParserResult override {
+            T temp;
+            auto result = convertInto( arg, temp );
+            if( result )
+                m_ref.push_back( temp );
+            return result;
+        }
+    };
+
+    struct BoundFlagRef : BoundFlagRefBase {
+        bool &m_ref;
+
+        explicit BoundFlagRef( bool &ref ) : m_ref( ref ) {}
+
+        auto setFlag( bool flag ) -> ParserResult override {
+            m_ref = flag;
+            return ParserResult::ok( ParseResultType::Matched );
+        }
+    };
+
+    template<typename ReturnType>
+    struct LambdaInvoker {
+        static_assert( std::is_same<ReturnType, ParserResult>::value, "Lambda must return void or clara::ParserResult" );
+
+        template<typename L, typename ArgType>
+        static auto invoke( L const &lambda, ArgType const &arg ) -> ParserResult {
+            return lambda( arg );
+        }
+    };
+
+    template<>
+    struct LambdaInvoker<void> {
+        template<typename L, typename ArgType>
+        static auto invoke( L const &lambda, ArgType const &arg ) -> ParserResult {
+            lambda( arg );
+            return ParserResult::ok( ParseResultType::Matched );
+        }
+    };
+
+    template<typename ArgType, typename L>
+    inline auto invokeLambda( L const &lambda, std::string const &arg ) -> ParserResult {
+        ArgType temp{};
+        auto result = convertInto( arg, temp );
+        return !result
+           ? result
+           : LambdaInvoker<typename UnaryLambdaTraits<L>::ReturnType>::invoke( lambda, temp );
+    }
+
+    template<typename L>
+    struct BoundLambda : BoundValueRefBase {
+        L m_lambda;
+
+        static_assert( UnaryLambdaTraits<L>::isValid, "Supplied lambda must take exactly one argument" );
+        explicit BoundLambda( L const &lambda ) : m_lambda( lambda ) {}
+
+        auto setValue( std::string const &arg ) -> ParserResult override {
+            return invokeLambda<typename UnaryLambdaTraits<L>::ArgType>( m_lambda, arg );
+        }
+    };
+
+    template<typename L>
+    struct BoundFlagLambda : BoundFlagRefBase {
+        L m_lambda;
+
+        static_assert( UnaryLambdaTraits<L>::isValid, "Supplied lambda must take exactly one argument" );
+        static_assert( std::is_same<typename UnaryLambdaTraits<L>::ArgType, bool>::value, "flags must be boolean" );
+
+        explicit BoundFlagLambda( L const &lambda ) : m_lambda( lambda ) {}
+
+        auto setFlag( bool flag ) -> ParserResult override {
+            return LambdaInvoker<typename UnaryLambdaTraits<L>::ReturnType>::invoke( m_lambda, flag );
+        }
+    };
+
+    enum class Optionality { Optional, Required };
+
+    struct Parser;
+
+    class ParserBase {
+    public:
+        virtual ~ParserBase() = default;
+        virtual auto validate() const -> Result { return Result::ok(); }
+        virtual auto parse( std::string const& exeName, TokenStream const &tokens) const -> InternalParseResult  = 0;
+        virtual auto cardinality() const -> size_t { return 1; }
+
+        auto parse( Args const &args ) const -> InternalParseResult {
+            return parse( args.exeName(), TokenStream( args ) );
+        }
+    };
+
+    template<typename DerivedT>
+    class ComposableParserImpl : public ParserBase {
+    public:
+        template<typename T>
+        auto operator|( T const &other ) const -> Parser;
+
+		template<typename T>
+        auto operator+( T const &other ) const -> Parser;
+    };
+
+    // Common code and state for Args and Opts
+    template<typename DerivedT>
+    class ParserRefImpl : public ComposableParserImpl<DerivedT> {
+    protected:
+        Optionality m_optionality = Optionality::Optional;
+        std::shared_ptr<BoundRef> m_ref;
+        std::string m_hint;
+        std::string m_description;
+
+        explicit ParserRefImpl( std::shared_ptr<BoundRef> const &ref ) : m_ref( ref ) {}
+
+    public:
+        template<typename T>
+        ParserRefImpl( T &ref, std::string const &hint )
+        :   m_ref( std::make_shared<BoundValueRef<T>>( ref ) ),
+            m_hint( hint )
+        {}
+
+        template<typename LambdaT>
+        ParserRefImpl( LambdaT const &ref, std::string const &hint )
+        :   m_ref( std::make_shared<BoundLambda<LambdaT>>( ref ) ),
+            m_hint(hint)
+        {}
+
+        auto operator()( std::string const &description ) -> DerivedT & {
+            m_description = description;
+            return static_cast<DerivedT &>( *this );
+        }
+
+        auto optional() -> DerivedT & {
+            m_optionality = Optionality::Optional;
+            return static_cast<DerivedT &>( *this );
+        };
+
+        auto required() -> DerivedT & {
+            m_optionality = Optionality::Required;
+            return static_cast<DerivedT &>( *this );
+        };
+
+        auto isOptional() const -> bool {
+            return m_optionality == Optionality::Optional;
+        }
+
+        auto cardinality() const -> size_t override {
+            if( m_ref->isContainer() )
+                return 0;
+            else
+                return 1;
+        }
+
+        auto hint() const -> std::string { return m_hint; }
+    };
+
+    class ExeName : public ComposableParserImpl<ExeName> {
+        std::shared_ptr<std::string> m_name;
+        std::shared_ptr<BoundValueRefBase> m_ref;
+
+        template<typename LambdaT>
+        static auto makeRef(LambdaT const &lambda) -> std::shared_ptr<BoundValueRefBase> {
+            return std::make_shared<BoundLambda<LambdaT>>( lambda) ;
+        }
+
+    public:
+        ExeName() : m_name( std::make_shared<std::string>( "<executable>" ) ) {}
+
+        explicit ExeName( std::string &ref ) : ExeName() {
+            m_ref = std::make_shared<BoundValueRef<std::string>>( ref );
+        }
+
+        template<typename LambdaT>
+        explicit ExeName( LambdaT const& lambda ) : ExeName() {
+            m_ref = std::make_shared<BoundLambda<LambdaT>>( lambda );
+        }
+
+        // The exe name is not parsed out of the normal tokens, but is handled specially
+        auto parse( std::string const&, TokenStream const &tokens ) const -> InternalParseResult override {
+            return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, tokens ) );
+        }
+
+        auto name() const -> std::string { return *m_name; }
+        auto set( std::string const& newName ) -> ParserResult {
+
+            auto lastSlash = newName.find_last_of( "\\/" );
+            auto filename = ( lastSlash == std::string::npos )
+                    ? newName
+                    : newName.substr( lastSlash+1 );
+
+            *m_name = filename;
+            if( m_ref )
+                return m_ref->setValue( filename );
+            else
+                return ParserResult::ok( ParseResultType::Matched );
+        }
+    };
+
+    class Arg : public ParserRefImpl<Arg> {
+    public:
+        using ParserRefImpl::ParserRefImpl;
+
+        auto parse( std::string const &, TokenStream const &tokens ) const -> InternalParseResult override {
+            auto validationResult = validate();
+            if( !validationResult )
+                return InternalParseResult( validationResult );
+
+            auto remainingTokens = tokens;
+            auto const &token = *remainingTokens;
+            if( token.type != TokenType::Argument )
+                return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, remainingTokens ) );
+
+            assert( !m_ref->isFlag() );
+            auto valueRef = static_cast<detail::BoundValueRefBase*>( m_ref.get() );
+
+            auto result = valueRef->setValue( remainingTokens->token );
+            if( !result )
+                return InternalParseResult( result );
+            else
+                return InternalParseResult::ok( ParseState( ParseResultType::Matched, ++remainingTokens ) );
+        }
+    };
+
+    inline auto normaliseOpt( std::string const &optName ) -> std::string {
+#ifdef CATCH_PLATFORM_WINDOWS
+        if( optName[0] == '/' )
+            return "-" + optName.substr( 1 );
+        else
+#endif
+            return optName;
+    }
+
+    class Opt : public ParserRefImpl<Opt> {
+    protected:
+        std::vector<std::string> m_optNames;
+
+    public:
+        template<typename LambdaT>
+        explicit Opt( LambdaT const &ref ) : ParserRefImpl( std::make_shared<BoundFlagLambda<LambdaT>>( ref ) ) {}
+
+        explicit Opt( bool &ref ) : ParserRefImpl( std::make_shared<BoundFlagRef>( ref ) ) {}
+
+        template<typename LambdaT>
+        Opt( LambdaT const &ref, std::string const &hint ) : ParserRefImpl( ref, hint ) {}
+
+        template<typename T>
+        Opt( T &ref, std::string const &hint ) : ParserRefImpl( ref, hint ) {}
+
+        auto operator[]( std::string const &optName ) -> Opt & {
+            m_optNames.push_back( optName );
+            return *this;
+        }
+
+        auto getHelpColumns() const -> std::vector<HelpColumns> {
+            std::ostringstream oss;
+            bool first = true;
+            for( auto const &opt : m_optNames ) {
+                if (first)
+                    first = false;
+                else
+                    oss << ", ";
+                oss << opt;
+            }
+            if( !m_hint.empty() )
+                oss << " <" << m_hint << ">";
+            return { { oss.str(), m_description } };
+        }
+
+        auto isMatch( std::string const &optToken ) const -> bool {
+            auto normalisedToken = normaliseOpt( optToken );
+            for( auto const &name : m_optNames ) {
+                if( normaliseOpt( name ) == normalisedToken )
+                    return true;
+            }
+            return false;
+        }
+
+        using ParserBase::parse;
+
+        auto parse( std::string const&, TokenStream const &tokens ) const -> InternalParseResult override {
+            auto validationResult = validate();
+            if( !validationResult )
+                return InternalParseResult( validationResult );
+
+            auto remainingTokens = tokens;
+            if( remainingTokens && remainingTokens->type == TokenType::Option ) {
+                auto const &token = *remainingTokens;
+                if( isMatch(token.token ) ) {
+                    if( m_ref->isFlag() ) {
+                        auto flagRef = static_cast<detail::BoundFlagRefBase*>( m_ref.get() );
+                        auto result = flagRef->setFlag( true );
+                        if( !result )
+                            return InternalParseResult( result );
+                        if( result.value() == ParseResultType::ShortCircuitAll )
+                            return InternalParseResult::ok( ParseState( result.value(), remainingTokens ) );
+                    } else {
+                        auto valueRef = static_cast<detail::BoundValueRefBase*>( m_ref.get() );
+                        ++remainingTokens;
+                        if( !remainingTokens )
+                            return InternalParseResult::runtimeError( "Expected argument following " + token.token );
+                        auto const &argToken = *remainingTokens;
+                        if( argToken.type != TokenType::Argument )
+                            return InternalParseResult::runtimeError( "Expected argument following " + token.token );
+                        auto result = valueRef->setValue( argToken.token );
+                        if( !result )
+                            return InternalParseResult( result );
+                        if( result.value() == ParseResultType::ShortCircuitAll )
+                            return InternalParseResult::ok( ParseState( result.value(), remainingTokens ) );
+                    }
+                    return InternalParseResult::ok( ParseState( ParseResultType::Matched, ++remainingTokens ) );
+                }
+            }
+            return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, remainingTokens ) );
+        }
+
+        auto validate() const -> Result override {
+            if( m_optNames.empty() )
+                return Result::logicError( "No options supplied to Opt" );
+            for( auto const &name : m_optNames ) {
+                if( name.empty() )
+                    return Result::logicError( "Option name cannot be empty" );
+#ifdef CATCH_PLATFORM_WINDOWS
+                if( name[0] != '-' && name[0] != '/' )
+                    return Result::logicError( "Option name must begin with '-' or '/'" );
+#else
+                if( name[0] != '-' )
+                    return Result::logicError( "Option name must begin with '-'" );
+#endif
+            }
+            return ParserRefImpl::validate();
+        }
+    };
+
+    struct Help : Opt {
+        Help( bool &showHelpFlag )
+        :   Opt([&]( bool flag ) {
+                showHelpFlag = flag;
+                return ParserResult::ok( ParseResultType::ShortCircuitAll );
+            })
+        {
+            static_cast<Opt &>( *this )
+                    ("display usage information")
+                    ["-?"]["-h"]["--help"]
+                    .optional();
+        }
+    };
+
+    struct Parser : ParserBase {
+
+        mutable ExeName m_exeName;
+        std::vector<Opt> m_options;
+        std::vector<Arg> m_args;
+
+        auto operator|=( ExeName const &exeName ) -> Parser & {
+            m_exeName = exeName;
+            return *this;
+        }
+
+        auto operator|=( Arg const &arg ) -> Parser & {
+            m_args.push_back(arg);
+            return *this;
+        }
+
+        auto operator|=( Opt const &opt ) -> Parser & {
+            m_options.push_back(opt);
+            return *this;
+        }
+
+        auto operator|=( Parser const &other ) -> Parser & {
+            m_options.insert(m_options.end(), other.m_options.begin(), other.m_options.end());
+            m_args.insert(m_args.end(), other.m_args.begin(), other.m_args.end());
+            return *this;
+        }
+
+        template<typename T>
+        auto operator|( T const &other ) const -> Parser {
+            return Parser( *this ) |= other;
+        }
+
+        // Forward deprecated interface with '+' instead of '|'
+        template<typename T>
+        auto operator+=( T const &other ) -> Parser & { return operator|=( other ); }
+        template<typename T>
+        auto operator+( T const &other ) const -> Parser { return operator|( other ); }
+
+        auto getHelpColumns() const -> std::vector<HelpColumns> {
+            std::vector<HelpColumns> cols;
+            for (auto const &o : m_options) {
+                auto childCols = o.getHelpColumns();
+                cols.insert( cols.end(), childCols.begin(), childCols.end() );
+            }
+            return cols;
+        }
+
+        void writeToStream( std::ostream &os ) const {
+            if (!m_exeName.name().empty()) {
+                os << "usage:\n" << "  " << m_exeName.name() << " ";
+                bool required = true, first = true;
+                for( auto const &arg : m_args ) {
+                    if (first)
+                        first = false;
+                    else
+                        os << " ";
+                    if( arg.isOptional() && required ) {
+                        os << "[";
+                        required = false;
+                    }
+                    os << "<" << arg.hint() << ">";
+                    if( arg.cardinality() == 0 )
+                        os << " ... ";
+                }
+                if( !required )
+                    os << "]";
+                if( !m_options.empty() )
+                    os << " options";
+                os << "\n\nwhere options are:" << std::endl;
+            }
+
+            auto rows = getHelpColumns();
+            size_t consoleWidth = CATCH_CLARA_CONFIG_CONSOLE_WIDTH;
+            size_t optWidth = 0;
+            for( auto const &cols : rows )
+                optWidth = (std::max)(optWidth, cols.left.size() + 2);
+
+            optWidth = (std::min)(optWidth, consoleWidth/2);
+
+            for( auto const &cols : rows ) {
+                auto row =
+                        TextFlow::Column( cols.left ).width( optWidth ).indent( 2 ) +
+                        TextFlow::Spacer(4) +
+                        TextFlow::Column( cols.right ).width( consoleWidth - 7 - optWidth );
+                os << row << std::endl;
+            }
+        }
+
+        friend auto operator<<( std::ostream &os, Parser const &parser ) -> std::ostream& {
+            parser.writeToStream( os );
+            return os;
+        }
+
+        auto validate() const -> Result override {
+            for( auto const &opt : m_options ) {
+                auto result = opt.validate();
+                if( !result )
+                    return result;
+            }
+            for( auto const &arg : m_args ) {
+                auto result = arg.validate();
+                if( !result )
+                    return result;
+            }
+            return Result::ok();
+        }
+
+        using ParserBase::parse;
+
+        auto parse( std::string const& exeName, TokenStream const &tokens ) const -> InternalParseResult override {
+
+            struct ParserInfo {
+                ParserBase const* parser = nullptr;
+                size_t count = 0;
+            };
+            const size_t totalParsers = m_options.size() + m_args.size();
+            assert( totalParsers < 512 );
+            // ParserInfo parseInfos[totalParsers]; // <-- this is what we really want to do
+            ParserInfo parseInfos[512];
+
+            {
+                size_t i = 0;
+                for (auto const &opt : m_options) parseInfos[i++].parser = &opt;
+                for (auto const &arg : m_args) parseInfos[i++].parser = &arg;
+            }
+
+            m_exeName.set( exeName );
+
+            auto result = InternalParseResult::ok( ParseState( ParseResultType::NoMatch, tokens ) );
+            while( result.value().remainingTokens() ) {
+                bool tokenParsed = false;
+
+                for( size_t i = 0; i < totalParsers; ++i ) {
+                    auto&  parseInfo = parseInfos[i];
+                    if( parseInfo.parser->cardinality() == 0 || parseInfo.count < parseInfo.parser->cardinality() ) {
+                        result = parseInfo.parser->parse(exeName, result.value().remainingTokens());
+                        if (!result)
+                            return result;
+                        if (result.value().type() != ParseResultType::NoMatch) {
+                            tokenParsed = true;
+                            ++parseInfo.count;
+                            break;
+                        }
+                    }
+                }
+
+                if( result.value().type() == ParseResultType::ShortCircuitAll )
+                    return result;
+                if( !tokenParsed )
+                    return InternalParseResult::runtimeError( "Unrecognised token: " + result.value().remainingTokens()->token );
+            }
+            // !TBD Check missing required options
+            return result;
+        }
+    };
+
+    template<typename DerivedT>
+    template<typename T>
+    auto ComposableParserImpl<DerivedT>::operator|( T const &other ) const -> Parser {
+        return Parser() | static_cast<DerivedT const &>( *this ) | other;
+    }
+} // namespace detail
+
+// A Combined parser
+using detail::Parser;
+
+// A parser for options
+using detail::Opt;
+
+// A parser for arguments
+using detail::Arg;
+
+// Wrapper for argc, argv from main()
+using detail::Args;
+
+// Specifies the name of the executable
+using detail::ExeName;
+
+// Convenience wrapper for option parser that specifies the help option
+using detail::Help;
+
+// enum of result types from a parse
+using detail::ParseResultType;
+
+// Result type for parser operation
+using detail::ParserResult;
+
+}} // namespace Catch::clara
+
+// end clara.hpp
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+// Restore Clara's value for console width, if present
+#ifdef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH
+#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH
+#undef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH
+#endif
+
+// end catch_clara.h
+namespace Catch {
+
+    clara::Parser makeCommandLineParser( ConfigData& config );
+
+} // end namespace Catch
+
+// end catch_commandline.h
+#include <fstream>
+#include <ctime>
+
+namespace Catch {
+
+    clara::Parser makeCommandLineParser( ConfigData& config ) {
+
+        using namespace clara;
+
+        auto const setWarning = [&]( std::string const& warning ) {
+                auto warningSet = [&]() {
+                    if( warning == "NoAssertions" )
+                        return WarnAbout::NoAssertions;
+
+                    if ( warning == "NoTests" )
+                        return WarnAbout::NoTests;
+
+                    return WarnAbout::Nothing;
+                }();
+
+                if (warningSet == WarnAbout::Nothing)
+                    return ParserResult::runtimeError( "Unrecognised warning: '" + warning + "'" );
+                config.warnings = static_cast<WarnAbout::What>( config.warnings | warningSet );
+                return ParserResult::ok( ParseResultType::Matched );
+            };
+        auto const loadTestNamesFromFile = [&]( std::string const& filename ) {
+                std::ifstream f( filename.c_str() );
+                if( !f.is_open() )
+                    return ParserResult::runtimeError( "Unable to load input file: '" + filename + "'" );
+
+                std::string line;
+                while( std::getline( f, line ) ) {
+                    line = trim(line);
+                    if( !line.empty() && !startsWith( line, '#' ) ) {
+                        if( !startsWith( line, '"' ) )
+                            line = '"' + line + '"';
+                        config.testsOrTags.push_back( line + ',' );
+                    }
+                }
+                return ParserResult::ok( ParseResultType::Matched );
+            };
+        auto const setTestOrder = [&]( std::string const& order ) {
+                if( startsWith( "declared", order ) )
+                    config.runOrder = RunTests::InDeclarationOrder;
+                else if( startsWith( "lexical", order ) )
+                    config.runOrder = RunTests::InLexicographicalOrder;
+                else if( startsWith( "random", order ) )
+                    config.runOrder = RunTests::InRandomOrder;
+                else
+                    return clara::ParserResult::runtimeError( "Unrecognised ordering: '" + order + "'" );
+                return ParserResult::ok( ParseResultType::Matched );
+            };
+        auto const setRngSeed = [&]( std::string const& seed ) {
+                if( seed != "time" )
+                    return clara::detail::convertInto( seed, config.rngSeed );
+                config.rngSeed = static_cast<unsigned int>( std::time(nullptr) );
+                return ParserResult::ok( ParseResultType::Matched );
+            };
+        auto const setColourUsage = [&]( std::string const& useColour ) {
+                    auto mode = toLower( useColour );
+
+                    if( mode == "yes" )
+                        config.useColour = UseColour::Yes;
+                    else if( mode == "no" )
+                        config.useColour = UseColour::No;
+                    else if( mode == "auto" )
+                        config.useColour = UseColour::Auto;
+                    else
+                        return ParserResult::runtimeError( "colour mode must be one of: auto, yes or no. '" + useColour + "' not recognised" );
+                return ParserResult::ok( ParseResultType::Matched );
+            };
+        auto const setWaitForKeypress = [&]( std::string const& keypress ) {
+                auto keypressLc = toLower( keypress );
+                if( keypressLc == "start" )
+                    config.waitForKeypress = WaitForKeypress::BeforeStart;
+                else if( keypressLc == "exit" )
+                    config.waitForKeypress = WaitForKeypress::BeforeExit;
+                else if( keypressLc == "both" )
+                    config.waitForKeypress = WaitForKeypress::BeforeStartAndExit;
+                else
+                    return ParserResult::runtimeError( "keypress argument must be one of: start, exit or both. '" + keypress + "' not recognised" );
+            return ParserResult::ok( ParseResultType::Matched );
+            };
+        auto const setVerbosity = [&]( std::string const& verbosity ) {
+            auto lcVerbosity = toLower( verbosity );
+            if( lcVerbosity == "quiet" )
+                config.verbosity = Verbosity::Quiet;
+            else if( lcVerbosity == "normal" )
+                config.verbosity = Verbosity::Normal;
+            else if( lcVerbosity == "high" )
+                config.verbosity = Verbosity::High;
+            else
+                return ParserResult::runtimeError( "Unrecognised verbosity, '" + verbosity + "'" );
+            return ParserResult::ok( ParseResultType::Matched );
+        };
+        auto const setReporter = [&]( std::string const& reporter ) {
+            IReporterRegistry::FactoryMap const& factories = getRegistryHub().getReporterRegistry().getFactories();
+
+            auto lcReporter = toLower( reporter );
+            auto result = factories.find( lcReporter );
+
+            if( factories.end() != result )
+                config.reporterName = lcReporter;
+            else
+                return ParserResult::runtimeError( "Unrecognized reporter, '" + reporter + "'. Check available with --list-reporters" );
+            return ParserResult::ok( ParseResultType::Matched );
+        };
+
+        auto cli
+            = ExeName( config.processName )
+            | Help( config.showHelp )
+            | Opt( config.listTests )
+                ["-l"]["--list-tests"]
+                ( "list all/matching test cases" )
+            | Opt( config.listTags )
+                ["-t"]["--list-tags"]
+                ( "list all/matching tags" )
+            | Opt( config.showSuccessfulTests )
+                ["-s"]["--success"]
+                ( "include successful tests in output" )
+            | Opt( config.shouldDebugBreak )
+                ["-b"]["--break"]
+                ( "break into debugger on failure" )
+            | Opt( config.noThrow )
+                ["-e"]["--nothrow"]
+                ( "skip exception tests" )
+            | Opt( config.showInvisibles )
+                ["-i"]["--invisibles"]
+                ( "show invisibles (tabs, newlines)" )
+            | Opt( config.outputFilename, "filename" )
+                ["-o"]["--out"]
+                ( "output filename" )
+            | Opt( setReporter, "name" )
+                ["-r"]["--reporter"]
+                ( "reporter to use (defaults to console)" )
+            | Opt( config.name, "name" )
+                ["-n"]["--name"]
+                ( "suite name" )
+            | Opt( [&]( bool ){ config.abortAfter = 1; } )
+                ["-a"]["--abort"]
+                ( "abort at first failure" )
+            | Opt( [&]( int x ){ config.abortAfter = x; }, "no. failures" )
+                ["-x"]["--abortx"]
+                ( "abort after x failures" )
+            | Opt( setWarning, "warning name" )
+                ["-w"]["--warn"]
+                ( "enable warnings" )
+            | Opt( [&]( bool flag ) { config.showDurations = flag ? ShowDurations::Always : ShowDurations::Never; }, "yes|no" )
+                ["-d"]["--durations"]
+                ( "show test durations" )
+            | Opt( loadTestNamesFromFile, "filename" )
+                ["-f"]["--input-file"]
+                ( "load test names to run from a file" )
+            | Opt( config.filenamesAsTags )
+                ["-#"]["--filenames-as-tags"]
+                ( "adds a tag for the filename" )
+            | Opt( config.sectionsToRun, "section name" )
+                ["-c"]["--section"]
+                ( "specify section to run" )
+            | Opt( setVerbosity, "quiet|normal|high" )
+                ["-v"]["--verbosity"]
+                ( "set output verbosity" )
+            | Opt( config.listTestNamesOnly )
+                ["--list-test-names-only"]
+                ( "list all/matching test cases names only" )
+            | Opt( config.listReporters )
+                ["--list-reporters"]
+                ( "list all reporters" )
+            | Opt( setTestOrder, "decl|lex|rand" )
+                ["--order"]
+                ( "test case order (defaults to decl)" )
+            | Opt( setRngSeed, "'time'|number" )
+                ["--rng-seed"]
+                ( "set a specific seed for random numbers" )
+            | Opt( setColourUsage, "yes|no" )
+                ["--use-colour"]
+                ( "should output be colourised" )
+            | Opt( config.libIdentify )
+                ["--libidentify"]
+                ( "report name and version according to libidentify standard" )
+            | Opt( setWaitForKeypress, "start|exit|both" )
+                ["--wait-for-keypress"]
+                ( "waits for a keypress before exiting" )
+            | Opt( config.benchmarkResolutionMultiple, "multiplier" )
+                ["--benchmark-resolution-multiple"]
+                ( "multiple of clock resolution to run benchmarks" )
+
+            | Arg( config.testsOrTags, "test name|pattern|tags" )
+                ( "which test or tests to use" );
+
+        return cli;
+    }
+
+} // end namespace Catch
+// end catch_commandline.cpp
+// start catch_common.cpp
+
+#include <cstring>
+#include <ostream>
+
+namespace Catch {
+
+    bool SourceLineInfo::empty() const noexcept {
+        return file[0] == '\0';
+    }
+    bool SourceLineInfo::operator == ( SourceLineInfo const& other ) const noexcept {
+        return line == other.line && (file == other.file || std::strcmp(file, other.file) == 0);
+    }
+    bool SourceLineInfo::operator < ( SourceLineInfo const& other ) const noexcept {
+        // We can assume that the same file will usually have the same pointer.
+        // Thus, if the pointers are the same, there is no point in calling the strcmp
+        return line < other.line || ( line == other.line && file != other.file && (std::strcmp(file, other.file) < 0));
+    }
+
+    std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ) {
+#ifndef __GNUG__
+        os << info.file << '(' << info.line << ')';
+#else
+        os << info.file << ':' << info.line;
+#endif
+        return os;
+    }
+
+    std::string StreamEndStop::operator+() const {
+        return std::string();
+    }
+
+    NonCopyable::NonCopyable() = default;
+    NonCopyable::~NonCopyable() = default;
+
+}
+// end catch_common.cpp
+// start catch_config.cpp
+
+namespace Catch {
+
+    Config::Config( ConfigData const& data )
+    :   m_data( data ),
+        m_stream( openStream() )
+    {
+        TestSpecParser parser(ITagAliasRegistry::get());
+        if (data.testsOrTags.empty()) {
+            parser.parse("~[.]"); // All not hidden tests
+        }
+        else {
+            m_hasTestFilters = true;
+            for( auto const& testOrTags : data.testsOrTags )
+                parser.parse( testOrTags );
+        }
+        m_testSpec = parser.testSpec();
+    }
+
+    std::string const& Config::getFilename() const {
+        return m_data.outputFilename ;
+    }
+
+    bool Config::listTests() const          { return m_data.listTests; }
+    bool Config::listTestNamesOnly() const  { return m_data.listTestNamesOnly; }
+    bool Config::listTags() const           { return m_data.listTags; }
+    bool Config::listReporters() const      { return m_data.listReporters; }
+
+    std::string Config::getProcessName() const { return m_data.processName; }
+    std::string const& Config::getReporterName() const { return m_data.reporterName; }
+
+    std::vector<std::string> const& Config::getTestsOrTags() const { return m_data.testsOrTags; }
+    std::vector<std::string> const& Config::getSectionsToRun() const { return m_data.sectionsToRun; }
+
+    TestSpec const& Config::testSpec() const { return m_testSpec; }
+    bool Config::hasTestFilters() const { return m_hasTestFilters; }
+
+    bool Config::showHelp() const { return m_data.showHelp; }
+
+    // IConfig interface
+    bool Config::allowThrows() const                   { return !m_data.noThrow; }
+    std::ostream& Config::stream() const               { return m_stream->stream(); }
+    std::string Config::name() const                   { return m_data.name.empty() ? m_data.processName : m_data.name; }
+    bool Config::includeSuccessfulResults() const      { return m_data.showSuccessfulTests; }
+    bool Config::warnAboutMissingAssertions() const    { return !!(m_data.warnings & WarnAbout::NoAssertions); }
+    bool Config::warnAboutNoTests() const              { return !!(m_data.warnings & WarnAbout::NoTests); }
+    ShowDurations::OrNot Config::showDurations() const { return m_data.showDurations; }
+    RunTests::InWhatOrder Config::runOrder() const     { return m_data.runOrder; }
+    unsigned int Config::rngSeed() const               { return m_data.rngSeed; }
+    int Config::benchmarkResolutionMultiple() const    { return m_data.benchmarkResolutionMultiple; }
+    UseColour::YesOrNo Config::useColour() const       { return m_data.useColour; }
+    bool Config::shouldDebugBreak() const              { return m_data.shouldDebugBreak; }
+    int Config::abortAfter() const                     { return m_data.abortAfter; }
+    bool Config::showInvisibles() const                { return m_data.showInvisibles; }
+    Verbosity Config::verbosity() const                { return m_data.verbosity; }
+
+    IStream const* Config::openStream() {
+        return Catch::makeStream(m_data.outputFilename);
+    }
+
+} // end namespace Catch
+// end catch_config.cpp
+// start catch_console_colour.cpp
+
+#if defined(__clang__)
+#    pragma clang diagnostic push
+#    pragma clang diagnostic ignored "-Wexit-time-destructors"
+#endif
+
+// start catch_errno_guard.h
+
+namespace Catch {
+
+    class ErrnoGuard {
+    public:
+        ErrnoGuard();
+        ~ErrnoGuard();
+    private:
+        int m_oldErrno;
+    };
+
+}
+
+// end catch_errno_guard.h
+#include <sstream>
+
+namespace Catch {
+    namespace {
+
+        struct IColourImpl {
+            virtual ~IColourImpl() = default;
+            virtual void use( Colour::Code _colourCode ) = 0;
+        };
+
+        struct NoColourImpl : IColourImpl {
+            void use( Colour::Code ) {}
+
+            static IColourImpl* instance() {
+                static NoColourImpl s_instance;
+                return &s_instance;
+            }
+        };
+
+    } // anon namespace
+} // namespace Catch
+
+#if !defined( CATCH_CONFIG_COLOUR_NONE ) && !defined( CATCH_CONFIG_COLOUR_WINDOWS ) && !defined( CATCH_CONFIG_COLOUR_ANSI )
+#   ifdef CATCH_PLATFORM_WINDOWS
+#       define CATCH_CONFIG_COLOUR_WINDOWS
+#   else
+#       define CATCH_CONFIG_COLOUR_ANSI
+#   endif
+#endif
+
+#if defined ( CATCH_CONFIG_COLOUR_WINDOWS ) /////////////////////////////////////////
+
+namespace Catch {
+namespace {
+
+    class Win32ColourImpl : public IColourImpl {
+    public:
+        Win32ColourImpl() : stdoutHandle( GetStdHandle(STD_OUTPUT_HANDLE) )
+        {
+            CONSOLE_SCREEN_BUFFER_INFO csbiInfo;
+            GetConsoleScreenBufferInfo( stdoutHandle, &csbiInfo );
+            originalForegroundAttributes = csbiInfo.wAttributes & ~( BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_BLUE | BACKGROUND_INTENSITY );
+            originalBackgroundAttributes = csbiInfo.wAttributes & ~( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY );
+        }
+
+        virtual void use( Colour::Code _colourCode ) override {
+            switch( _colourCode ) {
+                case Colour::None:      return setTextAttribute( originalForegroundAttributes );
+                case Colour::White:     return setTextAttribute( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE );
+                case Colour::Red:       return setTextAttribute( FOREGROUND_RED );
+                case Colour::Green:     return setTextAttribute( FOREGROUND_GREEN );
+                case Colour::Blue:      return setTextAttribute( FOREGROUND_BLUE );
+                case Colour::Cyan:      return setTextAttribute( FOREGROUND_BLUE | FOREGROUND_GREEN );
+                case Colour::Yellow:    return setTextAttribute( FOREGROUND_RED | FOREGROUND_GREEN );
+                case Colour::Grey:      return setTextAttribute( 0 );
+
+                case Colour::LightGrey:     return setTextAttribute( FOREGROUND_INTENSITY );
+                case Colour::BrightRed:     return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_RED );
+                case Colour::BrightGreen:   return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN );
+                case Colour::BrightWhite:   return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE );
+                case Colour::BrightYellow:  return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN );
+
+                case Colour::Bright: CATCH_INTERNAL_ERROR( "not a colour" );
+
+                default:
+                    CATCH_ERROR( "Unknown colour requested" );
+            }
+        }
+
+    private:
+        void setTextAttribute( WORD _textAttribute ) {
+            SetConsoleTextAttribute( stdoutHandle, _textAttribute | originalBackgroundAttributes );
+        }
+        HANDLE stdoutHandle;
+        WORD originalForegroundAttributes;
+        WORD originalBackgroundAttributes;
+    };
+
+    IColourImpl* platformColourInstance() {
+        static Win32ColourImpl s_instance;
+
+        IConfigPtr config = getCurrentContext().getConfig();
+        UseColour::YesOrNo colourMode = config
+            ? config->useColour()
+            : UseColour::Auto;
+        if( colourMode == UseColour::Auto )
+            colourMode = UseColour::Yes;
+        return colourMode == UseColour::Yes
+            ? &s_instance
+            : NoColourImpl::instance();
+    }
+
+} // end anon namespace
+} // end namespace Catch
+
+#elif defined( CATCH_CONFIG_COLOUR_ANSI ) //////////////////////////////////////
+
+#include <unistd.h>
+
+namespace Catch {
+namespace {
+
+    // use POSIX/ ANSI console terminal codes
+    // Thanks to Adam Strzelecki for original contribution
+    // (http://github.com/nanoant)
+    // https://github.com/philsquared/Catch/pull/131
+    class PosixColourImpl : public IColourImpl {
+    public:
+        virtual void use( Colour::Code _colourCode ) override {
+            switch( _colourCode ) {
+                case Colour::None:
+                case Colour::White:     return setColour( "[0m" );
+                case Colour::Red:       return setColour( "[0;31m" );
+                case Colour::Green:     return setColour( "[0;32m" );
+                case Colour::Blue:      return setColour( "[0;34m" );
+                case Colour::Cyan:      return setColour( "[0;36m" );
+                case Colour::Yellow:    return setColour( "[0;33m" );
+                case Colour::Grey:      return setColour( "[1;30m" );
+
+                case Colour::LightGrey:     return setColour( "[0;37m" );
+                case Colour::BrightRed:     return setColour( "[1;31m" );
+                case Colour::BrightGreen:   return setColour( "[1;32m" );
+                case Colour::BrightWhite:   return setColour( "[1;37m" );
+                case Colour::BrightYellow:  return setColour( "[1;33m" );
+
+                case Colour::Bright: CATCH_INTERNAL_ERROR( "not a colour" );
+                default: CATCH_INTERNAL_ERROR( "Unknown colour requested" );
+            }
+        }
+        static IColourImpl* instance() {
+            static PosixColourImpl s_instance;
+            return &s_instance;
+        }
+
+    private:
+        void setColour( const char* _escapeCode ) {
+            Catch::cout() << '\033' << _escapeCode;
+        }
+    };
+
+    bool useColourOnPlatform() {
+        return
+#ifdef CATCH_PLATFORM_MAC
+            !isDebuggerActive() &&
+#endif
+#if !(defined(__DJGPP__) && defined(__STRICT_ANSI__))
+            isatty(STDOUT_FILENO)
+#else
+            false
+#endif
+            ;
+    }
+    IColourImpl* platformColourInstance() {
+        ErrnoGuard guard;
+        IConfigPtr config = getCurrentContext().getConfig();
+        UseColour::YesOrNo colourMode = config
+            ? config->useColour()
+            : UseColour::Auto;
+        if( colourMode == UseColour::Auto )
+            colourMode = useColourOnPlatform()
+                ? UseColour::Yes
+                : UseColour::No;
+        return colourMode == UseColour::Yes
+            ? PosixColourImpl::instance()
+            : NoColourImpl::instance();
+    }
+
+} // end anon namespace
+} // end namespace Catch
+
+#else  // not Windows or ANSI ///////////////////////////////////////////////
+
+namespace Catch {
+
+    static IColourImpl* platformColourInstance() { return NoColourImpl::instance(); }
+
+} // end namespace Catch
+
+#endif // Windows/ ANSI/ None
+
+namespace Catch {
+
+    Colour::Colour( Code _colourCode ) { use( _colourCode ); }
+    Colour::Colour( Colour&& rhs ) noexcept {
+        m_moved = rhs.m_moved;
+        rhs.m_moved = true;
+    }
+    Colour& Colour::operator=( Colour&& rhs ) noexcept {
+        m_moved = rhs.m_moved;
+        rhs.m_moved  = true;
+        return *this;
+    }
+
+    Colour::~Colour(){ if( !m_moved ) use( None ); }
+
+    void Colour::use( Code _colourCode ) {
+        static IColourImpl* impl = platformColourInstance();
+        impl->use( _colourCode );
+    }
+
+    std::ostream& operator << ( std::ostream& os, Colour const& ) {
+        return os;
+    }
+
+} // end namespace Catch
+
+#if defined(__clang__)
+#    pragma clang diagnostic pop
+#endif
+
+// end catch_console_colour.cpp
+// start catch_context.cpp
+
+namespace Catch {
+
+    class Context : public IMutableContext, NonCopyable {
+
+    public: // IContext
+        virtual IResultCapture* getResultCapture() override {
+            return m_resultCapture;
+        }
+        virtual IRunner* getRunner() override {
+            return m_runner;
+        }
+
+        virtual IConfigPtr const& getConfig() const override {
+            return m_config;
+        }
+
+        virtual ~Context() override;
+
+    public: // IMutableContext
+        virtual void setResultCapture( IResultCapture* resultCapture ) override {
+            m_resultCapture = resultCapture;
+        }
+        virtual void setRunner( IRunner* runner ) override {
+            m_runner = runner;
+        }
+        virtual void setConfig( IConfigPtr const& config ) override {
+            m_config = config;
+        }
+
+        friend IMutableContext& getCurrentMutableContext();
+
+    private:
+        IConfigPtr m_config;
+        IRunner* m_runner = nullptr;
+        IResultCapture* m_resultCapture = nullptr;
+    };
+
+    IMutableContext *IMutableContext::currentContext = nullptr;
+
+    void IMutableContext::createContext()
+    {
+        currentContext = new Context();
+    }
+
+    void cleanUpContext() {
+        delete IMutableContext::currentContext;
+        IMutableContext::currentContext = nullptr;
+    }
+    IContext::~IContext() = default;
+    IMutableContext::~IMutableContext() = default;
+    Context::~Context() = default;
+}
+// end catch_context.cpp
+// start catch_debug_console.cpp
+
+// start catch_debug_console.h
+
+#include <string>
+
+namespace Catch {
+    void writeToDebugConsole( std::string const& text );
+}
+
+// end catch_debug_console.h
+#ifdef CATCH_PLATFORM_WINDOWS
+
+    namespace Catch {
+        void writeToDebugConsole( std::string const& text ) {
+            ::OutputDebugStringA( text.c_str() );
+        }
+    }
+
+#else
+
+    namespace Catch {
+        void writeToDebugConsole( std::string const& text ) {
+            // !TBD: Need a version for Mac/ XCode and other IDEs
+            Catch::cout() << text;
+        }
+    }
+
+#endif // Platform
+// end catch_debug_console.cpp
+// start catch_debugger.cpp
+
+#ifdef CATCH_PLATFORM_MAC
+
+#  include <assert.h>
+#  include <stdbool.h>
+#  include <sys/types.h>
+#  include <unistd.h>
+#  include <sys/sysctl.h>
+#  include <cstddef>
+#  include <ostream>
+
+namespace Catch {
+
+        // The following function is taken directly from the following technical note:
+        // http://developer.apple.com/library/mac/#qa/qa2004/qa1361.html
+
+        // Returns true if the current process is being debugged (either
+        // running under the debugger or has a debugger attached post facto).
+        bool isDebuggerActive(){
+
+            int                 mib[4];
+            struct kinfo_proc   info;
+            std::size_t         size;
+
+            // Initialize the flags so that, if sysctl fails for some bizarre
+            // reason, we get a predictable result.
+
+            info.kp_proc.p_flag = 0;
+
+            // Initialize mib, which tells sysctl the info we want, in this case
+            // we're looking for information about a specific process ID.
+
+            mib[0] = CTL_KERN;
+            mib[1] = KERN_PROC;
+            mib[2] = KERN_PROC_PID;
+            mib[3] = getpid();
+
+            // Call sysctl.
+
+            size = sizeof(info);
+            if( sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, nullptr, 0) != 0 ) {
+                Catch::cerr() << "\n** Call to sysctl failed - unable to determine if debugger is active **\n" << std::endl;
+                return false;
+            }
+
+            // We're being debugged if the P_TRACED flag is set.
+
+            return ( (info.kp_proc.p_flag & P_TRACED) != 0 );
+        }
+    } // namespace Catch
+
+#elif defined(CATCH_PLATFORM_LINUX)
+    #include <fstream>
+    #include <string>
+
+    namespace Catch{
+        // The standard POSIX way of detecting a debugger is to attempt to
+        // ptrace() the process, but this needs to be done from a child and not
+        // this process itself to still allow attaching to this process later
+        // if wanted, so is rather heavy. Under Linux we have the PID of the
+        // "debugger" (which doesn't need to be gdb, of course, it could also
+        // be strace, for example) in /proc/$PID/status, so just get it from
+        // there instead.
+        bool isDebuggerActive(){
+            // Libstdc++ has a bug, where std::ifstream sets errno to 0
+            // This way our users can properly assert over errno values
+            ErrnoGuard guard;
+            std::ifstream in("/proc/self/status");
+            for( std::string line; std::getline(in, line); ) {
+                static const int PREFIX_LEN = 11;
+                if( line.compare(0, PREFIX_LEN, "TracerPid:\t") == 0 ) {
+                    // We're traced if the PID is not 0 and no other PID starts
+                    // with 0 digit, so it's enough to check for just a single
+                    // character.
+                    return line.length() > PREFIX_LEN && line[PREFIX_LEN] != '0';
+                }
+            }
+
+            return false;
+        }
+    } // namespace Catch
+#elif defined(_MSC_VER)
+    extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent();
+    namespace Catch {
+        bool isDebuggerActive() {
+            return IsDebuggerPresent() != 0;
+        }
+    }
+#elif defined(__MINGW32__)
+    extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent();
+    namespace Catch {
+        bool isDebuggerActive() {
+            return IsDebuggerPresent() != 0;
+        }
+    }
+#else
+    namespace Catch {
+       bool isDebuggerActive() { return false; }
+    }
+#endif // Platform
+// end catch_debugger.cpp
+// start catch_decomposer.cpp
+
+namespace Catch {
+
+    ITransientExpression::~ITransientExpression() = default;
+
+    void formatReconstructedExpression( std::ostream &os, std::string const& lhs, StringRef op, std::string const& rhs ) {
+        if( lhs.size() + rhs.size() < 40 &&
+                lhs.find('\n') == std::string::npos &&
+                rhs.find('\n') == std::string::npos )
+            os << lhs << " " << op << " " << rhs;
+        else
+            os << lhs << "\n" << op << "\n" << rhs;
+    }
+}
+// end catch_decomposer.cpp
+// start catch_enforce.cpp
+
+namespace Catch {
+#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) && !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS_CUSTOM_HANDLER)
+    [[noreturn]]
+    void throw_exception(std::exception const& e) {
+        Catch::cerr() << "Catch will terminate because it needed to throw an exception.\n"
+                      << "The message was: " << e.what() << '\n';
+        std::terminate();
+    }
+#endif
+} // namespace Catch;
+// end catch_enforce.cpp
+// start catch_errno_guard.cpp
+
+#include <cerrno>
+
+namespace Catch {
+        ErrnoGuard::ErrnoGuard():m_oldErrno(errno){}
+        ErrnoGuard::~ErrnoGuard() { errno = m_oldErrno; }
+}
+// end catch_errno_guard.cpp
+// start catch_exception_translator_registry.cpp
+
+// start catch_exception_translator_registry.h
+
+#include <vector>
+#include <string>
+#include <memory>
+
+namespace Catch {
+
+    class ExceptionTranslatorRegistry : public IExceptionTranslatorRegistry {
+    public:
+        ~ExceptionTranslatorRegistry();
+        virtual void registerTranslator( const IExceptionTranslator* translator );
+        virtual std::string translateActiveException() const override;
+        std::string tryTranslators() const;
+
+    private:
+        std::vector<std::unique_ptr<IExceptionTranslator const>> m_translators;
+    };
+}
+
+// end catch_exception_translator_registry.h
+#ifdef __OBJC__
+#import "Foundation/Foundation.h"
+#endif
+
+namespace Catch {
+
+    ExceptionTranslatorRegistry::~ExceptionTranslatorRegistry() {
+    }
+
+    void ExceptionTranslatorRegistry::registerTranslator( const IExceptionTranslator* translator ) {
+        m_translators.push_back( std::unique_ptr<const IExceptionTranslator>( translator ) );
+    }
+
+#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)
+    std::string ExceptionTranslatorRegistry::translateActiveException() const {
+        try {
+#ifdef __OBJC__
+            // In Objective-C try objective-c exceptions first
+            @try {
+                return tryTranslators();
+            }
+            @catch (NSException *exception) {
+                return Catch::Detail::stringify( [exception description] );
+            }
+#else
+            // Compiling a mixed mode project with MSVC means that CLR
+            // exceptions will be caught in (...) as well. However, these
+            // do not fill-in std::current_exception and thus lead to crash
+            // when attempting rethrow.
+            // /EHa switch also causes structured exceptions to be caught
+            // here, but they fill-in current_exception properly, so
+            // at worst the output should be a little weird, instead of
+            // causing a crash.
+            if (std::current_exception() == nullptr) {
+                return "Non C++ exception. Possibly a CLR exception.";
+            }
+            return tryTranslators();
+#endif
+        }
+        catch( TestFailureException& ) {
+            std::rethrow_exception(std::current_exception());
+        }
+        catch( std::exception& ex ) {
+            return ex.what();
+        }
+        catch( std::string& msg ) {
+            return msg;
+        }
+        catch( const char* msg ) {
+            return msg;
+        }
+        catch(...) {
+            return "Unknown exception";
+        }
+    }
+
+#else // ^^ Exceptions are enabled // Exceptions are disabled vv
+    std::string ExceptionTranslatorRegistry::translateActiveException() const {
+        CATCH_INTERNAL_ERROR("Attempted to translate active exception under CATCH_CONFIG_DISABLE_EXCEPTIONS!");
+    }
+#endif
+
+    std::string ExceptionTranslatorRegistry::tryTranslators() const {
+        if( m_translators.empty() )
+            std::rethrow_exception(std::current_exception());
+        else
+            return m_translators[0]->translate( m_translators.begin()+1, m_translators.end() );
+    }
+}
+// end catch_exception_translator_registry.cpp
+// start catch_fatal_condition.cpp
+
+#if defined(__GNUC__)
+#    pragma GCC diagnostic push
+#    pragma GCC diagnostic ignored "-Wmissing-field-initializers"
+#endif
+
+#if defined( CATCH_CONFIG_WINDOWS_SEH ) || defined( CATCH_CONFIG_POSIX_SIGNALS )
+
+namespace {
+    // Report the error condition
+    void reportFatal( char const * const message ) {
+        Catch::getCurrentContext().getResultCapture()->handleFatalErrorCondition( message );
+    }
+}
+
+#endif // signals/SEH handling
+
+#if defined( CATCH_CONFIG_WINDOWS_SEH )
+
+namespace Catch {
+    struct SignalDefs { DWORD id; const char* name; };
+
+    // There is no 1-1 mapping between signals and windows exceptions.
+    // Windows can easily distinguish between SO and SigSegV,
+    // but SigInt, SigTerm, etc are handled differently.
+    static SignalDefs signalDefs[] = {
+        { EXCEPTION_ILLEGAL_INSTRUCTION,  "SIGILL - Illegal instruction signal" },
+        { EXCEPTION_STACK_OVERFLOW, "SIGSEGV - Stack overflow" },
+        { EXCEPTION_ACCESS_VIOLATION, "SIGSEGV - Segmentation violation signal" },
+        { EXCEPTION_INT_DIVIDE_BY_ZERO, "Divide by zero error" },
+    };
+
+    LONG CALLBACK FatalConditionHandler::handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo) {
+        for (auto const& def : signalDefs) {
+            if (ExceptionInfo->ExceptionRecord->ExceptionCode == def.id) {
+                reportFatal(def.name);
+            }
+        }
+        // If its not an exception we care about, pass it along.
+        // This stops us from eating debugger breaks etc.
+        return EXCEPTION_CONTINUE_SEARCH;
+    }
+
+    FatalConditionHandler::FatalConditionHandler() {
+        isSet = true;
+        // 32k seems enough for Catch to handle stack overflow,
+        // but the value was found experimentally, so there is no strong guarantee
+        guaranteeSize = 32 * 1024;
+        exceptionHandlerHandle = nullptr;
+        // Register as first handler in current chain
+        exceptionHandlerHandle = AddVectoredExceptionHandler(1, handleVectoredException);
+        // Pass in guarantee size to be filled
+        SetThreadStackGuarantee(&guaranteeSize);
+    }
+
+    void FatalConditionHandler::reset() {
+        if (isSet) {
+            RemoveVectoredExceptionHandler(exceptionHandlerHandle);
+            SetThreadStackGuarantee(&guaranteeSize);
+            exceptionHandlerHandle = nullptr;
+            isSet = false;
+        }
+    }
+
+    FatalConditionHandler::~FatalConditionHandler() {
+        reset();
+    }
+
+bool FatalConditionHandler::isSet = false;
+ULONG FatalConditionHandler::guaranteeSize = 0;
+PVOID FatalConditionHandler::exceptionHandlerHandle = nullptr;
+
+} // namespace Catch
+
+#elif defined( CATCH_CONFIG_POSIX_SIGNALS )
+
+namespace Catch {
+
+    struct SignalDefs {
+        int id;
+        const char* name;
+    };
+
+    // 32kb for the alternate stack seems to be sufficient. However, this value
+    // is experimentally determined, so that's not guaranteed.
+    constexpr static std::size_t sigStackSize = 32768 >= MINSIGSTKSZ ? 32768 : MINSIGSTKSZ;
+
+    static SignalDefs signalDefs[] = {
+        { SIGINT,  "SIGINT - Terminal interrupt signal" },
+        { SIGILL,  "SIGILL - Illegal instruction signal" },
+        { SIGFPE,  "SIGFPE - Floating point error signal" },
+        { SIGSEGV, "SIGSEGV - Segmentation violation signal" },
+        { SIGTERM, "SIGTERM - Termination request signal" },
+        { SIGABRT, "SIGABRT - Abort (abnormal termination) signal" }
+    };
+
+    void FatalConditionHandler::handleSignal( int sig ) {
+        char const * name = "<unknown signal>";
+        for (auto const& def : signalDefs) {
+            if (sig == def.id) {
+                name = def.name;
+                break;
+            }
+        }
+        reset();
+        reportFatal(name);
+        raise( sig );
+    }
+
+    FatalConditionHandler::FatalConditionHandler() {
+        isSet = true;
+        stack_t sigStack;
+        sigStack.ss_sp = altStackMem;
+        sigStack.ss_size = sigStackSize;
+        sigStack.ss_flags = 0;
+        sigaltstack(&sigStack, &oldSigStack);
+        struct sigaction sa = { };
+
+        sa.sa_handler = handleSignal;
+        sa.sa_flags = SA_ONSTACK;
+        for (std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i) {
+            sigaction(signalDefs[i].id, &sa, &oldSigActions[i]);
+        }
+    }
+
+    FatalConditionHandler::~FatalConditionHandler() {
+        reset();
+    }
+
+    void FatalConditionHandler::reset() {
+        if( isSet ) {
+            // Set signals back to previous values -- hopefully nobody overwrote them in the meantime
+            for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) {
+                sigaction(signalDefs[i].id, &oldSigActions[i], nullptr);
+            }
+            // Return the old stack
+            sigaltstack(&oldSigStack, nullptr);
+            isSet = false;
+        }
+    }
+
+    bool FatalConditionHandler::isSet = false;
+    struct sigaction FatalConditionHandler::oldSigActions[sizeof(signalDefs)/sizeof(SignalDefs)] = {};
+    stack_t FatalConditionHandler::oldSigStack = {};
+    char FatalConditionHandler::altStackMem[sigStackSize] = {};
+
+} // namespace Catch
+
+#else
+
+namespace Catch {
+    void FatalConditionHandler::reset() {}
+}
+
+#endif // signals/SEH handling
+
+#if defined(__GNUC__)
+#    pragma GCC diagnostic pop
+#endif
+// end catch_fatal_condition.cpp
+// start catch_generators.cpp
+
+// start catch_random_number_generator.h
+
+#include <algorithm>
+#include <random>
+
+namespace Catch {
+
+    struct IConfig;
+
+    std::mt19937& rng();
+    void seedRng( IConfig const& config );
+    unsigned int rngSeed();
+
+}
+
+// end catch_random_number_generator.h
+#include <limits>
+#include <set>
+
+namespace Catch {
+
+IGeneratorTracker::~IGeneratorTracker() {}
+
+namespace Generators {
+
+    GeneratorBase::~GeneratorBase() {}
+
+    std::vector<size_t> randomiseIndices( size_t selectionSize, size_t sourceSize ) {
+
+        assert( selectionSize <= sourceSize );
+        std::vector<size_t> indices;
+        indices.reserve( selectionSize );
+        std::uniform_int_distribution<size_t> uid( 0, sourceSize-1 );
+
+        std::set<size_t> seen;
+        // !TBD: improve this algorithm
+        while( indices.size() < selectionSize ) {
+            auto index = uid( rng() );
+            if( seen.insert( index ).second )
+                indices.push_back( index );
+        }
+        return indices;
+    }
+
+    auto acquireGeneratorTracker( SourceLineInfo const& lineInfo ) -> IGeneratorTracker& {
+        return getResultCapture().acquireGeneratorTracker( lineInfo );
+    }
+
+    template<>
+    auto all<int>() -> Generator<int> {
+        return range( std::numeric_limits<int>::min(), std::numeric_limits<int>::max() );
+    }
+
+} // namespace Generators
+} // namespace Catch
+// end catch_generators.cpp
+// start catch_interfaces_capture.cpp
+
+namespace Catch {
+    IResultCapture::~IResultCapture() = default;
+}
+// end catch_interfaces_capture.cpp
+// start catch_interfaces_config.cpp
+
+namespace Catch {
+    IConfig::~IConfig() = default;
+}
+// end catch_interfaces_config.cpp
+// start catch_interfaces_exception.cpp
+
+namespace Catch {
+    IExceptionTranslator::~IExceptionTranslator() = default;
+    IExceptionTranslatorRegistry::~IExceptionTranslatorRegistry() = default;
+}
+// end catch_interfaces_exception.cpp
+// start catch_interfaces_registry_hub.cpp
+
+namespace Catch {
+    IRegistryHub::~IRegistryHub() = default;
+    IMutableRegistryHub::~IMutableRegistryHub() = default;
+}
+// end catch_interfaces_registry_hub.cpp
+// start catch_interfaces_reporter.cpp
+
+// start catch_reporter_listening.h
+
+namespace Catch {
+
+    class ListeningReporter : public IStreamingReporter {
+        using Reporters = std::vector<IStreamingReporterPtr>;
+        Reporters m_listeners;
+        IStreamingReporterPtr m_reporter = nullptr;
+        ReporterPreferences m_preferences;
+
+    public:
+        ListeningReporter();
+
+        void addListener( IStreamingReporterPtr&& listener );
+        void addReporter( IStreamingReporterPtr&& reporter );
+
+    public: // IStreamingReporter
+
+        ReporterPreferences getPreferences() const override;
+
+        void noMatchingTestCases( std::string const& spec ) override;
+
+        static std::set<Verbosity> getSupportedVerbosities();
+
+        void benchmarkStarting( BenchmarkInfo const& benchmarkInfo ) override;
+        void benchmarkEnded( BenchmarkStats const& benchmarkStats ) override;
+
+        void testRunStarting( TestRunInfo const& testRunInfo ) override;
+        void testGroupStarting( GroupInfo const& groupInfo ) override;
+        void testCaseStarting( TestCaseInfo const& testInfo ) override;
+        void sectionStarting( SectionInfo const& sectionInfo ) override;
+        void assertionStarting( AssertionInfo const& assertionInfo ) override;
+
+        // The return value indicates if the messages buffer should be cleared:
+        bool assertionEnded( AssertionStats const& assertionStats ) override;
+        void sectionEnded( SectionStats const& sectionStats ) override;
+        void testCaseEnded( TestCaseStats const& testCaseStats ) override;
+        void testGroupEnded( TestGroupStats const& testGroupStats ) override;
+        void testRunEnded( TestRunStats const& testRunStats ) override;
+
+        void skipTest( TestCaseInfo const& testInfo ) override;
+        bool isMulti() const override;
+
+    };
+
+} // end namespace Catch
+
+// end catch_reporter_listening.h
+namespace Catch {
+
+    ReporterConfig::ReporterConfig( IConfigPtr const& _fullConfig )
+    :   m_stream( &_fullConfig->stream() ), m_fullConfig( _fullConfig ) {}
+
+    ReporterConfig::ReporterConfig( IConfigPtr const& _fullConfig, std::ostream& _stream )
+    :   m_stream( &_stream ), m_fullConfig( _fullConfig ) {}
+
+    std::ostream& ReporterConfig::stream() const { return *m_stream; }
+    IConfigPtr ReporterConfig::fullConfig() const { return m_fullConfig; }
+
+    TestRunInfo::TestRunInfo( std::string const& _name ) : name( _name ) {}
+
+    GroupInfo::GroupInfo(  std::string const& _name,
+                           std::size_t _groupIndex,
+                           std::size_t _groupsCount )
+    :   name( _name ),
+        groupIndex( _groupIndex ),
+        groupsCounts( _groupsCount )
+    {}
+
+     AssertionStats::AssertionStats( AssertionResult const& _assertionResult,
+                                     std::vector<MessageInfo> const& _infoMessages,
+                                     Totals const& _totals )
+    :   assertionResult( _assertionResult ),
+        infoMessages( _infoMessages ),
+        totals( _totals )
+    {
+        assertionResult.m_resultData.lazyExpression.m_transientExpression = _assertionResult.m_resultData.lazyExpression.m_transientExpression;
+
+        if( assertionResult.hasMessage() ) {
+            // Copy message into messages list.
+            // !TBD This should have been done earlier, somewhere
+            MessageBuilder builder( assertionResult.getTestMacroName(), assertionResult.getSourceInfo(), assertionResult.getResultType() );
+            builder << assertionResult.getMessage();
+            builder.m_info.message = builder.m_stream.str();
+
+            infoMessages.push_back( builder.m_info );
+        }
+    }
+
+     AssertionStats::~AssertionStats() = default;
+
+    SectionStats::SectionStats(  SectionInfo const& _sectionInfo,
+                                 Counts const& _assertions,
+                                 double _durationInSeconds,
+                                 bool _missingAssertions )
+    :   sectionInfo( _sectionInfo ),
+        assertions( _assertions ),
+        durationInSeconds( _durationInSeconds ),
+        missingAssertions( _missingAssertions )
+    {}
+
+    SectionStats::~SectionStats() = default;
+
+    TestCaseStats::TestCaseStats(  TestCaseInfo const& _testInfo,
+                                   Totals const& _totals,
+                                   std::string const& _stdOut,
+                                   std::string const& _stdErr,
+                                   bool _aborting )
+    : testInfo( _testInfo ),
+        totals( _totals ),
+        stdOut( _stdOut ),
+        stdErr( _stdErr ),
+        aborting( _aborting )
+    {}
+
+    TestCaseStats::~TestCaseStats() = default;
+
+    TestGroupStats::TestGroupStats( GroupInfo const& _groupInfo,
+                                    Totals const& _totals,
+                                    bool _aborting )
+    :   groupInfo( _groupInfo ),
+        totals( _totals ),
+        aborting( _aborting )
+    {}
+
+    TestGroupStats::TestGroupStats( GroupInfo const& _groupInfo )
+    :   groupInfo( _groupInfo ),
+        aborting( false )
+    {}
+
+    TestGroupStats::~TestGroupStats() = default;
+
+    TestRunStats::TestRunStats(   TestRunInfo const& _runInfo,
+                    Totals const& _totals,
+                    bool _aborting )
+    :   runInfo( _runInfo ),
+        totals( _totals ),
+        aborting( _aborting )
+    {}
+
+    TestRunStats::~TestRunStats() = default;
+
+    void IStreamingReporter::fatalErrorEncountered( StringRef ) {}
+    bool IStreamingReporter::isMulti() const { return false; }
+
+    IReporterFactory::~IReporterFactory() = default;
+    IReporterRegistry::~IReporterRegistry() = default;
+
+} // end namespace Catch
+// end catch_interfaces_reporter.cpp
+// start catch_interfaces_runner.cpp
+
+namespace Catch {
+    IRunner::~IRunner() = default;
+}
+// end catch_interfaces_runner.cpp
+// start catch_interfaces_testcase.cpp
+
+namespace Catch {
+    ITestInvoker::~ITestInvoker() = default;
+    ITestCaseRegistry::~ITestCaseRegistry() = default;
+}
+// end catch_interfaces_testcase.cpp
+// start catch_leak_detector.cpp
+
+#ifdef CATCH_CONFIG_WINDOWS_CRTDBG
+#include <crtdbg.h>
+
+namespace Catch {
+
+    LeakDetector::LeakDetector() {
+        int flag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG);
+        flag |= _CRTDBG_LEAK_CHECK_DF;
+        flag |= _CRTDBG_ALLOC_MEM_DF;
+        _CrtSetDbgFlag(flag);
+        _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG);
+        _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR);
+        // Change this to leaking allocation's number to break there
+        _CrtSetBreakAlloc(-1);
+    }
+}
+
+#else
+
+    Catch::LeakDetector::LeakDetector() {}
+
+#endif
+
+Catch::LeakDetector::~LeakDetector() {
+    Catch::cleanUp();
+}
+// end catch_leak_detector.cpp
+// start catch_list.cpp
+
+// start catch_list.h
+
+#include <set>
+
+namespace Catch {
+
+    std::size_t listTests( Config const& config );
+
+    std::size_t listTestsNamesOnly( Config const& config );
+
+    struct TagInfo {
+        void add( std::string const& spelling );
+        std::string all() const;
+
+        std::set<std::string> spellings;
+        std::size_t count = 0;
+    };
+
+    std::size_t listTags( Config const& config );
+
+    std::size_t listReporters();
+
+    Option<std::size_t> list( Config const& config );
+
+} // end namespace Catch
+
+// end catch_list.h
+// start catch_text.h
+
+namespace Catch {
+    using namespace clara::TextFlow;
+}
+
+// end catch_text.h
+#include <limits>
+#include <algorithm>
+#include <iomanip>
+
+namespace Catch {
+
+    std::size_t listTests( Config const& config ) {
+        TestSpec testSpec = config.testSpec();
+        if( config.hasTestFilters() )
+            Catch::cout() << "Matching test cases:\n";
+        else {
+            Catch::cout() << "All available test cases:\n";
+        }
+
+        auto matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config );
+        for( auto const& testCaseInfo : matchedTestCases ) {
+            Colour::Code colour = testCaseInfo.isHidden()
+                ? Colour::SecondaryText
+                : Colour::None;
+            Colour colourGuard( colour );
+
+            Catch::cout() << Column( testCaseInfo.name ).initialIndent( 2 ).indent( 4 ) << "\n";
+            if( config.verbosity() >= Verbosity::High ) {
+                Catch::cout() << Column( Catch::Detail::stringify( testCaseInfo.lineInfo ) ).indent(4) << std::endl;
+                std::string description = testCaseInfo.description;
+                if( description.empty() )
+                    description = "(NO DESCRIPTION)";
+                Catch::cout() << Column( description ).indent(4) << std::endl;
+            }
+            if( !testCaseInfo.tags.empty() )
+                Catch::cout() << Column( testCaseInfo.tagsAsString() ).indent( 6 ) << "\n";
+        }
+
+        if( !config.hasTestFilters() )
+            Catch::cout() << pluralise( matchedTestCases.size(), "test case" ) << '\n' << std::endl;
+        else
+            Catch::cout() << pluralise( matchedTestCases.size(), "matching test case" ) << '\n' << std::endl;
+        return matchedTestCases.size();
+    }
+
+    std::size_t listTestsNamesOnly( Config const& config ) {
+        TestSpec testSpec = config.testSpec();
+        std::size_t matchedTests = 0;
+        std::vector<TestCase> matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config );
+        for( auto const& testCaseInfo : matchedTestCases ) {
+            matchedTests++;
+            if( startsWith( testCaseInfo.name, '#' ) )
+               Catch::cout() << '"' << testCaseInfo.name << '"';
+            else
+               Catch::cout() << testCaseInfo.name;
+            if ( config.verbosity() >= Verbosity::High )
+                Catch::cout() << "\t@" << testCaseInfo.lineInfo;
+            Catch::cout() << std::endl;
+        }
+        return matchedTests;
+    }
+
+    void TagInfo::add( std::string const& spelling ) {
+        ++count;
+        spellings.insert( spelling );
+    }
+
+    std::string TagInfo::all() const {
+        std::string out;
+        for( auto const& spelling : spellings )
+            out += "[" + spelling + "]";
+        return out;
+    }
+
+    std::size_t listTags( Config const& config ) {
+        TestSpec testSpec = config.testSpec();
+        if( config.hasTestFilters() )
+            Catch::cout() << "Tags for matching test cases:\n";
+        else {
+            Catch::cout() << "All available tags:\n";
+        }
+
+        std::map<std::string, TagInfo> tagCounts;
+
+        std::vector<TestCase> matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config );
+        for( auto const& testCase : matchedTestCases ) {
+            for( auto const& tagName : testCase.getTestCaseInfo().tags ) {
+                std::string lcaseTagName = toLower( tagName );
+                auto countIt = tagCounts.find( lcaseTagName );
+                if( countIt == tagCounts.end() )
+                    countIt = tagCounts.insert( std::make_pair( lcaseTagName, TagInfo() ) ).first;
+                countIt->second.add( tagName );
+            }
+        }
+
+        for( auto const& tagCount : tagCounts ) {
+            ReusableStringStream rss;
+            rss << "  " << std::setw(2) << tagCount.second.count << "  ";
+            auto str = rss.str();
+            auto wrapper = Column( tagCount.second.all() )
+                                                    .initialIndent( 0 )
+                                                    .indent( str.size() )
+                                                    .width( CATCH_CONFIG_CONSOLE_WIDTH-10 );
+            Catch::cout() << str << wrapper << '\n';
+        }
+        Catch::cout() << pluralise( tagCounts.size(), "tag" ) << '\n' << std::endl;
+        return tagCounts.size();
+    }
+
+    std::size_t listReporters() {
+        Catch::cout() << "Available reporters:\n";
+        IReporterRegistry::FactoryMap const& factories = getRegistryHub().getReporterRegistry().getFactories();
+        std::size_t maxNameLen = 0;
+        for( auto const& factoryKvp : factories )
+            maxNameLen = (std::max)( maxNameLen, factoryKvp.first.size() );
+
+        for( auto const& factoryKvp : factories ) {
+            Catch::cout()
+                    << Column( factoryKvp.first + ":" )
+                            .indent(2)
+                            .width( 5+maxNameLen )
+                    +  Column( factoryKvp.second->getDescription() )
+                            .initialIndent(0)
+                            .indent(2)
+                            .width( CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen-8 )
+                    << "\n";
+        }
+        Catch::cout() << std::endl;
+        return factories.size();
+    }
+
+    Option<std::size_t> list( Config const& config ) {
+        Option<std::size_t> listedCount;
+        if( config.listTests() )
+            listedCount = listedCount.valueOr(0) + listTests( config );
+        if( config.listTestNamesOnly() )
+            listedCount = listedCount.valueOr(0) + listTestsNamesOnly( config );
+        if( config.listTags() )
+            listedCount = listedCount.valueOr(0) + listTags( config );
+        if( config.listReporters() )
+            listedCount = listedCount.valueOr(0) + listReporters();
+        return listedCount;
+    }
+
+} // end namespace Catch
+// end catch_list.cpp
+// start catch_matchers.cpp
+
+namespace Catch {
+namespace Matchers {
+    namespace Impl {
+
+        std::string MatcherUntypedBase::toString() const {
+            if( m_cachedToString.empty() )
+                m_cachedToString = describe();
+            return m_cachedToString;
+        }
+
+        MatcherUntypedBase::~MatcherUntypedBase() = default;
+
+    } // namespace Impl
+} // namespace Matchers
+
+using namespace Matchers;
+using Matchers::Impl::MatcherBase;
+
+} // namespace Catch
+// end catch_matchers.cpp
+// start catch_matchers_floating.cpp
+
+// start catch_polyfills.hpp
+
+namespace Catch {
+    bool isnan(float f);
+    bool isnan(double d);
+}
+
+// end catch_polyfills.hpp
+// start catch_to_string.hpp
+
+#include <string>
+
+namespace Catch {
+    template <typename T>
+    std::string to_string(T const& t) {
+#if defined(CATCH_CONFIG_CPP11_TO_STRING)
+        return std::to_string(t);
+#else
+        ReusableStringStream rss;
+        rss << t;
+        return rss.str();
+#endif
+    }
+} // end namespace Catch
+
+// end catch_to_string.hpp
+#include <cstdlib>
+#include <cstdint>
+#include <cstring>
+
+namespace Catch {
+namespace Matchers {
+namespace Floating {
+enum class FloatingPointKind : uint8_t {
+    Float,
+    Double
+};
+}
+}
+}
+
+namespace {
+
+template <typename T>
+struct Converter;
+
+template <>
+struct Converter<float> {
+    static_assert(sizeof(float) == sizeof(int32_t), "Important ULP matcher assumption violated");
+    Converter(float f) {
+        std::memcpy(&i, &f, sizeof(f));
+    }
+    int32_t i;
+};
+
+template <>
+struct Converter<double> {
+    static_assert(sizeof(double) == sizeof(int64_t), "Important ULP matcher assumption violated");
+    Converter(double d) {
+        std::memcpy(&i, &d, sizeof(d));
+    }
+    int64_t i;
+};
+
+template <typename T>
+auto convert(T t) -> Converter<T> {
+    return Converter<T>(t);
+}
+
+template <typename FP>
+bool almostEqualUlps(FP lhs, FP rhs, int maxUlpDiff) {
+    // Comparison with NaN should always be false.
+    // This way we can rule it out before getting into the ugly details
+    if (Catch::isnan(lhs) || Catch::isnan(rhs)) {
+        return false;
+    }
+
+    auto lc = convert(lhs);
+    auto rc = convert(rhs);
+
+    if ((lc.i < 0) != (rc.i < 0)) {
+        // Potentially we can have +0 and -0
+        return lhs == rhs;
+    }
+
+    auto ulpDiff = std::abs(lc.i - rc.i);
+    return ulpDiff <= maxUlpDiff;
+}
+
+}
+
+namespace Catch {
+namespace Matchers {
+namespace Floating {
+    WithinAbsMatcher::WithinAbsMatcher(double target, double margin)
+        :m_target{ target }, m_margin{ margin } {
+        CATCH_ENFORCE(margin >= 0, "Invalid margin: " << margin << '.'
+            << " Margin has to be non-negative.");
+    }
+
+    // Performs equivalent check of std::fabs(lhs - rhs) <= margin
+    // But without the subtraction to allow for INFINITY in comparison
+    bool WithinAbsMatcher::match(double const& matchee) const {
+        return (matchee + m_margin >= m_target) && (m_target + m_margin >= matchee);
+    }
+
+    std::string WithinAbsMatcher::describe() const {
+        return "is within " + ::Catch::Detail::stringify(m_margin) + " of " + ::Catch::Detail::stringify(m_target);
+    }
+
+    WithinUlpsMatcher::WithinUlpsMatcher(double target, int ulps, FloatingPointKind baseType)
+        :m_target{ target }, m_ulps{ ulps }, m_type{ baseType } {
+        CATCH_ENFORCE(ulps >= 0, "Invalid ULP setting: " << ulps << '.'
+            << " ULPs have to be non-negative.");
+    }
+
+#if defined(__clang__)
+#pragma clang diagnostic push
+// Clang <3.5 reports on the default branch in the switch below
+#pragma clang diagnostic ignored "-Wunreachable-code"
+#endif
+
+    bool WithinUlpsMatcher::match(double const& matchee) const {
+        switch (m_type) {
+        case FloatingPointKind::Float:
+            return almostEqualUlps<float>(static_cast<float>(matchee), static_cast<float>(m_target), m_ulps);
+        case FloatingPointKind::Double:
+            return almostEqualUlps<double>(matchee, m_target, m_ulps);
+        default:
+            CATCH_INTERNAL_ERROR( "Unknown FloatingPointKind value" );
+        }
+    }
+
+#if defined(__clang__)
+#pragma clang diagnostic pop
+#endif
+
+    std::string WithinUlpsMatcher::describe() const {
+        return "is within " + Catch::to_string(m_ulps) + " ULPs of " + ::Catch::Detail::stringify(m_target) + ((m_type == FloatingPointKind::Float)? "f" : "");
+    }
+
+}// namespace Floating
+
+Floating::WithinUlpsMatcher WithinULP(double target, int maxUlpDiff) {
+    return Floating::WithinUlpsMatcher(target, maxUlpDiff, Floating::FloatingPointKind::Double);
+}
+
+Floating::WithinUlpsMatcher WithinULP(float target, int maxUlpDiff) {
+    return Floating::WithinUlpsMatcher(target, maxUlpDiff, Floating::FloatingPointKind::Float);
+}
+
+Floating::WithinAbsMatcher WithinAbs(double target, double margin) {
+    return Floating::WithinAbsMatcher(target, margin);
+}
+
+} // namespace Matchers
+} // namespace Catch
+
+// end catch_matchers_floating.cpp
+// start catch_matchers_generic.cpp
+
+std::string Catch::Matchers::Generic::Detail::finalizeDescription(const std::string& desc) {
+    if (desc.empty()) {
+        return "matches undescribed predicate";
+    } else {
+        return "matches predicate: \"" + desc + '"';
+    }
+}
+// end catch_matchers_generic.cpp
+// start catch_matchers_string.cpp
+
+#include <regex>
+
+namespace Catch {
+namespace Matchers {
+
+    namespace StdString {
+
+        CasedString::CasedString( std::string const& str, CaseSensitive::Choice caseSensitivity )
+        :   m_caseSensitivity( caseSensitivity ),
+            m_str( adjustString( str ) )
+        {}
+        std::string CasedString::adjustString( std::string const& str ) const {
+            return m_caseSensitivity == CaseSensitive::No
+                   ? toLower( str )
+                   : str;
+        }
+        std::string CasedString::caseSensitivitySuffix() const {
+            return m_caseSensitivity == CaseSensitive::No
+                   ? " (case insensitive)"
+                   : std::string();
+        }
+
+        StringMatcherBase::StringMatcherBase( std::string const& operation, CasedString const& comparator )
+        : m_comparator( comparator ),
+          m_operation( operation ) {
+        }
+
+        std::string StringMatcherBase::describe() const {
+            std::string description;
+            description.reserve(5 + m_operation.size() + m_comparator.m_str.size() +
+                                        m_comparator.caseSensitivitySuffix().size());
+            description += m_operation;
+            description += ": \"";
+            description += m_comparator.m_str;
+            description += "\"";
+            description += m_comparator.caseSensitivitySuffix();
+            return description;
+        }
+
+        EqualsMatcher::EqualsMatcher( CasedString const& comparator ) : StringMatcherBase( "equals", comparator ) {}
+
+        bool EqualsMatcher::match( std::string const& source ) const {
+            return m_comparator.adjustString( source ) == m_comparator.m_str;
+        }
+
+        ContainsMatcher::ContainsMatcher( CasedString const& comparator ) : StringMatcherBase( "contains", comparator ) {}
+
+        bool ContainsMatcher::match( std::string const& source ) const {
+            return contains( m_comparator.adjustString( source ), m_comparator.m_str );
+        }
+
+        StartsWithMatcher::StartsWithMatcher( CasedString const& comparator ) : StringMatcherBase( "starts with", comparator ) {}
+
+        bool StartsWithMatcher::match( std::string const& source ) const {
+            return startsWith( m_comparator.adjustString( source ), m_comparator.m_str );
+        }
+
+        EndsWithMatcher::EndsWithMatcher( CasedString const& comparator ) : StringMatcherBase( "ends with", comparator ) {}
+
+        bool EndsWithMatcher::match( std::string const& source ) const {
+            return endsWith( m_comparator.adjustString( source ), m_comparator.m_str );
+        }
+
+        RegexMatcher::RegexMatcher(std::string regex, CaseSensitive::Choice caseSensitivity): m_regex(std::move(regex)), m_caseSensitivity(caseSensitivity) {}
+
+        bool RegexMatcher::match(std::string const& matchee) const {
+            auto flags = std::regex::ECMAScript; // ECMAScript is the default syntax option anyway
+            if (m_caseSensitivity == CaseSensitive::Choice::No) {
+                flags |= std::regex::icase;
+            }
+            auto reg = std::regex(m_regex, flags);
+            return std::regex_match(matchee, reg);
+        }
+
+        std::string RegexMatcher::describe() const {
+            return "matches " + ::Catch::Detail::stringify(m_regex) + ((m_caseSensitivity == CaseSensitive::Choice::Yes)? " case sensitively" : " case insensitively");
+        }
+
+    } // namespace StdString
+
+    StdString::EqualsMatcher Equals( std::string const& str, CaseSensitive::Choice caseSensitivity ) {
+        return StdString::EqualsMatcher( StdString::CasedString( str, caseSensitivity) );
+    }
+    StdString::ContainsMatcher Contains( std::string const& str, CaseSensitive::Choice caseSensitivity ) {
+        return StdString::ContainsMatcher( StdString::CasedString( str, caseSensitivity) );
+    }
+    StdString::EndsWithMatcher EndsWith( std::string const& str, CaseSensitive::Choice caseSensitivity ) {
+        return StdString::EndsWithMatcher( StdString::CasedString( str, caseSensitivity) );
+    }
+    StdString::StartsWithMatcher StartsWith( std::string const& str, CaseSensitive::Choice caseSensitivity ) {
+        return StdString::StartsWithMatcher( StdString::CasedString( str, caseSensitivity) );
+    }
+
+    StdString::RegexMatcher Matches(std::string const& regex, CaseSensitive::Choice caseSensitivity) {
+        return StdString::RegexMatcher(regex, caseSensitivity);
+    }
+
+} // namespace Matchers
+} // namespace Catch
+// end catch_matchers_string.cpp
+// start catch_message.cpp
+
+// start catch_uncaught_exceptions.h
+
+namespace Catch {
+    bool uncaught_exceptions();
+} // end namespace Catch
+
+// end catch_uncaught_exceptions.h
+#include <cassert>
+#include <stack>
+
+namespace Catch {
+
+    MessageInfo::MessageInfo(   StringRef const& _macroName,
+                                SourceLineInfo const& _lineInfo,
+                                ResultWas::OfType _type )
+    :   macroName( _macroName ),
+        lineInfo( _lineInfo ),
+        type( _type ),
+        sequence( ++globalCount )
+    {}
+
+    bool MessageInfo::operator==( MessageInfo const& other ) const {
+        return sequence == other.sequence;
+    }
+
+    bool MessageInfo::operator<( MessageInfo const& other ) const {
+        return sequence < other.sequence;
+    }
+
+    // This may need protecting if threading support is added
+    unsigned int MessageInfo::globalCount = 0;
+
+    ////////////////////////////////////////////////////////////////////////////
+
+    Catch::MessageBuilder::MessageBuilder( StringRef const& macroName,
+                                           SourceLineInfo const& lineInfo,
+                                           ResultWas::OfType type )
+        :m_info(macroName, lineInfo, type) {}
+
+    ////////////////////////////////////////////////////////////////////////////
+
+    ScopedMessage::ScopedMessage( MessageBuilder const& builder )
+    : m_info( builder.m_info )
+    {
+        m_info.message = builder.m_stream.str();
+        getResultCapture().pushScopedMessage( m_info );
+    }
+
+    ScopedMessage::~ScopedMessage() {
+        if ( !uncaught_exceptions() ){
+            getResultCapture().popScopedMessage(m_info);
+        }
+    }
+
+    Capturer::Capturer( StringRef macroName, SourceLineInfo const& lineInfo, ResultWas::OfType resultType, StringRef names ) {
+        auto trimmed = [&] (size_t start, size_t end) {
+            while (names[start] == ',' || isspace(names[start])) {
+                ++start;
+            }
+            while (names[end] == ',' || isspace(names[end])) {
+                --end;
+            }
+            return names.substr(start, end - start + 1);
+        };
+
+        size_t start = 0;
+        std::stack<char> openings;
+        for (size_t pos = 0; pos < names.size(); ++pos) {
+            char c = names[pos];
+            switch (c) {
+            case '[':
+            case '{':
+            case '(':
+            // It is basically impossible to disambiguate between
+            // comparison and start of template args in this context
+//            case '<':
+                openings.push(c);
+                break;
+            case ']':
+            case '}':
+            case ')':
+//           case '>':
+                openings.pop();
+                break;
+            case ',':
+                if (start != pos && openings.size() == 0) {
+                    m_messages.emplace_back(macroName, lineInfo, resultType);
+                    m_messages.back().message = trimmed(start, pos);
+                    m_messages.back().message += " := ";
+                    start = pos;
+                }
+            }
+        }
+        assert(openings.size() == 0 && "Mismatched openings");
+        m_messages.emplace_back(macroName, lineInfo, resultType);
+        m_messages.back().message = trimmed(start, names.size() - 1);
+        m_messages.back().message += " := ";
+    }
+    Capturer::~Capturer() {
+        if ( !uncaught_exceptions() ){
+            assert( m_captured == m_messages.size() );
+            for( size_t i = 0; i < m_captured; ++i  )
+                m_resultCapture.popScopedMessage( m_messages[i] );
+        }
+    }
+
+    void Capturer::captureValue( size_t index, std::string const& value ) {
+        assert( index < m_messages.size() );
+        m_messages[index].message += value;
+        m_resultCapture.pushScopedMessage( m_messages[index] );
+        m_captured++;
+    }
+
+} // end namespace Catch
+// end catch_message.cpp
+// start catch_output_redirect.cpp
+
+// start catch_output_redirect.h
+#ifndef TWOBLUECUBES_CATCH_OUTPUT_REDIRECT_H
+#define TWOBLUECUBES_CATCH_OUTPUT_REDIRECT_H
+
+#include <cstdio>
+#include <iosfwd>
+#include <string>
+
+namespace Catch {
+
+    class RedirectedStream {
+        std::ostream& m_originalStream;
+        std::ostream& m_redirectionStream;
+        std::streambuf* m_prevBuf;
+
+    public:
+        RedirectedStream( std::ostream& originalStream, std::ostream& redirectionStream );
+        ~RedirectedStream();
+    };
+
+    class RedirectedStdOut {
+        ReusableStringStream m_rss;
+        RedirectedStream m_cout;
+    public:
+        RedirectedStdOut();
+        auto str() const -> std::string;
+    };
+
+    // StdErr has two constituent streams in C++, std::cerr and std::clog
+    // This means that we need to redirect 2 streams into 1 to keep proper
+    // order of writes
+    class RedirectedStdErr {
+        ReusableStringStream m_rss;
+        RedirectedStream m_cerr;
+        RedirectedStream m_clog;
+    public:
+        RedirectedStdErr();
+        auto str() const -> std::string;
+    };
+
+#if defined(CATCH_CONFIG_NEW_CAPTURE)
+
+    // Windows's implementation of std::tmpfile is terrible (it tries
+    // to create a file inside system folder, thus requiring elevated
+    // privileges for the binary), so we have to use tmpnam(_s) and
+    // create the file ourselves there.
+    class TempFile {
+    public:
+        TempFile(TempFile const&) = delete;
+        TempFile& operator=(TempFile const&) = delete;
+        TempFile(TempFile&&) = delete;
+        TempFile& operator=(TempFile&&) = delete;
+
+        TempFile();
+        ~TempFile();
+
+        std::FILE* getFile();
+        std::string getContents();
+
+    private:
+        std::FILE* m_file = nullptr;
+    #if defined(_MSC_VER)
+        char m_buffer[L_tmpnam] = { 0 };
+    #endif
+    };
+
+    class OutputRedirect {
+    public:
+        OutputRedirect(OutputRedirect const&) = delete;
+        OutputRedirect& operator=(OutputRedirect const&) = delete;
+        OutputRedirect(OutputRedirect&&) = delete;
+        OutputRedirect& operator=(OutputRedirect&&) = delete;
+
+        OutputRedirect(std::string& stdout_dest, std::string& stderr_dest);
+        ~OutputRedirect();
+
+    private:
+        int m_originalStdout = -1;
+        int m_originalStderr = -1;
+        TempFile m_stdoutFile;
+        TempFile m_stderrFile;
+        std::string& m_stdoutDest;
+        std::string& m_stderrDest;
+    };
+
+#endif
+
+} // end namespace Catch
+
+#endif // TWOBLUECUBES_CATCH_OUTPUT_REDIRECT_H
+// end catch_output_redirect.h
+#include <cstdio>
+#include <cstring>
+#include <fstream>
+#include <sstream>
+#include <stdexcept>
+
+#if defined(CATCH_CONFIG_NEW_CAPTURE)
+    #if defined(_MSC_VER)
+    #include <io.h>      //_dup and _dup2
+    #define dup _dup
+    #define dup2 _dup2
+    #define fileno _fileno
+    #else
+    #include <unistd.h>  // dup and dup2
+    #endif
+#endif
+
+namespace Catch {
+
+    RedirectedStream::RedirectedStream( std::ostream& originalStream, std::ostream& redirectionStream )
+    :   m_originalStream( originalStream ),
+        m_redirectionStream( redirectionStream ),
+        m_prevBuf( m_originalStream.rdbuf() )
+    {
+        m_originalStream.rdbuf( m_redirectionStream.rdbuf() );
+    }
+
+    RedirectedStream::~RedirectedStream() {
+        m_originalStream.rdbuf( m_prevBuf );
+    }
+
+    RedirectedStdOut::RedirectedStdOut() : m_cout( Catch::cout(), m_rss.get() ) {}
+    auto RedirectedStdOut::str() const -> std::string { return m_rss.str(); }
+
+    RedirectedStdErr::RedirectedStdErr()
+    :   m_cerr( Catch::cerr(), m_rss.get() ),
+        m_clog( Catch::clog(), m_rss.get() )
+    {}
+    auto RedirectedStdErr::str() const -> std::string { return m_rss.str(); }
+
+#if defined(CATCH_CONFIG_NEW_CAPTURE)
+
+#if defined(_MSC_VER)
+    TempFile::TempFile() {
+        if (tmpnam_s(m_buffer)) {
+            CATCH_RUNTIME_ERROR("Could not get a temp filename");
+        }
+        if (fopen_s(&m_file, m_buffer, "w")) {
+            char buffer[100];
+            if (strerror_s(buffer, errno)) {
+                CATCH_RUNTIME_ERROR("Could not translate errno to a string");
+            }
+            CATCH_RUNTIME_ERROR("Coul dnot open the temp file: '" << m_buffer << "' because: " << buffer);
+        }
+    }
+#else
+    TempFile::TempFile() {
+        m_file = std::tmpfile();
+        if (!m_file) {
+            CATCH_RUNTIME_ERROR("Could not create a temp file.");
+        }
+    }
+
+#endif
+
+    TempFile::~TempFile() {
+         // TBD: What to do about errors here?
+         std::fclose(m_file);
+         // We manually create the file on Windows only, on Linux
+         // it will be autodeleted
+#if defined(_MSC_VER)
+         std::remove(m_buffer);
+#endif
+    }
+
+    FILE* TempFile::getFile() {
+        return m_file;
+    }
+
+    std::string TempFile::getContents() {
+        std::stringstream sstr;
+        char buffer[100] = {};
+        std::rewind(m_file);
+        while (std::fgets(buffer, sizeof(buffer), m_file)) {
+            sstr << buffer;
+        }
+        return sstr.str();
+    }
+
+    OutputRedirect::OutputRedirect(std::string& stdout_dest, std::string& stderr_dest) :
+        m_originalStdout(dup(1)),
+        m_originalStderr(dup(2)),
+        m_stdoutDest(stdout_dest),
+        m_stderrDest(stderr_dest) {
+        dup2(fileno(m_stdoutFile.getFile()), 1);
+        dup2(fileno(m_stderrFile.getFile()), 2);
+    }
+
+    OutputRedirect::~OutputRedirect() {
+        Catch::cout() << std::flush;
+        fflush(stdout);
+        // Since we support overriding these streams, we flush cerr
+        // even though std::cerr is unbuffered
+        Catch::cerr() << std::flush;
+        Catch::clog() << std::flush;
+        fflush(stderr);
+
+        dup2(m_originalStdout, 1);
+        dup2(m_originalStderr, 2);
+
+        m_stdoutDest += m_stdoutFile.getContents();
+        m_stderrDest += m_stderrFile.getContents();
+    }
+
+#endif // CATCH_CONFIG_NEW_CAPTURE
+
+} // namespace Catch
+
+#if defined(CATCH_CONFIG_NEW_CAPTURE)
+    #if defined(_MSC_VER)
+    #undef dup
+    #undef dup2
+    #undef fileno
+    #endif
+#endif
+// end catch_output_redirect.cpp
+// start catch_polyfills.cpp
+
+#include <cmath>
+
+namespace Catch {
+
+#if !defined(CATCH_CONFIG_POLYFILL_ISNAN)
+    bool isnan(float f) {
+        return std::isnan(f);
+    }
+    bool isnan(double d) {
+        return std::isnan(d);
+    }
+#else
+    // For now we only use this for embarcadero
+    bool isnan(float f) {
+        return std::_isnan(f);
+    }
+    bool isnan(double d) {
+        return std::_isnan(d);
+    }
+#endif
+
+} // end namespace Catch
+// end catch_polyfills.cpp
+// start catch_random_number_generator.cpp
+
+namespace Catch {
+
+    std::mt19937& rng() {
+        static std::mt19937 s_rng;
+        return s_rng;
+    }
+
+    void seedRng( IConfig const& config ) {
+        if( config.rngSeed() != 0 ) {
+            std::srand( config.rngSeed() );
+            rng().seed( config.rngSeed() );
+        }
+    }
+
+    unsigned int rngSeed() {
+        return getCurrentContext().getConfig()->rngSeed();
+    }
+}
+// end catch_random_number_generator.cpp
+// start catch_registry_hub.cpp
+
+// start catch_test_case_registry_impl.h
+
+#include <vector>
+#include <set>
+#include <algorithm>
+#include <ios>
+
+namespace Catch {
+
+    class TestCase;
+    struct IConfig;
+
+    std::vector<TestCase> sortTests( IConfig const& config, std::vector<TestCase> const& unsortedTestCases );
+    bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config );
+
+    void enforceNoDuplicateTestCases( std::vector<TestCase> const& functions );
+
+    std::vector<TestCase> filterTests( std::vector<TestCase> const& testCases, TestSpec const& testSpec, IConfig const& config );
+    std::vector<TestCase> const& getAllTestCasesSorted( IConfig const& config );
+
+    class TestRegistry : public ITestCaseRegistry {
+    public:
+        virtual ~TestRegistry() = default;
+
+        virtual void registerTest( TestCase const& testCase );
+
+        std::vector<TestCase> const& getAllTests() const override;
+        std::vector<TestCase> const& getAllTestsSorted( IConfig const& config ) const override;
+
+    private:
+        std::vector<TestCase> m_functions;
+        mutable RunTests::InWhatOrder m_currentSortOrder = RunTests::InDeclarationOrder;
+        mutable std::vector<TestCase> m_sortedFunctions;
+        std::size_t m_unnamedCount = 0;
+        std::ios_base::Init m_ostreamInit; // Forces cout/ cerr to be initialised
+    };
+
+    ///////////////////////////////////////////////////////////////////////////
+
+    class TestInvokerAsFunction : public ITestInvoker {
+        void(*m_testAsFunction)();
+    public:
+        TestInvokerAsFunction( void(*testAsFunction)() ) noexcept;
+
+        void invoke() const override;
+    };
+
+    std::string extractClassName( StringRef const& classOrQualifiedMethodName );
+
+    ///////////////////////////////////////////////////////////////////////////
+
+} // end namespace Catch
+
+// end catch_test_case_registry_impl.h
+// start catch_reporter_registry.h
+
+#include <map>
+
+namespace Catch {
+
+    class ReporterRegistry : public IReporterRegistry {
+
+    public:
+
+        ~ReporterRegistry() override;
+
+        IStreamingReporterPtr create( std::string const& name, IConfigPtr const& config ) const override;
+
+        void registerReporter( std::string const& name, IReporterFactoryPtr const& factory );
+        void registerListener( IReporterFactoryPtr const& factory );
+
+        FactoryMap const& getFactories() const override;
+        Listeners const& getListeners() const override;
+
+    private:
+        FactoryMap m_factories;
+        Listeners m_listeners;
+    };
+}
+
+// end catch_reporter_registry.h
+// start catch_tag_alias_registry.h
+
+// start catch_tag_alias.h
+
+#include <string>
+
+namespace Catch {
+
+    struct TagAlias {
+        TagAlias(std::string const& _tag, SourceLineInfo _lineInfo);
+
+        std::string tag;
+        SourceLineInfo lineInfo;
+    };
+
+} // end namespace Catch
+
+// end catch_tag_alias.h
+#include <map>
+
+namespace Catch {
+
+    class TagAliasRegistry : public ITagAliasRegistry {
+    public:
+        ~TagAliasRegistry() override;
+        TagAlias const* find( std::string const& alias ) const override;
+        std::string expandAliases( std::string const& unexpandedTestSpec ) const override;
+        void add( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo );
+
+    private:
+        std::map<std::string, TagAlias> m_registry;
+    };
+
+} // end namespace Catch
+
+// end catch_tag_alias_registry.h
+// start catch_startup_exception_registry.h
+
+#include <vector>
+#include <exception>
+
+namespace Catch {
+
+    class StartupExceptionRegistry {
+    public:
+        void add(std::exception_ptr const& exception) noexcept;
+        std::vector<std::exception_ptr> const& getExceptions() const noexcept;
+    private:
+        std::vector<std::exception_ptr> m_exceptions;
+    };
+
+} // end namespace Catch
+
+// end catch_startup_exception_registry.h
+// start catch_singletons.hpp
+
+namespace Catch {
+
+    struct ISingleton {
+        virtual ~ISingleton();
+    };
+
+    void addSingleton( ISingleton* singleton );
+    void cleanupSingletons();
+
+    template<typename SingletonImplT, typename InterfaceT = SingletonImplT, typename MutableInterfaceT = InterfaceT>
+    class Singleton : SingletonImplT, public ISingleton {
+
+        static auto getInternal() -> Singleton* {
+            static Singleton* s_instance = nullptr;
+            if( !s_instance ) {
+                s_instance = new Singleton;
+                addSingleton( s_instance );
+            }
+            return s_instance;
+        }
+
+    public:
+        static auto get() -> InterfaceT const& {
+            return *getInternal();
+        }
+        static auto getMutable() -> MutableInterfaceT& {
+            return *getInternal();
+        }
+    };
+
+} // namespace Catch
+
+// end catch_singletons.hpp
+namespace Catch {
+
+    namespace {
+
+        class RegistryHub : public IRegistryHub, public IMutableRegistryHub,
+                            private NonCopyable {
+
+        public: // IRegistryHub
+            RegistryHub() = default;
+            IReporterRegistry const& getReporterRegistry() const override {
+                return m_reporterRegistry;
+            }
+            ITestCaseRegistry const& getTestCaseRegistry() const override {
+                return m_testCaseRegistry;
+            }
+            IExceptionTranslatorRegistry const& getExceptionTranslatorRegistry() const override {
+                return m_exceptionTranslatorRegistry;
+            }
+            ITagAliasRegistry const& getTagAliasRegistry() const override {
+                return m_tagAliasRegistry;
+            }
+            StartupExceptionRegistry const& getStartupExceptionRegistry() const override {
+                return m_exceptionRegistry;
+            }
+
+        public: // IMutableRegistryHub
+            void registerReporter( std::string const& name, IReporterFactoryPtr const& factory ) override {
+                m_reporterRegistry.registerReporter( name, factory );
+            }
+            void registerListener( IReporterFactoryPtr const& factory ) override {
+                m_reporterRegistry.registerListener( factory );
+            }
+            void registerTest( TestCase const& testInfo ) override {
+                m_testCaseRegistry.registerTest( testInfo );
+            }
+            void registerTranslator( const IExceptionTranslator* translator ) override {
+                m_exceptionTranslatorRegistry.registerTranslator( translator );
+            }
+            void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) override {
+                m_tagAliasRegistry.add( alias, tag, lineInfo );
+            }
+            void registerStartupException() noexcept override {
+                m_exceptionRegistry.add(std::current_exception());
+            }
+
+        private:
+            TestRegistry m_testCaseRegistry;
+            ReporterRegistry m_reporterRegistry;
+            ExceptionTranslatorRegistry m_exceptionTranslatorRegistry;
+            TagAliasRegistry m_tagAliasRegistry;
+            StartupExceptionRegistry m_exceptionRegistry;
+        };
+    }
+
+    using RegistryHubSingleton = Singleton<RegistryHub, IRegistryHub, IMutableRegistryHub>;
+
+    IRegistryHub const& getRegistryHub() {
+        return RegistryHubSingleton::get();
+    }
+    IMutableRegistryHub& getMutableRegistryHub() {
+        return RegistryHubSingleton::getMutable();
+    }
+    void cleanUp() {
+        cleanupSingletons();
+        cleanUpContext();
+    }
+    std::string translateActiveException() {
+        return getRegistryHub().getExceptionTranslatorRegistry().translateActiveException();
+    }
+
+} // end namespace Catch
+// end catch_registry_hub.cpp
+// start catch_reporter_registry.cpp
+
+namespace Catch {
+
+    ReporterRegistry::~ReporterRegistry() = default;
+
+    IStreamingReporterPtr ReporterRegistry::create( std::string const& name, IConfigPtr const& config ) const {
+        auto it =  m_factories.find( name );
+        if( it == m_factories.end() )
+            return nullptr;
+        return it->second->create( ReporterConfig( config ) );
+    }
+
+    void ReporterRegistry::registerReporter( std::string const& name, IReporterFactoryPtr const& factory ) {
+        m_factories.emplace(name, factory);
+    }
+    void ReporterRegistry::registerListener( IReporterFactoryPtr const& factory ) {
+        m_listeners.push_back( factory );
+    }
+
+    IReporterRegistry::FactoryMap const& ReporterRegistry::getFactories() const {
+        return m_factories;
+    }
+    IReporterRegistry::Listeners const& ReporterRegistry::getListeners() const {
+        return m_listeners;
+    }
+
+}
+// end catch_reporter_registry.cpp
+// start catch_result_type.cpp
+
+namespace Catch {
+
+    bool isOk( ResultWas::OfType resultType ) {
+        return ( resultType & ResultWas::FailureBit ) == 0;
+    }
+    bool isJustInfo( int flags ) {
+        return flags == ResultWas::Info;
+    }
+
+    ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs ) {
+        return static_cast<ResultDisposition::Flags>( static_cast<int>( lhs ) | static_cast<int>( rhs ) );
+    }
+
+    bool shouldContinueOnFailure( int flags )    { return ( flags & ResultDisposition::ContinueOnFailure ) != 0; }
+    bool shouldSuppressFailure( int flags )      { return ( flags & ResultDisposition::SuppressFail ) != 0; }
+
+} // end namespace Catch
+// end catch_result_type.cpp
+// start catch_run_context.cpp
+
+#include <cassert>
+#include <algorithm>
+#include <sstream>
+
+namespace Catch {
+
+    namespace Generators {
+        struct GeneratorTracker : TestCaseTracking::TrackerBase, IGeneratorTracker {
+            size_t m_index = static_cast<size_t>( -1 );
+            GeneratorBasePtr m_generator;
+
+            GeneratorTracker( TestCaseTracking::NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent )
+            :   TrackerBase( nameAndLocation, ctx, parent )
+            {}
+            ~GeneratorTracker();
+
+            static GeneratorTracker& acquire( TrackerContext& ctx, TestCaseTracking::NameAndLocation const& nameAndLocation ) {
+                std::shared_ptr<GeneratorTracker> tracker;
+
+                ITracker& currentTracker = ctx.currentTracker();
+                if( TestCaseTracking::ITrackerPtr childTracker = currentTracker.findChild( nameAndLocation ) ) {
+                    assert( childTracker );
+                    assert( childTracker->isIndexTracker() );
+                    tracker = std::static_pointer_cast<GeneratorTracker>( childTracker );
+                }
+                else {
+                    tracker = std::make_shared<GeneratorTracker>( nameAndLocation, ctx, &currentTracker );
+                    currentTracker.addChild( tracker );
+                }
+
+                if( !ctx.completedCycle() && !tracker->isComplete() ) {
+                    if( tracker->m_runState != ExecutingChildren && tracker->m_runState != NeedsAnotherRun )
+                        tracker->moveNext();
+                    tracker->open();
+                }
+
+                return *tracker;
+            }
+
+            void moveNext() {
+                m_index++;
+                m_children.clear();
+            }
+
+            // TrackerBase interface
+            bool isIndexTracker() const override { return true; }
+            auto hasGenerator() const -> bool override {
+                return !!m_generator;
+            }
+            void close() override {
+                TrackerBase::close();
+                if( m_runState == CompletedSuccessfully && m_index < m_generator->size()-1 )
+                    m_runState = Executing;
+            }
+
+            // IGeneratorTracker interface
+            auto getGenerator() const -> GeneratorBasePtr const& override {
+                return m_generator;
+            }
+            void setGenerator( GeneratorBasePtr&& generator ) override {
+                m_generator = std::move( generator );
+            }
+            auto getIndex() const -> size_t override {
+                return m_index;
+            }
+        };
+        GeneratorTracker::~GeneratorTracker() {}
+    }
+
+    RunContext::RunContext(IConfigPtr const& _config, IStreamingReporterPtr&& reporter)
+    :   m_runInfo(_config->name()),
+        m_context(getCurrentMutableContext()),
+        m_config(_config),
+        m_reporter(std::move(reporter)),
+        m_lastAssertionInfo{ StringRef(), SourceLineInfo("",0), StringRef(), ResultDisposition::Normal },
+        m_includeSuccessfulResults( m_config->includeSuccessfulResults() || m_reporter->getPreferences().shouldReportAllAssertions )
+    {
+        m_context.setRunner(this);
+        m_context.setConfig(m_config);
+        m_context.setResultCapture(this);
+        m_reporter->testRunStarting(m_runInfo);
+    }
+
+    RunContext::~RunContext() {
+        m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, aborting()));
+    }
+
+    void RunContext::testGroupStarting(std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount) {
+        m_reporter->testGroupStarting(GroupInfo(testSpec, groupIndex, groupsCount));
+    }
+
+    void RunContext::testGroupEnded(std::string const& testSpec, Totals const& totals, std::size_t groupIndex, std::size_t groupsCount) {
+        m_reporter->testGroupEnded(TestGroupStats(GroupInfo(testSpec, groupIndex, groupsCount), totals, aborting()));
+    }
+
+    Totals RunContext::runTest(TestCase const& testCase) {
+        Totals prevTotals = m_totals;
+
+        std::string redirectedCout;
+        std::string redirectedCerr;
+
+        auto const& testInfo = testCase.getTestCaseInfo();
+
+        m_reporter->testCaseStarting(testInfo);
+
+        m_activeTestCase = &testCase;
+
+        ITracker& rootTracker = m_trackerContext.startRun();
+        assert(rootTracker.isSectionTracker());
+        static_cast<SectionTracker&>(rootTracker).addInitialFilters(m_config->getSectionsToRun());
+        do {
+            m_trackerContext.startCycle();
+            m_testCaseTracker = &SectionTracker::acquire(m_trackerContext, TestCaseTracking::NameAndLocation(testInfo.name, testInfo.lineInfo));
+            runCurrentTest(redirectedCout, redirectedCerr);
+        } while (!m_testCaseTracker->isSuccessfullyCompleted() && !aborting());
+
+        Totals deltaTotals = m_totals.delta(prevTotals);
+        if (testInfo.expectedToFail() && deltaTotals.testCases.passed > 0) {
+            deltaTotals.assertions.failed++;
+            deltaTotals.testCases.passed--;
+            deltaTotals.testCases.failed++;
+        }
+        m_totals.testCases += deltaTotals.testCases;
+        m_reporter->testCaseEnded(TestCaseStats(testInfo,
+                                  deltaTotals,
+                                  redirectedCout,
+                                  redirectedCerr,
+                                  aborting()));
+
+        m_activeTestCase = nullptr;
+        m_testCaseTracker = nullptr;
+
+        return deltaTotals;
+    }
+
+    IConfigPtr RunContext::config() const {
+        return m_config;
+    }
+
+    IStreamingReporter& RunContext::reporter() const {
+        return *m_reporter;
+    }
+
+    void RunContext::assertionEnded(AssertionResult const & result) {
+        if (result.getResultType() == ResultWas::Ok) {
+            m_totals.assertions.passed++;
+            m_lastAssertionPassed = true;
+        } else if (!result.isOk()) {
+            m_lastAssertionPassed = false;
+            if( m_activeTestCase->getTestCaseInfo().okToFail() )
+                m_totals.assertions.failedButOk++;
+            else
+                m_totals.assertions.failed++;
+        }
+        else {
+            m_lastAssertionPassed = true;
+        }
+
+        // We have no use for the return value (whether messages should be cleared), because messages were made scoped
+        // and should be let to clear themselves out.
+        static_cast<void>(m_reporter->assertionEnded(AssertionStats(result, m_messages, m_totals)));
+
+        // Reset working state
+        resetAssertionInfo();
+        m_lastResult = result;
+    }
+    void RunContext::resetAssertionInfo() {
+        m_lastAssertionInfo.macroName = StringRef();
+        m_lastAssertionInfo.capturedExpression = "{Unknown expression after the reported line}"_sr;
+    }
+
+    bool RunContext::sectionStarted(SectionInfo const & sectionInfo, Counts & assertions) {
+        ITracker& sectionTracker = SectionTracker::acquire(m_trackerContext, TestCaseTracking::NameAndLocation(sectionInfo.name, sectionInfo.lineInfo));
+        if (!sectionTracker.isOpen())
+            return false;
+        m_activeSections.push_back(&sectionTracker);
+
+        m_lastAssertionInfo.lineInfo = sectionInfo.lineInfo;
+
+        m_reporter->sectionStarting(sectionInfo);
+
+        assertions = m_totals.assertions;
+
+        return true;
+    }
+    auto RunContext::acquireGeneratorTracker( SourceLineInfo const& lineInfo ) -> IGeneratorTracker& {
+        using namespace Generators;
+        GeneratorTracker& tracker = GeneratorTracker::acquire( m_trackerContext, TestCaseTracking::NameAndLocation( "generator", lineInfo ) );
+        assert( tracker.isOpen() );
+        m_lastAssertionInfo.lineInfo = lineInfo;
+        return tracker;
+    }
+
+    bool RunContext::testForMissingAssertions(Counts& assertions) {
+        if (assertions.total() != 0)
+            return false;
+        if (!m_config->warnAboutMissingAssertions())
+            return false;
+        if (m_trackerContext.currentTracker().hasChildren())
+            return false;
+        m_totals.assertions.failed++;
+        assertions.failed++;
+        return true;
+    }
+
+    void RunContext::sectionEnded(SectionEndInfo const & endInfo) {
+        Counts assertions = m_totals.assertions - endInfo.prevAssertions;
+        bool missingAssertions = testForMissingAssertions(assertions);
+
+        if (!m_activeSections.empty()) {
+            m_activeSections.back()->close();
+            m_activeSections.pop_back();
+        }
+
+        m_reporter->sectionEnded(SectionStats(endInfo.sectionInfo, assertions, endInfo.durationInSeconds, missingAssertions));
+        m_messages.clear();
+    }
+
+    void RunContext::sectionEndedEarly(SectionEndInfo const & endInfo) {
+        if (m_unfinishedSections.empty())
+            m_activeSections.back()->fail();
+        else
+            m_activeSections.back()->close();
+        m_activeSections.pop_back();
+
+        m_unfinishedSections.push_back(endInfo);
+    }
+    void RunContext::benchmarkStarting( BenchmarkInfo const& info ) {
+        m_reporter->benchmarkStarting( info );
+    }
+    void RunContext::benchmarkEnded( BenchmarkStats const& stats ) {
+        m_reporter->benchmarkEnded( stats );
+    }
+
+    void RunContext::pushScopedMessage(MessageInfo const & message) {
+        m_messages.push_back(message);
+    }
+
+    void RunContext::popScopedMessage(MessageInfo const & message) {
+        m_messages.erase(std::remove(m_messages.begin(), m_messages.end(), message), m_messages.end());
+    }
+
+    std::string RunContext::getCurrentTestName() const {
+        return m_activeTestCase
+            ? m_activeTestCase->getTestCaseInfo().name
+            : std::string();
+    }
+
+    const AssertionResult * RunContext::getLastResult() const {
+        return &(*m_lastResult);
+    }
+
+    void RunContext::exceptionEarlyReported() {
+        m_shouldReportUnexpected = false;
+    }
+
+    void RunContext::handleFatalErrorCondition( StringRef message ) {
+        // First notify reporter that bad things happened
+        m_reporter->fatalErrorEncountered(message);
+
+        // Don't rebuild the result -- the stringification itself can cause more fatal errors
+        // Instead, fake a result data.
+        AssertionResultData tempResult( ResultWas::FatalErrorCondition, { false } );
+        tempResult.message = message;
+        AssertionResult result(m_lastAssertionInfo, tempResult);
+
+        assertionEnded(result);
+
+        handleUnfinishedSections();
+
+        // Recreate section for test case (as we will lose the one that was in scope)
+        auto const& testCaseInfo = m_activeTestCase->getTestCaseInfo();
+        SectionInfo testCaseSection(testCaseInfo.lineInfo, testCaseInfo.name);
+
+        Counts assertions;
+        assertions.failed = 1;
+        SectionStats testCaseSectionStats(testCaseSection, assertions, 0, false);
+        m_reporter->sectionEnded(testCaseSectionStats);
+
+        auto const& testInfo = m_activeTestCase->getTestCaseInfo();
+
+        Totals deltaTotals;
+        deltaTotals.testCases.failed = 1;
+        deltaTotals.assertions.failed = 1;
+        m_reporter->testCaseEnded(TestCaseStats(testInfo,
+                                  deltaTotals,
+                                  std::string(),
+                                  std::string(),
+                                  false));
+        m_totals.testCases.failed++;
+        testGroupEnded(std::string(), m_totals, 1, 1);
+        m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, false));
+    }
+
+    bool RunContext::lastAssertionPassed() {
+         return m_lastAssertionPassed;
+    }
+
+    void RunContext::assertionPassed() {
+        m_lastAssertionPassed = true;
+        ++m_totals.assertions.passed;
+        resetAssertionInfo();
+    }
+
+    bool RunContext::aborting() const {
+        return m_totals.assertions.failed >= static_cast<std::size_t>(m_config->abortAfter());
+    }
+
+    void RunContext::runCurrentTest(std::string & redirectedCout, std::string & redirectedCerr) {
+        auto const& testCaseInfo = m_activeTestCase->getTestCaseInfo();
+        SectionInfo testCaseSection(testCaseInfo.lineInfo, testCaseInfo.name);
+        m_reporter->sectionStarting(testCaseSection);
+        Counts prevAssertions = m_totals.assertions;
+        double duration = 0;
+        m_shouldReportUnexpected = true;
+        m_lastAssertionInfo = { "TEST_CASE"_sr, testCaseInfo.lineInfo, StringRef(), ResultDisposition::Normal };
+
+        seedRng(*m_config);
+
+        Timer timer;
+        CATCH_TRY {
+            if (m_reporter->getPreferences().shouldRedirectStdOut) {
+#if !defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT)
+                RedirectedStdOut redirectedStdOut;
+                RedirectedStdErr redirectedStdErr;
+
+                timer.start();
+                invokeActiveTestCase();
+                redirectedCout += redirectedStdOut.str();
+                redirectedCerr += redirectedStdErr.str();
+#else
+                OutputRedirect r(redirectedCout, redirectedCerr);
+                timer.start();
+                invokeActiveTestCase();
+#endif
+            } else {
+                timer.start();
+                invokeActiveTestCase();
+            }
+            duration = timer.getElapsedSeconds();
+        } CATCH_CATCH_ANON (TestFailureException&) {
+            // This just means the test was aborted due to failure
+        } CATCH_CATCH_ALL {
+            // Under CATCH_CONFIG_FAST_COMPILE, unexpected exceptions under REQUIRE assertions
+            // are reported without translation at the point of origin.
+            if( m_shouldReportUnexpected ) {
+                AssertionReaction dummyReaction;
+                handleUnexpectedInflightException( m_lastAssertionInfo, translateActiveException(), dummyReaction );
+            }
+        }
+        Counts assertions = m_totals.assertions - prevAssertions;
+        bool missingAssertions = testForMissingAssertions(assertions);
+
+        m_testCaseTracker->close();
+        handleUnfinishedSections();
+        m_messages.clear();
+
+        SectionStats testCaseSectionStats(testCaseSection, assertions, duration, missingAssertions);
+        m_reporter->sectionEnded(testCaseSectionStats);
+    }
+
+    void RunContext::invokeActiveTestCase() {
+        FatalConditionHandler fatalConditionHandler; // Handle signals
+        m_activeTestCase->invoke();
+        fatalConditionHandler.reset();
+    }
+
+    void RunContext::handleUnfinishedSections() {
+        // If sections ended prematurely due to an exception we stored their
+        // infos here so we can tear them down outside the unwind process.
+        for (auto it = m_unfinishedSections.rbegin(),
+             itEnd = m_unfinishedSections.rend();
+             it != itEnd;
+             ++it)
+            sectionEnded(*it);
+        m_unfinishedSections.clear();
+    }
+
+    void RunContext::handleExpr(
+        AssertionInfo const& info,
+        ITransientExpression const& expr,
+        AssertionReaction& reaction
+    ) {
+        m_reporter->assertionStarting( info );
+
+        bool negated = isFalseTest( info.resultDisposition );
+        bool result = expr.getResult() != negated;
+
+        if( result ) {
+            if (!m_includeSuccessfulResults) {
+                assertionPassed();
+            }
+            else {
+                reportExpr(info, ResultWas::Ok, &expr, negated);
+            }
+        }
+        else {
+            reportExpr(info, ResultWas::ExpressionFailed, &expr, negated );
+            populateReaction( reaction );
+        }
+    }
+    void RunContext::reportExpr(
+            AssertionInfo const &info,
+            ResultWas::OfType resultType,
+            ITransientExpression const *expr,
+            bool negated ) {
+
+        m_lastAssertionInfo = info;
+        AssertionResultData data( resultType, LazyExpression( negated ) );
+
+        AssertionResult assertionResult{ info, data };
+        assertionResult.m_resultData.lazyExpression.m_transientExpression = expr;
+
+        assertionEnded( assertionResult );
+    }
+
+    void RunContext::handleMessage(
+            AssertionInfo const& info,
+            ResultWas::OfType resultType,
+            StringRef const& message,
+            AssertionReaction& reaction
+    ) {
+        m_reporter->assertionStarting( info );
+
+        m_lastAssertionInfo = info;
+
+        AssertionResultData data( resultType, LazyExpression( false ) );
+        data.message = message;
+        AssertionResult assertionResult{ m_lastAssertionInfo, data };
+        assertionEnded( assertionResult );
+        if( !assertionResult.isOk() )
+            populateReaction( reaction );
+    }
+    void RunContext::handleUnexpectedExceptionNotThrown(
+            AssertionInfo const& info,
+            AssertionReaction& reaction
+    ) {
+        handleNonExpr(info, Catch::ResultWas::DidntThrowException, reaction);
+    }
+
+    void RunContext::handleUnexpectedInflightException(
+            AssertionInfo const& info,
+            std::string const& message,
+            AssertionReaction& reaction
+    ) {
+        m_lastAssertionInfo = info;
+
+        AssertionResultData data( ResultWas::ThrewException, LazyExpression( false ) );
+        data.message = message;
+        AssertionResult assertionResult{ info, data };
+        assertionEnded( assertionResult );
+        populateReaction( reaction );
+    }
+
+    void RunContext::populateReaction( AssertionReaction& reaction ) {
+        reaction.shouldDebugBreak = m_config->shouldDebugBreak();
+        reaction.shouldThrow = aborting() || (m_lastAssertionInfo.resultDisposition & ResultDisposition::Normal);
+    }
+
+    void RunContext::handleIncomplete(
+            AssertionInfo const& info
+    ) {
+        m_lastAssertionInfo = info;
+
+        AssertionResultData data( ResultWas::ThrewException, LazyExpression( false ) );
+        data.message = "Exception translation was disabled by CATCH_CONFIG_FAST_COMPILE";
+        AssertionResult assertionResult{ info, data };
+        assertionEnded( assertionResult );
+    }
+    void RunContext::handleNonExpr(
+            AssertionInfo const &info,
+            ResultWas::OfType resultType,
+            AssertionReaction &reaction
+    ) {
+        m_lastAssertionInfo = info;
+
+        AssertionResultData data( resultType, LazyExpression( false ) );
+        AssertionResult assertionResult{ info, data };
+        assertionEnded( assertionResult );
+
+        if( !assertionResult.isOk() )
+            populateReaction( reaction );
+    }
+
+    IResultCapture& getResultCapture() {
+        if (auto* capture = getCurrentContext().getResultCapture())
+            return *capture;
+        else
+            CATCH_INTERNAL_ERROR("No result capture instance");
+    }
+}
+// end catch_run_context.cpp
+// start catch_section.cpp
+
+namespace Catch {
+
+    Section::Section( SectionInfo const& info )
+    :   m_info( info ),
+        m_sectionIncluded( getResultCapture().sectionStarted( m_info, m_assertions ) )
+    {
+        m_timer.start();
+    }
+
+    Section::~Section() {
+        if( m_sectionIncluded ) {
+            SectionEndInfo endInfo{ m_info, m_assertions, m_timer.getElapsedSeconds() };
+            if( uncaught_exceptions() )
+                getResultCapture().sectionEndedEarly( endInfo );
+            else
+                getResultCapture().sectionEnded( endInfo );
+        }
+    }
+
+    // This indicates whether the section should be executed or not
+    Section::operator bool() const {
+        return m_sectionIncluded;
+    }
+
+} // end namespace Catch
+// end catch_section.cpp
+// start catch_section_info.cpp
+
+namespace Catch {
+
+    SectionInfo::SectionInfo
+        (   SourceLineInfo const& _lineInfo,
+            std::string const& _name )
+    :   name( _name ),
+        lineInfo( _lineInfo )
+    {}
+
+} // end namespace Catch
+// end catch_section_info.cpp
+// start catch_session.cpp
+
+// start catch_session.h
+
+#include <memory>
+
+namespace Catch {
+
+    class Session : NonCopyable {
+    public:
+
+        Session();
+        ~Session() override;
+
+        void showHelp() const;
+        void libIdentify();
+
+        int applyCommandLine( int argc, char const * const * argv );
+    #if defined(CATCH_CONFIG_WCHAR) && defined(WIN32) && defined(UNICODE)
+        int applyCommandLine( int argc, wchar_t const * const * argv );
+    #endif
+
+        void useConfigData( ConfigData const& configData );
+
+        template<typename CharT>
+        int run(int argc, CharT const * const argv[]) {
+            if (m_startupExceptions)
+                return 1;
+            int returnCode = applyCommandLine(argc, argv);
+            if (returnCode == 0)
+                returnCode = run();
+            return returnCode;
+        }
+
+        int run();
+
+        clara::Parser const& cli() const;
+        void cli( clara::Parser const& newParser );
+        ConfigData& configData();
+        Config& config();
+    private:
+        int runInternal();
+
+        clara::Parser m_cli;
+        ConfigData m_configData;
+        std::shared_ptr<Config> m_config;
+        bool m_startupExceptions = false;
+    };
+
+} // end namespace Catch
+
+// end catch_session.h
+// start catch_version.h
+
+#include <iosfwd>
+
+namespace Catch {
+
+    // Versioning information
+    struct Version {
+        Version( Version const& ) = delete;
+        Version& operator=( Version const& ) = delete;
+        Version(    unsigned int _majorVersion,
+                    unsigned int _minorVersion,
+                    unsigned int _patchNumber,
+                    char const * const _branchName,
+                    unsigned int _buildNumber );
+
+        unsigned int const majorVersion;
+        unsigned int const minorVersion;
+        unsigned int const patchNumber;
+
+        // buildNumber is only used if branchName is not null
+        char const * const branchName;
+        unsigned int const buildNumber;
+
+        friend std::ostream& operator << ( std::ostream& os, Version const& version );
+    };
+
+    Version const& libraryVersion();
+}
+
+// end catch_version.h
+#include <cstdlib>
+#include <iomanip>
+
+namespace Catch {
+
+    namespace {
+        const int MaxExitCode = 255;
+
+        IStreamingReporterPtr createReporter(std::string const& reporterName, IConfigPtr const& config) {
+            auto reporter = Catch::getRegistryHub().getReporterRegistry().create(reporterName, config);
+            CATCH_ENFORCE(reporter, "No reporter registered with name: '" << reporterName << "'");
+
+            return reporter;
+        }
+
+        IStreamingReporterPtr makeReporter(std::shared_ptr<Config> const& config) {
+            if (Catch::getRegistryHub().getReporterRegistry().getListeners().empty()) {
+                return createReporter(config->getReporterName(), config);
+            }
+
+            auto multi = std::unique_ptr<ListeningReporter>(new ListeningReporter);
+
+            auto const& listeners = Catch::getRegistryHub().getReporterRegistry().getListeners();
+            for (auto const& listener : listeners) {
+                multi->addListener(listener->create(Catch::ReporterConfig(config)));
+            }
+            multi->addReporter(createReporter(config->getReporterName(), config));
+            return std::move(multi);
+        }
+
+        Catch::Totals runTests(std::shared_ptr<Config> const& config) {
+            auto reporter = makeReporter(config);
+
+            RunContext context(config, std::move(reporter));
+
+            Totals totals;
+
+            context.testGroupStarting(config->name(), 1, 1);
+
+            TestSpec testSpec = config->testSpec();
+
+            auto const& allTestCases = getAllTestCasesSorted(*config);
+            for (auto const& testCase : allTestCases) {
+                if (!context.aborting() && matchTest(testCase, testSpec, *config))
+                    totals += context.runTest(testCase);
+                else
+                    context.reporter().skipTest(testCase);
+            }
+
+            if (config->warnAboutNoTests() && totals.testCases.total() == 0) {
+                ReusableStringStream testConfig;
+
+                bool first = true;
+                for (const auto& input : config->getTestsOrTags()) {
+                    if (!first) { testConfig << ' '; }
+                    first = false;
+                    testConfig << input;
+                }
+
+                context.reporter().noMatchingTestCases(testConfig.str());
+                totals.error = -1;
+            }
+
+            context.testGroupEnded(config->name(), totals, 1, 1);
+            return totals;
+        }
+
+        void applyFilenamesAsTags(Catch::IConfig const& config) {
+            auto& tests = const_cast<std::vector<TestCase>&>(getAllTestCasesSorted(config));
+            for (auto& testCase : tests) {
+                auto tags = testCase.tags;
+
+                std::string filename = testCase.lineInfo.file;
+                auto lastSlash = filename.find_last_of("\\/");
+                if (lastSlash != std::string::npos) {
+                    filename.erase(0, lastSlash);
+                    filename[0] = '#';
+                }
+
+                auto lastDot = filename.find_last_of('.');
+                if (lastDot != std::string::npos) {
+                    filename.erase(lastDot);
+                }
+
+                tags.push_back(std::move(filename));
+                setTags(testCase, tags);
+            }
+        }
+
+    } // anon namespace
+
+    Session::Session() {
+        static bool alreadyInstantiated = false;
+        if( alreadyInstantiated ) {
+            CATCH_TRY { CATCH_INTERNAL_ERROR( "Only one instance of Catch::Session can ever be used" ); }
+            CATCH_CATCH_ALL { getMutableRegistryHub().registerStartupException(); }
+        }
+
+        // There cannot be exceptions at startup in no-exception mode.
+#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)
+        const auto& exceptions = getRegistryHub().getStartupExceptionRegistry().getExceptions();
+        if ( !exceptions.empty() ) {
+            m_startupExceptions = true;
+            Colour colourGuard( Colour::Red );
+            Catch::cerr() << "Errors occurred during startup!" << '\n';
+            // iterate over all exceptions and notify user
+            for ( const auto& ex_ptr : exceptions ) {
+                try {
+                    std::rethrow_exception(ex_ptr);
+                } catch ( std::exception const& ex ) {
+                    Catch::cerr() << Column( ex.what() ).indent(2) << '\n';
+                }
+            }
+        }
+#endif
+
+        alreadyInstantiated = true;
+        m_cli = makeCommandLineParser( m_configData );
+    }
+    Session::~Session() {
+        Catch::cleanUp();
+    }
+
+    void Session::showHelp() const {
+        Catch::cout()
+                << "\nCatch v" << libraryVersion() << "\n"
+                << m_cli << std::endl
+                << "For more detailed usage please see the project docs\n" << std::endl;
+    }
+    void Session::libIdentify() {
+        Catch::cout()
+                << std::left << std::setw(16) << "description: " << "A Catch test executable\n"
+                << std::left << std::setw(16) << "category: " << "testframework\n"
+                << std::left << std::setw(16) << "framework: " << "Catch Test\n"
+                << std::left << std::setw(16) << "version: " << libraryVersion() << std::endl;
+    }
+
+    int Session::applyCommandLine( int argc, char const * const * argv ) {
+        if( m_startupExceptions )
+            return 1;
+
+        auto result = m_cli.parse( clara::Args( argc, argv ) );
+        if( !result ) {
+            Catch::cerr()
+                << Colour( Colour::Red )
+                << "\nError(s) in input:\n"
+                << Column( result.errorMessage() ).indent( 2 )
+                << "\n\n";
+            Catch::cerr() << "Run with -? for usage\n" << std::endl;
+            return MaxExitCode;
+        }
+
+        if( m_configData.showHelp )
+            showHelp();
+        if( m_configData.libIdentify )
+            libIdentify();
+        m_config.reset();
+        return 0;
+    }
+
+#if defined(CATCH_CONFIG_WCHAR) && defined(WIN32) && defined(UNICODE)
+    int Session::applyCommandLine( int argc, wchar_t const * const * argv ) {
+
+        char **utf8Argv = new char *[ argc ];
+
+        for ( int i = 0; i < argc; ++i ) {
+            int bufSize = WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, NULL, 0, NULL, NULL );
+
+            utf8Argv[ i ] = new char[ bufSize ];
+
+            WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, utf8Argv[i], bufSize, NULL, NULL );
+        }
+
+        int returnCode = applyCommandLine( argc, utf8Argv );
+
+        for ( int i = 0; i < argc; ++i )
+            delete [] utf8Argv[ i ];
+
+        delete [] utf8Argv;
+
+        return returnCode;
+    }
+#endif
+
+    void Session::useConfigData( ConfigData const& configData ) {
+        m_configData = configData;
+        m_config.reset();
+    }
+
+    int Session::run() {
+        if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeStart ) != 0 ) {
+            Catch::cout() << "...waiting for enter/ return before starting" << std::endl;
+            static_cast<void>(std::getchar());
+        }
+        int exitCode = runInternal();
+        if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeExit ) != 0 ) {
+            Catch::cout() << "...waiting for enter/ return before exiting, with code: " << exitCode << std::endl;
+            static_cast<void>(std::getchar());
+        }
+        return exitCode;
+    }
+
+    clara::Parser const& Session::cli() const {
+        return m_cli;
+    }
+    void Session::cli( clara::Parser const& newParser ) {
+        m_cli = newParser;
+    }
+    ConfigData& Session::configData() {
+        return m_configData;
+    }
+    Config& Session::config() {
+        if( !m_config )
+            m_config = std::make_shared<Config>( m_configData );
+        return *m_config;
+    }
+
+    int Session::runInternal() {
+        if( m_startupExceptions )
+            return 1;
+
+        if (m_configData.showHelp || m_configData.libIdentify) {
+            return 0;
+        }
+
+        CATCH_TRY {
+            config(); // Force config to be constructed
+
+            seedRng( *m_config );
+
+            if( m_configData.filenamesAsTags )
+                applyFilenamesAsTags( *m_config );
+
+            // Handle list request
+            if( Option<std::size_t> listed = list( config() ) )
+                return static_cast<int>( *listed );
+
+            auto totals = runTests( m_config );
+            // Note that on unices only the lower 8 bits are usually used, clamping
+            // the return value to 255 prevents false negative when some multiple
+            // of 256 tests has failed
+            return (std::min) (MaxExitCode, (std::max) (totals.error, static_cast<int>(totals.assertions.failed)));
+        }
+#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)
+        catch( std::exception& ex ) {
+            Catch::cerr() << ex.what() << std::endl;
+            return MaxExitCode;
+        }
+#endif
+    }
+
+} // end namespace Catch
+// end catch_session.cpp
+// start catch_singletons.cpp
+
+#include <vector>
+
+namespace Catch {
+
+    namespace {
+        static auto getSingletons() -> std::vector<ISingleton*>*& {
+            static std::vector<ISingleton*>* g_singletons = nullptr;
+            if( !g_singletons )
+                g_singletons = new std::vector<ISingleton*>();
+            return g_singletons;
+        }
+    }
+
+    ISingleton::~ISingleton() {}
+
+    void addSingleton(ISingleton* singleton ) {
+        getSingletons()->push_back( singleton );
+    }
+    void cleanupSingletons() {
+        auto& singletons = getSingletons();
+        for( auto singleton : *singletons )
+            delete singleton;
+        delete singletons;
+        singletons = nullptr;
+    }
+
+} // namespace Catch
+// end catch_singletons.cpp
+// start catch_startup_exception_registry.cpp
+
+namespace Catch {
+void StartupExceptionRegistry::add( std::exception_ptr const& exception ) noexcept {
+        CATCH_TRY {
+            m_exceptions.push_back(exception);
+        } CATCH_CATCH_ALL {
+            // If we run out of memory during start-up there's really not a lot more we can do about it
+            std::terminate();
+        }
+    }
+
+    std::vector<std::exception_ptr> const& StartupExceptionRegistry::getExceptions() const noexcept {
+        return m_exceptions;
+    }
+
+} // end namespace Catch
+// end catch_startup_exception_registry.cpp
+// start catch_stream.cpp
+
+#include <cstdio>
+#include <iostream>
+#include <fstream>
+#include <sstream>
+#include <vector>
+#include <memory>
+
+namespace Catch {
+
+    Catch::IStream::~IStream() = default;
+
+    namespace detail { namespace {
+        template<typename WriterF, std::size_t bufferSize=256>
+        class StreamBufImpl : public std::streambuf {
+            char data[bufferSize];
+            WriterF m_writer;
+
+        public:
+            StreamBufImpl() {
+                setp( data, data + sizeof(data) );
+            }
+
+            ~StreamBufImpl() noexcept {
+                StreamBufImpl::sync();
+            }
+
+        private:
+            int overflow( int c ) override {
+                sync();
+
+                if( c != EOF ) {
+                    if( pbase() == epptr() )
+                        m_writer( std::string( 1, static_cast<char>( c ) ) );
+                    else
+                        sputc( static_cast<char>( c ) );
+                }
+                return 0;
+            }
+
+            int sync() override {
+                if( pbase() != pptr() ) {
+                    m_writer( std::string( pbase(), static_cast<std::string::size_type>( pptr() - pbase() ) ) );
+                    setp( pbase(), epptr() );
+                }
+                return 0;
+            }
+        };
+
+        ///////////////////////////////////////////////////////////////////////////
+
+        struct OutputDebugWriter {
+
+            void operator()( std::string const&str ) {
+                writeToDebugConsole( str );
+            }
+        };
+
+        ///////////////////////////////////////////////////////////////////////////
+
+        class FileStream : public IStream {
+            mutable std::ofstream m_ofs;
+        public:
+            FileStream( StringRef filename ) {
+                m_ofs.open( filename.c_str() );
+                CATCH_ENFORCE( !m_ofs.fail(), "Unable to open file: '" << filename << "'" );
+            }
+            ~FileStream() override = default;
+        public: // IStream
+            std::ostream& stream() const override {
+                return m_ofs;
+            }
+        };
+
+        ///////////////////////////////////////////////////////////////////////////
+
+        class CoutStream : public IStream {
+            mutable std::ostream m_os;
+        public:
+            // Store the streambuf from cout up-front because
+            // cout may get redirected when running tests
+            CoutStream() : m_os( Catch::cout().rdbuf() ) {}
+            ~CoutStream() override = default;
+
+        public: // IStream
+            std::ostream& stream() const override { return m_os; }
+        };
+
+        ///////////////////////////////////////////////////////////////////////////
+
+        class DebugOutStream : public IStream {
+            std::unique_ptr<StreamBufImpl<OutputDebugWriter>> m_streamBuf;
+            mutable std::ostream m_os;
+        public:
+            DebugOutStream()
+            :   m_streamBuf( new StreamBufImpl<OutputDebugWriter>() ),
+                m_os( m_streamBuf.get() )
+            {}
+
+            ~DebugOutStream() override = default;
+
+        public: // IStream
+            std::ostream& stream() const override { return m_os; }
+        };
+
+    }} // namespace anon::detail
+
+    ///////////////////////////////////////////////////////////////////////////
+
+    auto makeStream( StringRef const &filename ) -> IStream const* {
+        if( filename.empty() )
+            return new detail::CoutStream();
+        else if( filename[0] == '%' ) {
+            if( filename == "%debug" )
+                return new detail::DebugOutStream();
+            else
+                CATCH_ERROR( "Unrecognised stream: '" << filename << "'" );
+        }
+        else
+            return new detail::FileStream( filename );
+    }
+
+    // This class encapsulates the idea of a pool of ostringstreams that can be reused.
+    struct StringStreams {
+        std::vector<std::unique_ptr<std::ostringstream>> m_streams;
+        std::vector<std::size_t> m_unused;
+        std::ostringstream m_referenceStream; // Used for copy state/ flags from
+
+        auto add() -> std::size_t {
+            if( m_unused.empty() ) {
+                m_streams.push_back( std::unique_ptr<std::ostringstream>( new std::ostringstream ) );
+                return m_streams.size()-1;
+            }
+            else {
+                auto index = m_unused.back();
+                m_unused.pop_back();
+                return index;
+            }
+        }
+
+        void release( std::size_t index ) {
+            m_streams[index]->copyfmt( m_referenceStream ); // Restore initial flags and other state
+            m_unused.push_back(index);
+        }
+    };
+
+    ReusableStringStream::ReusableStringStream()
+    :   m_index( Singleton<StringStreams>::getMutable().add() ),
+        m_oss( Singleton<StringStreams>::getMutable().m_streams[m_index].get() )
+    {}
+
+    ReusableStringStream::~ReusableStringStream() {
+        static_cast<std::ostringstream*>( m_oss )->str("");
+        m_oss->clear();
+        Singleton<StringStreams>::getMutable().release( m_index );
+    }
+
+    auto ReusableStringStream::str() const -> std::string {
+        return static_cast<std::ostringstream*>( m_oss )->str();
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+
+#ifndef CATCH_CONFIG_NOSTDOUT // If you #define this you must implement these functions
+    std::ostream& cout() { return std::cout; }
+    std::ostream& cerr() { return std::cerr; }
+    std::ostream& clog() { return std::clog; }
+#endif
+}
+// end catch_stream.cpp
+// start catch_string_manip.cpp
+
+#include <algorithm>
+#include <ostream>
+#include <cstring>
+#include <cctype>
+
+namespace Catch {
+
+    namespace {
+        char toLowerCh(char c) {
+            return static_cast<char>( std::tolower( c ) );
+        }
+    }
+
+    bool startsWith( std::string const& s, std::string const& prefix ) {
+        return s.size() >= prefix.size() && std::equal(prefix.begin(), prefix.end(), s.begin());
+    }
+    bool startsWith( std::string const& s, char prefix ) {
+        return !s.empty() && s[0] == prefix;
+    }
+    bool endsWith( std::string const& s, std::string const& suffix ) {
+        return s.size() >= suffix.size() && std::equal(suffix.rbegin(), suffix.rend(), s.rbegin());
+    }
+    bool endsWith( std::string const& s, char suffix ) {
+        return !s.empty() && s[s.size()-1] == suffix;
+    }
+    bool contains( std::string const& s, std::string const& infix ) {
+        return s.find( infix ) != std::string::npos;
+    }
+    void toLowerInPlace( std::string& s ) {
+        std::transform( s.begin(), s.end(), s.begin(), toLowerCh );
+    }
+    std::string toLower( std::string const& s ) {
+        std::string lc = s;
+        toLowerInPlace( lc );
+        return lc;
+    }
+    std::string trim( std::string const& str ) {
+        static char const* whitespaceChars = "\n\r\t ";
+        std::string::size_type start = str.find_first_not_of( whitespaceChars );
+        std::string::size_type end = str.find_last_not_of( whitespaceChars );
+
+        return start != std::string::npos ? str.substr( start, 1+end-start ) : std::string();
+    }
+
+    bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ) {
+        bool replaced = false;
+        std::size_t i = str.find( replaceThis );
+        while( i != std::string::npos ) {
+            replaced = true;
+            str = str.substr( 0, i ) + withThis + str.substr( i+replaceThis.size() );
+            if( i < str.size()-withThis.size() )
+                i = str.find( replaceThis, i+withThis.size() );
+            else
+                i = std::string::npos;
+        }
+        return replaced;
+    }
+
+    pluralise::pluralise( std::size_t count, std::string const& label )
+    :   m_count( count ),
+        m_label( label )
+    {}
+
+    std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ) {
+        os << pluraliser.m_count << ' ' << pluraliser.m_label;
+        if( pluraliser.m_count != 1 )
+            os << 's';
+        return os;
+    }
+
+}
+// end catch_string_manip.cpp
+// start catch_stringref.cpp
+
+#if defined(__clang__)
+#    pragma clang diagnostic push
+#    pragma clang diagnostic ignored "-Wexit-time-destructors"
+#endif
+
+#include <ostream>
+#include <cstring>
+#include <cstdint>
+
+namespace {
+    const uint32_t byte_2_lead = 0xC0;
+    const uint32_t byte_3_lead = 0xE0;
+    const uint32_t byte_4_lead = 0xF0;
+}
+
+namespace Catch {
+    StringRef::StringRef( char const* rawChars ) noexcept
+    : StringRef( rawChars, static_cast<StringRef::size_type>(std::strlen(rawChars) ) )
+    {}
+
+    StringRef::operator std::string() const {
+        return std::string( m_start, m_size );
+    }
+
+    void StringRef::swap( StringRef& other ) noexcept {
+        std::swap( m_start, other.m_start );
+        std::swap( m_size, other.m_size );
+        std::swap( m_data, other.m_data );
+    }
+
+    auto StringRef::c_str() const -> char const* {
+        if( isSubstring() )
+           const_cast<StringRef*>( this )->takeOwnership();
+        return m_start;
+    }
+    auto StringRef::currentData() const noexcept -> char const* {
+        return m_start;
+    }
+
+    auto StringRef::isOwned() const noexcept -> bool {
+        return m_data != nullptr;
+    }
+    auto StringRef::isSubstring() const noexcept -> bool {
+        return m_start[m_size] != '\0';
+    }
+
+    void StringRef::takeOwnership() {
+        if( !isOwned() ) {
+            m_data = new char[m_size+1];
+            memcpy( m_data, m_start, m_size );
+            m_data[m_size] = '\0';
+            m_start = m_data;
+        }
+    }
+    auto StringRef::substr( size_type start, size_type size ) const noexcept -> StringRef {
+        if( start < m_size )
+            return StringRef( m_start+start, size );
+        else
+            return StringRef();
+    }
+    auto StringRef::operator == ( StringRef const& other ) const noexcept -> bool {
+        return
+            size() == other.size() &&
+            (std::strncmp( m_start, other.m_start, size() ) == 0);
+    }
+    auto StringRef::operator != ( StringRef const& other ) const noexcept -> bool {
+        return !operator==( other );
+    }
+
+    auto StringRef::operator[](size_type index) const noexcept -> char {
+        return m_start[index];
+    }
+
+    auto StringRef::numberOfCharacters() const noexcept -> size_type {
+        size_type noChars = m_size;
+        // Make adjustments for uft encodings
+        for( size_type i=0; i < m_size; ++i ) {
+            char c = m_start[i];
+            if( ( c & byte_2_lead ) == byte_2_lead ) {
+                noChars--;
+                if (( c & byte_3_lead ) == byte_3_lead )
+                    noChars--;
+                if( ( c & byte_4_lead ) == byte_4_lead )
+                    noChars--;
+            }
+        }
+        return noChars;
+    }
+
+    auto operator + ( StringRef const& lhs, StringRef const& rhs ) -> std::string {
+        std::string str;
+        str.reserve( lhs.size() + rhs.size() );
+        str += lhs;
+        str += rhs;
+        return str;
+    }
+    auto operator + ( StringRef const& lhs, const char* rhs ) -> std::string {
+        return std::string( lhs ) + std::string( rhs );
+    }
+    auto operator + ( char const* lhs, StringRef const& rhs ) -> std::string {
+        return std::string( lhs ) + std::string( rhs );
+    }
+
+    auto operator << ( std::ostream& os, StringRef const& str ) -> std::ostream& {
+        return os.write(str.currentData(), str.size());
+    }
+
+    auto operator+=( std::string& lhs, StringRef const& rhs ) -> std::string& {
+        lhs.append(rhs.currentData(), rhs.size());
+        return lhs;
+    }
+
+} // namespace Catch
+
+#if defined(__clang__)
+#    pragma clang diagnostic pop
+#endif
+// end catch_stringref.cpp
+// start catch_tag_alias.cpp
+
+namespace Catch {
+    TagAlias::TagAlias(std::string const & _tag, SourceLineInfo _lineInfo): tag(_tag), lineInfo(_lineInfo) {}
+}
+// end catch_tag_alias.cpp
+// start catch_tag_alias_autoregistrar.cpp
+
+namespace Catch {
+
+    RegistrarForTagAliases::RegistrarForTagAliases(char const* alias, char const* tag, SourceLineInfo const& lineInfo) {
+        CATCH_TRY {
+            getMutableRegistryHub().registerTagAlias(alias, tag, lineInfo);
+        } CATCH_CATCH_ALL {
+            // Do not throw when constructing global objects, instead register the exception to be processed later
+            getMutableRegistryHub().registerStartupException();
+        }
+    }
+
+}
+// end catch_tag_alias_autoregistrar.cpp
+// start catch_tag_alias_registry.cpp
+
+#include <sstream>
+
+namespace Catch {
+
+    TagAliasRegistry::~TagAliasRegistry() {}
+
+    TagAlias const* TagAliasRegistry::find( std::string const& alias ) const {
+        auto it = m_registry.find( alias );
+        if( it != m_registry.end() )
+            return &(it->second);
+        else
+            return nullptr;
+    }
+
+    std::string TagAliasRegistry::expandAliases( std::string const& unexpandedTestSpec ) const {
+        std::string expandedTestSpec = unexpandedTestSpec;
+        for( auto const& registryKvp : m_registry ) {
+            std::size_t pos = expandedTestSpec.find( registryKvp.first );
+            if( pos != std::string::npos ) {
+                expandedTestSpec =  expandedTestSpec.substr( 0, pos ) +
+                                    registryKvp.second.tag +
+                                    expandedTestSpec.substr( pos + registryKvp.first.size() );
+            }
+        }
+        return expandedTestSpec;
+    }
+
+    void TagAliasRegistry::add( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) {
+        CATCH_ENFORCE( startsWith(alias, "[@") && endsWith(alias, ']'),
+                      "error: tag alias, '" << alias << "' is not of the form [@alias name].\n" << lineInfo );
+
+        CATCH_ENFORCE( m_registry.insert(std::make_pair(alias, TagAlias(tag, lineInfo))).second,
+                      "error: tag alias, '" << alias << "' already registered.\n"
+                      << "\tFirst seen at: " << find(alias)->lineInfo << "\n"
+                      << "\tRedefined at: " << lineInfo );
+    }
+
+    ITagAliasRegistry::~ITagAliasRegistry() {}
+
+    ITagAliasRegistry const& ITagAliasRegistry::get() {
+        return getRegistryHub().getTagAliasRegistry();
+    }
+
+} // end namespace Catch
+// end catch_tag_alias_registry.cpp
+// start catch_test_case_info.cpp
+
+#include <cctype>
+#include <exception>
+#include <algorithm>
+#include <sstream>
+
+namespace Catch {
+
+    namespace {
+        TestCaseInfo::SpecialProperties parseSpecialTag( std::string const& tag ) {
+            if( startsWith( tag, '.' ) ||
+                tag == "!hide" )
+                return TestCaseInfo::IsHidden;
+            else if( tag == "!throws" )
+                return TestCaseInfo::Throws;
+            else if( tag == "!shouldfail" )
+                return TestCaseInfo::ShouldFail;
+            else if( tag == "!mayfail" )
+                return TestCaseInfo::MayFail;
+            else if( tag == "!nonportable" )
+                return TestCaseInfo::NonPortable;
+            else if( tag == "!benchmark" )
+                return static_cast<TestCaseInfo::SpecialProperties>( TestCaseInfo::Benchmark | TestCaseInfo::IsHidden );
+            else
+                return TestCaseInfo::None;
+        }
+        bool isReservedTag( std::string const& tag ) {
+            return parseSpecialTag( tag ) == TestCaseInfo::None && tag.size() > 0 && !std::isalnum( static_cast<unsigned char>(tag[0]) );
+        }
+        void enforceNotReservedTag( std::string const& tag, SourceLineInfo const& _lineInfo ) {
+            CATCH_ENFORCE( !isReservedTag(tag),
+                          "Tag name: [" << tag << "] is not allowed.\n"
+                          << "Tag names starting with non alpha-numeric characters are reserved\n"
+                          << _lineInfo );
+        }
+    }
+
+    TestCase makeTestCase(  ITestInvoker* _testCase,
+                            std::string const& _className,
+                            NameAndTags const& nameAndTags,
+                            SourceLineInfo const& _lineInfo )
+    {
+        bool isHidden = false;
+
+        // Parse out tags
+        std::vector<std::string> tags;
+        std::string desc, tag;
+        bool inTag = false;
+        std::string _descOrTags = nameAndTags.tags;
+        for (char c : _descOrTags) {
+            if( !inTag ) {
+                if( c == '[' )
+                    inTag = true;
+                else
+                    desc += c;
+            }
+            else {
+                if( c == ']' ) {
+                    TestCaseInfo::SpecialProperties prop = parseSpecialTag( tag );
+                    if( ( prop & TestCaseInfo::IsHidden ) != 0 )
+                        isHidden = true;
+                    else if( prop == TestCaseInfo::None )
+                        enforceNotReservedTag( tag, _lineInfo );
+
+                    tags.push_back( tag );
+                    tag.clear();
+                    inTag = false;
+                }
+                else
+                    tag += c;
+            }
+        }
+        if( isHidden ) {
+            tags.push_back( "." );
+        }
+
+        TestCaseInfo info( nameAndTags.name, _className, desc, tags, _lineInfo );
+        return TestCase( _testCase, std::move(info) );
+    }
+
+    void setTags( TestCaseInfo& testCaseInfo, std::vector<std::string> tags ) {
+        std::sort(begin(tags), end(tags));
+        tags.erase(std::unique(begin(tags), end(tags)), end(tags));
+        testCaseInfo.lcaseTags.clear();
+
+        for( auto const& tag : tags ) {
+            std::string lcaseTag = toLower( tag );
+            testCaseInfo.properties = static_cast<TestCaseInfo::SpecialProperties>( testCaseInfo.properties | parseSpecialTag( lcaseTag ) );
+            testCaseInfo.lcaseTags.push_back( lcaseTag );
+        }
+        testCaseInfo.tags = std::move(tags);
+    }
+
+    TestCaseInfo::TestCaseInfo( std::string const& _name,
+                                std::string const& _className,
+                                std::string const& _description,
+                                std::vector<std::string> const& _tags,
+                                SourceLineInfo const& _lineInfo )
+    :   name( _name ),
+        className( _className ),
+        description( _description ),
+        lineInfo( _lineInfo ),
+        properties( None )
+    {
+        setTags( *this, _tags );
+    }
+
+    bool TestCaseInfo::isHidden() const {
+        return ( properties & IsHidden ) != 0;
+    }
+    bool TestCaseInfo::throws() const {
+        return ( properties & Throws ) != 0;
+    }
+    bool TestCaseInfo::okToFail() const {
+        return ( properties & (ShouldFail | MayFail ) ) != 0;
+    }
+    bool TestCaseInfo::expectedToFail() const {
+        return ( properties & (ShouldFail ) ) != 0;
+    }
+
+    std::string TestCaseInfo::tagsAsString() const {
+        std::string ret;
+        // '[' and ']' per tag
+        std::size_t full_size = 2 * tags.size();
+        for (const auto& tag : tags) {
+            full_size += tag.size();
+        }
+        ret.reserve(full_size);
+        for (const auto& tag : tags) {
+            ret.push_back('[');
+            ret.append(tag);
+            ret.push_back(']');
+        }
+
+        return ret;
+    }
+
+    TestCase::TestCase( ITestInvoker* testCase, TestCaseInfo&& info ) : TestCaseInfo( std::move(info) ), test( testCase ) {}
+
+    TestCase TestCase::withName( std::string const& _newName ) const {
+        TestCase other( *this );
+        other.name = _newName;
+        return other;
+    }
+
+    void TestCase::invoke() const {
+        test->invoke();
+    }
+
+    bool TestCase::operator == ( TestCase const& other ) const {
+        return  test.get() == other.test.get() &&
+                name == other.name &&
+                className == other.className;
+    }
+
+    bool TestCase::operator < ( TestCase const& other ) const {
+        return name < other.name;
+    }
+
+    TestCaseInfo const& TestCase::getTestCaseInfo() const
+    {
+        return *this;
+    }
+
+} // end namespace Catch
+// end catch_test_case_info.cpp
+// start catch_test_case_registry_impl.cpp
+
+#include <sstream>
+
+namespace Catch {
+
+    std::vector<TestCase> sortTests( IConfig const& config, std::vector<TestCase> const& unsortedTestCases ) {
+
+        std::vector<TestCase> sorted = unsortedTestCases;
+
+        switch( config.runOrder() ) {
+            case RunTests::InLexicographicalOrder:
+                std::sort( sorted.begin(), sorted.end() );
+                break;
+            case RunTests::InRandomOrder:
+                seedRng( config );
+                std::shuffle( sorted.begin(), sorted.end(), rng() );
+                break;
+            case RunTests::InDeclarationOrder:
+                // already in declaration order
+                break;
+        }
+        return sorted;
+    }
+    bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ) {
+        return testSpec.matches( testCase ) && ( config.allowThrows() || !testCase.throws() );
+    }
+
+    void enforceNoDuplicateTestCases( std::vector<TestCase> const& functions ) {
+        std::set<TestCase> seenFunctions;
+        for( auto const& function : functions ) {
+            auto prev = seenFunctions.insert( function );
+            CATCH_ENFORCE( prev.second,
+                    "error: TEST_CASE( \"" << function.name << "\" ) already defined.\n"
+                    << "\tFirst seen at " << prev.first->getTestCaseInfo().lineInfo << "\n"
+                    << "\tRedefined at " << function.getTestCaseInfo().lineInfo );
+        }
+    }
+
+    std::vector<TestCase> filterTests( std::vector<TestCase> const& testCases, TestSpec const& testSpec, IConfig const& config ) {
+        std::vector<TestCase> filtered;
+        filtered.reserve( testCases.size() );
+        for( auto const& testCase : testCases )
+            if( matchTest( testCase, testSpec, config ) )
+                filtered.push_back( testCase );
+        return filtered;
+    }
+    std::vector<TestCase> const& getAllTestCasesSorted( IConfig const& config ) {
+        return getRegistryHub().getTestCaseRegistry().getAllTestsSorted( config );
+    }
+
+    void TestRegistry::registerTest( TestCase const& testCase ) {
+        std::string name = testCase.getTestCaseInfo().name;
+        if( name.empty() ) {
+            ReusableStringStream rss;
+            rss << "Anonymous test case " << ++m_unnamedCount;
+            return registerTest( testCase.withName( rss.str() ) );
+        }
+        m_functions.push_back( testCase );
+    }
+
+    std::vector<TestCase> const& TestRegistry::getAllTests() const {
+        return m_functions;
+    }
+    std::vector<TestCase> const& TestRegistry::getAllTestsSorted( IConfig const& config ) const {
+        if( m_sortedFunctions.empty() )
+            enforceNoDuplicateTestCases( m_functions );
+
+        if(  m_currentSortOrder != config.runOrder() || m_sortedFunctions.empty() ) {
+            m_sortedFunctions = sortTests( config, m_functions );
+            m_currentSortOrder = config.runOrder();
+        }
+        return m_sortedFunctions;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    TestInvokerAsFunction::TestInvokerAsFunction( void(*testAsFunction)() ) noexcept : m_testAsFunction( testAsFunction ) {}
+
+    void TestInvokerAsFunction::invoke() const {
+        m_testAsFunction();
+    }
+
+    std::string extractClassName( StringRef const& classOrQualifiedMethodName ) {
+        std::string className = classOrQualifiedMethodName;
+        if( startsWith( className, '&' ) )
+        {
+            std::size_t lastColons = className.rfind( "::" );
+            std::size_t penultimateColons = className.rfind( "::", lastColons-1 );
+            if( penultimateColons == std::string::npos )
+                penultimateColons = 1;
+            className = className.substr( penultimateColons, lastColons-penultimateColons );
+        }
+        return className;
+    }
+
+} // end namespace Catch
+// end catch_test_case_registry_impl.cpp
+// start catch_test_case_tracker.cpp
+
+#include <algorithm>
+#include <cassert>
+#include <stdexcept>
+#include <memory>
+#include <sstream>
+
+#if defined(__clang__)
+#    pragma clang diagnostic push
+#    pragma clang diagnostic ignored "-Wexit-time-destructors"
+#endif
+
+namespace Catch {
+namespace TestCaseTracking {
+
+    NameAndLocation::NameAndLocation( std::string const& _name, SourceLineInfo const& _location )
+    :   name( _name ),
+        location( _location )
+    {}
+
+    ITracker::~ITracker() = default;
+
+    TrackerContext& TrackerContext::instance() {
+        static TrackerContext s_instance;
+        return s_instance;
+    }
+
+    ITracker& TrackerContext::startRun() {
+        m_rootTracker = std::make_shared<SectionTracker>( NameAndLocation( "{root}", CATCH_INTERNAL_LINEINFO ), *this, nullptr );
+        m_currentTracker = nullptr;
+        m_runState = Executing;
+        return *m_rootTracker;
+    }
+
+    void TrackerContext::endRun() {
+        m_rootTracker.reset();
+        m_currentTracker = nullptr;
+        m_runState = NotStarted;
+    }
+
+    void TrackerContext::startCycle() {
+        m_currentTracker = m_rootTracker.get();
+        m_runState = Executing;
+    }
+    void TrackerContext::completeCycle() {
+        m_runState = CompletedCycle;
+    }
+
+    bool TrackerContext::completedCycle() const {
+        return m_runState == CompletedCycle;
+    }
+    ITracker& TrackerContext::currentTracker() {
+        return *m_currentTracker;
+    }
+    void TrackerContext::setCurrentTracker( ITracker* tracker ) {
+        m_currentTracker = tracker;
+    }
+
+    TrackerBase::TrackerBase( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent )
+    :   m_nameAndLocation( nameAndLocation ),
+        m_ctx( ctx ),
+        m_parent( parent )
+    {}
+
+    NameAndLocation const& TrackerBase::nameAndLocation() const {
+        return m_nameAndLocation;
+    }
+    bool TrackerBase::isComplete() const {
+        return m_runState == CompletedSuccessfully || m_runState == Failed;
+    }
+    bool TrackerBase::isSuccessfullyCompleted() const {
+        return m_runState == CompletedSuccessfully;
+    }
+    bool TrackerBase::isOpen() const {
+        return m_runState != NotStarted && !isComplete();
+    }
+    bool TrackerBase::hasChildren() const {
+        return !m_children.empty();
+    }
+
+    void TrackerBase::addChild( ITrackerPtr const& child ) {
+        m_children.push_back( child );
+    }
+
+    ITrackerPtr TrackerBase::findChild( NameAndLocation const& nameAndLocation ) {
+        auto it = std::find_if( m_children.begin(), m_children.end(),
+            [&nameAndLocation]( ITrackerPtr const& tracker ){
+                return
+                    tracker->nameAndLocation().location == nameAndLocation.location &&
+                    tracker->nameAndLocation().name == nameAndLocation.name;
+            } );
+        return( it != m_children.end() )
+            ? *it
+            : nullptr;
+    }
+    ITracker& TrackerBase::parent() {
+        assert( m_parent ); // Should always be non-null except for root
+        return *m_parent;
+    }
+
+    void TrackerBase::openChild() {
+        if( m_runState != ExecutingChildren ) {
+            m_runState = ExecutingChildren;
+            if( m_parent )
+                m_parent->openChild();
+        }
+    }
+
+    bool TrackerBase::isSectionTracker() const { return false; }
+    bool TrackerBase::isIndexTracker() const { return false; }
+
+    void TrackerBase::open() {
+        m_runState = Executing;
+        moveToThis();
+        if( m_parent )
+            m_parent->openChild();
+    }
+
+    void TrackerBase::close() {
+
+        // Close any still open children (e.g. generators)
+        while( &m_ctx.currentTracker() != this )
+            m_ctx.currentTracker().close();
+
+        switch( m_runState ) {
+            case NeedsAnotherRun:
+                break;
+
+            case Executing:
+                m_runState = CompletedSuccessfully;
+                break;
+            case ExecutingChildren:
+                if( m_children.empty() || m_children.back()->isComplete() )
+                    m_runState = CompletedSuccessfully;
+                break;
+
+            case NotStarted:
+            case CompletedSuccessfully:
+            case Failed:
+                CATCH_INTERNAL_ERROR( "Illogical state: " << m_runState );
+
+            default:
+                CATCH_INTERNAL_ERROR( "Unknown state: " << m_runState );
+        }
+        moveToParent();
+        m_ctx.completeCycle();
+    }
+    void TrackerBase::fail() {
+        m_runState = Failed;
+        if( m_parent )
+            m_parent->markAsNeedingAnotherRun();
+        moveToParent();
+        m_ctx.completeCycle();
+    }
+    void TrackerBase::markAsNeedingAnotherRun() {
+        m_runState = NeedsAnotherRun;
+    }
+
+    void TrackerBase::moveToParent() {
+        assert( m_parent );
+        m_ctx.setCurrentTracker( m_parent );
+    }
+    void TrackerBase::moveToThis() {
+        m_ctx.setCurrentTracker( this );
+    }
+
+    SectionTracker::SectionTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent )
+    :   TrackerBase( nameAndLocation, ctx, parent )
+    {
+        if( parent ) {
+            while( !parent->isSectionTracker() )
+                parent = &parent->parent();
+
+            SectionTracker& parentSection = static_cast<SectionTracker&>( *parent );
+            addNextFilters( parentSection.m_filters );
+        }
+    }
+
+    bool SectionTracker::isSectionTracker() const { return true; }
+
+    SectionTracker& SectionTracker::acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation ) {
+        std::shared_ptr<SectionTracker> section;
+
+        ITracker& currentTracker = ctx.currentTracker();
+        if( ITrackerPtr childTracker = currentTracker.findChild( nameAndLocation ) ) {
+            assert( childTracker );
+            assert( childTracker->isSectionTracker() );
+            section = std::static_pointer_cast<SectionTracker>( childTracker );
+        }
+        else {
+            section = std::make_shared<SectionTracker>( nameAndLocation, ctx, &currentTracker );
+            currentTracker.addChild( section );
+        }
+        if( !ctx.completedCycle() )
+            section->tryOpen();
+        return *section;
+    }
+
+    void SectionTracker::tryOpen() {
+        if( !isComplete() && (m_filters.empty() || m_filters[0].empty() ||  m_filters[0] == m_nameAndLocation.name ) )
+            open();
+    }
+
+    void SectionTracker::addInitialFilters( std::vector<std::string> const& filters ) {
+        if( !filters.empty() ) {
+            m_filters.push_back(""); // Root - should never be consulted
+            m_filters.push_back(""); // Test Case - not a section filter
+            m_filters.insert( m_filters.end(), filters.begin(), filters.end() );
+        }
+    }
+    void SectionTracker::addNextFilters( std::vector<std::string> const& filters ) {
+        if( filters.size() > 1 )
+            m_filters.insert( m_filters.end(), ++filters.begin(), filters.end() );
+    }
+
+    IndexTracker::IndexTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent, int size )
+    :   TrackerBase( nameAndLocation, ctx, parent ),
+        m_size( size )
+    {}
+
+    bool IndexTracker::isIndexTracker() const { return true; }
+
+    IndexTracker& IndexTracker::acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation, int size ) {
+        std::shared_ptr<IndexTracker> tracker;
+
+        ITracker& currentTracker = ctx.currentTracker();
+        if( ITrackerPtr childTracker = currentTracker.findChild( nameAndLocation ) ) {
+            assert( childTracker );
+            assert( childTracker->isIndexTracker() );
+            tracker = std::static_pointer_cast<IndexTracker>( childTracker );
+        }
+        else {
+            tracker = std::make_shared<IndexTracker>( nameAndLocation, ctx, &currentTracker, size );
+            currentTracker.addChild( tracker );
+        }
+
+        if( !ctx.completedCycle() && !tracker->isComplete() ) {
+            if( tracker->m_runState != ExecutingChildren && tracker->m_runState != NeedsAnotherRun )
+                tracker->moveNext();
+            tracker->open();
+        }
+
+        return *tracker;
+    }
+
+    int IndexTracker::index() const { return m_index; }
+
+    void IndexTracker::moveNext() {
+        m_index++;
+        m_children.clear();
+    }
+
+    void IndexTracker::close() {
+        TrackerBase::close();
+        if( m_runState == CompletedSuccessfully && m_index < m_size-1 )
+            m_runState = Executing;
+    }
+
+} // namespace TestCaseTracking
+
+using TestCaseTracking::ITracker;
+using TestCaseTracking::TrackerContext;
+using TestCaseTracking::SectionTracker;
+using TestCaseTracking::IndexTracker;
+
+} // namespace Catch
+
+#if defined(__clang__)
+#    pragma clang diagnostic pop
+#endif
+// end catch_test_case_tracker.cpp
+// start catch_test_registry.cpp
+
+namespace Catch {
+
+    auto makeTestInvoker( void(*testAsFunction)() ) noexcept -> ITestInvoker* {
+        return new(std::nothrow) TestInvokerAsFunction( testAsFunction );
+    }
+
+    NameAndTags::NameAndTags( StringRef const& name_ , StringRef const& tags_ ) noexcept : name( name_ ), tags( tags_ ) {}
+
+    AutoReg::AutoReg( ITestInvoker* invoker, SourceLineInfo const& lineInfo, StringRef const& classOrMethod, NameAndTags const& nameAndTags ) noexcept {
+        CATCH_TRY {
+            getMutableRegistryHub()
+                    .registerTest(
+                        makeTestCase(
+                            invoker,
+                            extractClassName( classOrMethod ),
+                            nameAndTags,
+                            lineInfo));
+        } CATCH_CATCH_ALL {
+            // Do not throw when constructing global objects, instead register the exception to be processed later
+            getMutableRegistryHub().registerStartupException();
+        }
+    }
+
+    AutoReg::~AutoReg() = default;
+}
+// end catch_test_registry.cpp
+// start catch_test_spec.cpp
+
+#include <algorithm>
+#include <string>
+#include <vector>
+#include <memory>
+
+namespace Catch {
+
+    TestSpec::Pattern::~Pattern() = default;
+    TestSpec::NamePattern::~NamePattern() = default;
+    TestSpec::TagPattern::~TagPattern() = default;
+    TestSpec::ExcludedPattern::~ExcludedPattern() = default;
+
+    TestSpec::NamePattern::NamePattern( std::string const& name )
+    : m_wildcardPattern( toLower( name ), CaseSensitive::No )
+    {}
+    bool TestSpec::NamePattern::matches( TestCaseInfo const& testCase ) const {
+        return m_wildcardPattern.matches( toLower( testCase.name ) );
+    }
+
+    TestSpec::TagPattern::TagPattern( std::string const& tag ) : m_tag( toLower( tag ) ) {}
+    bool TestSpec::TagPattern::matches( TestCaseInfo const& testCase ) const {
+        return std::find(begin(testCase.lcaseTags),
+                         end(testCase.lcaseTags),
+                         m_tag) != end(testCase.lcaseTags);
+    }
+
+    TestSpec::ExcludedPattern::ExcludedPattern( PatternPtr const& underlyingPattern ) : m_underlyingPattern( underlyingPattern ) {}
+    bool TestSpec::ExcludedPattern::matches( TestCaseInfo const& testCase ) const { return !m_underlyingPattern->matches( testCase ); }
+
+    bool TestSpec::Filter::matches( TestCaseInfo const& testCase ) const {
+        // All patterns in a filter must match for the filter to be a match
+        for( auto const& pattern : m_patterns ) {
+            if( !pattern->matches( testCase ) )
+                return false;
+        }
+        return true;
+    }
+
+    bool TestSpec::hasFilters() const {
+        return !m_filters.empty();
+    }
+    bool TestSpec::matches( TestCaseInfo const& testCase ) const {
+        // A TestSpec matches if any filter matches
+        for( auto const& filter : m_filters )
+            if( filter.matches( testCase ) )
+                return true;
+        return false;
+    }
+}
+// end catch_test_spec.cpp
+// start catch_test_spec_parser.cpp
+
+namespace Catch {
+
+    TestSpecParser::TestSpecParser( ITagAliasRegistry const& tagAliases ) : m_tagAliases( &tagAliases ) {}
+
+    TestSpecParser& TestSpecParser::parse( std::string const& arg ) {
+        m_mode = None;
+        m_exclusion = false;
+        m_start = std::string::npos;
+        m_arg = m_tagAliases->expandAliases( arg );
+        m_escapeChars.clear();
+        for( m_pos = 0; m_pos < m_arg.size(); ++m_pos )
+            visitChar( m_arg[m_pos] );
+        if( m_mode == Name )
+            addPattern<TestSpec::NamePattern>();
+        return *this;
+    }
+    TestSpec TestSpecParser::testSpec() {
+        addFilter();
+        return m_testSpec;
+    }
+
+    void TestSpecParser::visitChar( char c ) {
+        if( m_mode == None ) {
+            switch( c ) {
+            case ' ': return;
+            case '~': m_exclusion = true; return;
+            case '[': return startNewMode( Tag, ++m_pos );
+            case '"': return startNewMode( QuotedName, ++m_pos );
+            case '\\': return escape();
+            default: startNewMode( Name, m_pos ); break;
+            }
+        }
+        if( m_mode == Name ) {
+            if( c == ',' ) {
+                addPattern<TestSpec::NamePattern>();
+                addFilter();
+            }
+            else if( c == '[' ) {
+                if( subString() == "exclude:" )
+                    m_exclusion = true;
+                else
+                    addPattern<TestSpec::NamePattern>();
+                startNewMode( Tag, ++m_pos );
+            }
+            else if( c == '\\' )
+                escape();
+        }
+        else if( m_mode == EscapedName )
+            m_mode = Name;
+        else if( m_mode == QuotedName && c == '"' )
+            addPattern<TestSpec::NamePattern>();
+        else if( m_mode == Tag && c == ']' )
+            addPattern<TestSpec::TagPattern>();
+    }
+    void TestSpecParser::startNewMode( Mode mode, std::size_t start ) {
+        m_mode = mode;
+        m_start = start;
+    }
+    void TestSpecParser::escape() {
+        if( m_mode == None )
+            m_start = m_pos;
+        m_mode = EscapedName;
+        m_escapeChars.push_back( m_pos );
+    }
+    std::string TestSpecParser::subString() const { return m_arg.substr( m_start, m_pos - m_start ); }
+
+    void TestSpecParser::addFilter() {
+        if( !m_currentFilter.m_patterns.empty() ) {
+            m_testSpec.m_filters.push_back( m_currentFilter );
+            m_currentFilter = TestSpec::Filter();
+        }
+    }
+
+    TestSpec parseTestSpec( std::string const& arg ) {
+        return TestSpecParser( ITagAliasRegistry::get() ).parse( arg ).testSpec();
+    }
+
+} // namespace Catch
+// end catch_test_spec_parser.cpp
+// start catch_timer.cpp
+
+#include <chrono>
+
+static const uint64_t nanosecondsInSecond = 1000000000;
+
+namespace Catch {
+
+    auto getCurrentNanosecondsSinceEpoch() -> uint64_t {
+        return std::chrono::duration_cast<std::chrono::nanoseconds>( std::chrono::high_resolution_clock::now().time_since_epoch() ).count();
+    }
+
+    namespace {
+        auto estimateClockResolution() -> uint64_t {
+            uint64_t sum = 0;
+            static const uint64_t iterations = 1000000;
+
+            auto startTime = getCurrentNanosecondsSinceEpoch();
+
+            for( std::size_t i = 0; i < iterations; ++i ) {
+
+                uint64_t ticks;
+                uint64_t baseTicks = getCurrentNanosecondsSinceEpoch();
+                do {
+                    ticks = getCurrentNanosecondsSinceEpoch();
+                } while( ticks == baseTicks );
+
+                auto delta = ticks - baseTicks;
+                sum += delta;
+
+                // If we have been calibrating for over 3 seconds -- the clock
+                // is terrible and we should move on.
+                // TBD: How to signal that the measured resolution is probably wrong?
+                if (ticks > startTime + 3 * nanosecondsInSecond) {
+                    return sum / i;
+                }
+            }
+
+            // We're just taking the mean, here. To do better we could take the std. dev and exclude outliers
+            // - and potentially do more iterations if there's a high variance.
+            return sum/iterations;
+        }
+    }
+    auto getEstimatedClockResolution() -> uint64_t {
+        static auto s_resolution = estimateClockResolution();
+        return s_resolution;
+    }
+
+    void Timer::start() {
+       m_nanoseconds = getCurrentNanosecondsSinceEpoch();
+    }
+    auto Timer::getElapsedNanoseconds() const -> uint64_t {
+        return getCurrentNanosecondsSinceEpoch() - m_nanoseconds;
+    }
+    auto Timer::getElapsedMicroseconds() const -> uint64_t {
+        return getElapsedNanoseconds()/1000;
+    }
+    auto Timer::getElapsedMilliseconds() const -> unsigned int {
+        return static_cast<unsigned int>(getElapsedMicroseconds()/1000);
+    }
+    auto Timer::getElapsedSeconds() const -> double {
+        return getElapsedMicroseconds()/1000000.0;
+    }
+
+} // namespace Catch
+// end catch_timer.cpp
+// start catch_tostring.cpp
+
+#if defined(__clang__)
+#    pragma clang diagnostic push
+#    pragma clang diagnostic ignored "-Wexit-time-destructors"
+#    pragma clang diagnostic ignored "-Wglobal-constructors"
+#endif
+
+// Enable specific decls locally
+#if !defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER)
+#define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER
+#endif
+
+#include <cmath>
+#include <iomanip>
+
+namespace Catch {
+
+namespace Detail {
+
+    const std::string unprintableString = "{?}";
+
+    namespace {
+        const int hexThreshold = 255;
+
+        struct Endianness {
+            enum Arch { Big, Little };
+
+            static Arch which() {
+                union _{
+                    int asInt;
+                    char asChar[sizeof (int)];
+                } u;
+
+                u.asInt = 1;
+                return ( u.asChar[sizeof(int)-1] == 1 ) ? Big : Little;
+            }
+        };
+    }
+
+    std::string rawMemoryToString( const void *object, std::size_t size ) {
+        // Reverse order for little endian architectures
+        int i = 0, end = static_cast<int>( size ), inc = 1;
+        if( Endianness::which() == Endianness::Little ) {
+            i = end-1;
+            end = inc = -1;
+        }
+
+        unsigned char const *bytes = static_cast<unsigned char const *>(object);
+        ReusableStringStream rss;
+        rss << "0x" << std::setfill('0') << std::hex;
+        for( ; i != end; i += inc )
+             rss << std::setw(2) << static_cast<unsigned>(bytes[i]);
+       return rss.str();
+    }
+}
+
+template<typename T>
+std::string fpToString( T value, int precision ) {
+    if (Catch::isnan(value)) {
+        return "nan";
+    }
+
+    ReusableStringStream rss;
+    rss << std::setprecision( precision )
+        << std::fixed
+        << value;
+    std::string d = rss.str();
+    std::size_t i = d.find_last_not_of( '0' );
+    if( i != std::string::npos && i != d.size()-1 ) {
+        if( d[i] == '.' )
+            i++;
+        d = d.substr( 0, i+1 );
+    }
+    return d;
+}
+
+//// ======================================================= ////
+//
+//   Out-of-line defs for full specialization of StringMaker
+//
+//// ======================================================= ////
+
+std::string StringMaker<std::string>::convert(const std::string& str) {
+    if (!getCurrentContext().getConfig()->showInvisibles()) {
+        return '"' + str + '"';
+    }
+
+    std::string s("\"");
+    for (char c : str) {
+        switch (c) {
+        case '\n':
+            s.append("\\n");
+            break;
+        case '\t':
+            s.append("\\t");
+            break;
+        default:
+            s.push_back(c);
+            break;
+        }
+    }
+    s.append("\"");
+    return s;
+}
+
+#ifdef CATCH_CONFIG_CPP17_STRING_VIEW
+std::string StringMaker<std::string_view>::convert(std::string_view str) {
+    return ::Catch::Detail::stringify(std::string{ str });
+}
+#endif
+
+std::string StringMaker<char const*>::convert(char const* str) {
+    if (str) {
+        return ::Catch::Detail::stringify(std::string{ str });
+    } else {
+        return{ "{null string}" };
+    }
+}
+std::string StringMaker<char*>::convert(char* str) {
+    if (str) {
+        return ::Catch::Detail::stringify(std::string{ str });
+    } else {
+        return{ "{null string}" };
+    }
+}
+
+#ifdef CATCH_CONFIG_WCHAR
+std::string StringMaker<std::wstring>::convert(const std::wstring& wstr) {
+    std::string s;
+    s.reserve(wstr.size());
+    for (auto c : wstr) {
+        s += (c <= 0xff) ? static_cast<char>(c) : '?';
+    }
+    return ::Catch::Detail::stringify(s);
+}
+
+# ifdef CATCH_CONFIG_CPP17_STRING_VIEW
+std::string StringMaker<std::wstring_view>::convert(std::wstring_view str) {
+    return StringMaker<std::wstring>::convert(std::wstring(str));
+}
+# endif
+
+std::string StringMaker<wchar_t const*>::convert(wchar_t const * str) {
+    if (str) {
+        return ::Catch::Detail::stringify(std::wstring{ str });
+    } else {
+        return{ "{null string}" };
+    }
+}
+std::string StringMaker<wchar_t *>::convert(wchar_t * str) {
+    if (str) {
+        return ::Catch::Detail::stringify(std::wstring{ str });
+    } else {
+        return{ "{null string}" };
+    }
+}
+#endif
+
+std::string StringMaker<int>::convert(int value) {
+    return ::Catch::Detail::stringify(static_cast<long long>(value));
+}
+std::string StringMaker<long>::convert(long value) {
+    return ::Catch::Detail::stringify(static_cast<long long>(value));
+}
+std::string StringMaker<long long>::convert(long long value) {
+    ReusableStringStream rss;
+    rss << value;
+    if (value > Detail::hexThreshold) {
+        rss << " (0x" << std::hex << value << ')';
+    }
+    return rss.str();
+}
+
+std::string StringMaker<unsigned int>::convert(unsigned int value) {
+    return ::Catch::Detail::stringify(static_cast<unsigned long long>(value));
+}
+std::string StringMaker<unsigned long>::convert(unsigned long value) {
+    return ::Catch::Detail::stringify(static_cast<unsigned long long>(value));
+}
+std::string StringMaker<unsigned long long>::convert(unsigned long long value) {
+    ReusableStringStream rss;
+    rss << value;
+    if (value > Detail::hexThreshold) {
+        rss << " (0x" << std::hex << value << ')';
+    }
+    return rss.str();
+}
+
+std::string StringMaker<bool>::convert(bool b) {
+    return b ? "true" : "false";
+}
+
+std::string StringMaker<signed char>::convert(signed char value) {
+    if (value == '\r') {
+        return "'\\r'";
+    } else if (value == '\f') {
+        return "'\\f'";
+    } else if (value == '\n') {
+        return "'\\n'";
+    } else if (value == '\t') {
+        return "'\\t'";
+    } else if ('\0' <= value && value < ' ') {
+        return ::Catch::Detail::stringify(static_cast<unsigned int>(value));
+    } else {
+        char chstr[] = "' '";
+        chstr[1] = value;
+        return chstr;
+    }
+}
+std::string StringMaker<char>::convert(char c) {
+    return ::Catch::Detail::stringify(static_cast<signed char>(c));
+}
+std::string StringMaker<unsigned char>::convert(unsigned char c) {
+    return ::Catch::Detail::stringify(static_cast<char>(c));
+}
+
+std::string StringMaker<std::nullptr_t>::convert(std::nullptr_t) {
+    return "nullptr";
+}
+
+std::string StringMaker<float>::convert(float value) {
+    return fpToString(value, 5) + 'f';
+}
+std::string StringMaker<double>::convert(double value) {
+    return fpToString(value, 10);
+}
+
+std::string ratio_string<std::atto>::symbol() { return "a"; }
+std::string ratio_string<std::femto>::symbol() { return "f"; }
+std::string ratio_string<std::pico>::symbol() { return "p"; }
+std::string ratio_string<std::nano>::symbol() { return "n"; }
+std::string ratio_string<std::micro>::symbol() { return "u"; }
+std::string ratio_string<std::milli>::symbol() { return "m"; }
+
+} // end namespace Catch
+
+#if defined(__clang__)
+#    pragma clang diagnostic pop
+#endif
+
+// end catch_tostring.cpp
+// start catch_totals.cpp
+
+namespace Catch {
+
+    Counts Counts::operator - ( Counts const& other ) const {
+        Counts diff;
+        diff.passed = passed - other.passed;
+        diff.failed = failed - other.failed;
+        diff.failedButOk = failedButOk - other.failedButOk;
+        return diff;
+    }
+
+    Counts& Counts::operator += ( Counts const& other ) {
+        passed += other.passed;
+        failed += other.failed;
+        failedButOk += other.failedButOk;
+        return *this;
+    }
+
+    std::size_t Counts::total() const {
+        return passed + failed + failedButOk;
+    }
+    bool Counts::allPassed() const {
+        return failed == 0 && failedButOk == 0;
+    }
+    bool Counts::allOk() const {
+        return failed == 0;
+    }
+
+    Totals Totals::operator - ( Totals const& other ) const {
+        Totals diff;
+        diff.assertions = assertions - other.assertions;
+        diff.testCases = testCases - other.testCases;
+        return diff;
+    }
+
+    Totals& Totals::operator += ( Totals const& other ) {
+        assertions += other.assertions;
+        testCases += other.testCases;
+        return *this;
+    }
+
+    Totals Totals::delta( Totals const& prevTotals ) const {
+        Totals diff = *this - prevTotals;
+        if( diff.assertions.failed > 0 )
+            ++diff.testCases.failed;
+        else if( diff.assertions.failedButOk > 0 )
+            ++diff.testCases.failedButOk;
+        else
+            ++diff.testCases.passed;
+        return diff;
+    }
+
+}
+// end catch_totals.cpp
+// start catch_uncaught_exceptions.cpp
+
+#include <exception>
+
+namespace Catch {
+    bool uncaught_exceptions() {
+#if defined(CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS)
+        return std::uncaught_exceptions() > 0;
+#else
+        return std::uncaught_exception();
+#endif
+  }
+} // end namespace Catch
+// end catch_uncaught_exceptions.cpp
+// start catch_version.cpp
+
+#include <ostream>
+
+namespace Catch {
+
+    Version::Version
+        (   unsigned int _majorVersion,
+            unsigned int _minorVersion,
+            unsigned int _patchNumber,
+            char const * const _branchName,
+            unsigned int _buildNumber )
+    :   majorVersion( _majorVersion ),
+        minorVersion( _minorVersion ),
+        patchNumber( _patchNumber ),
+        branchName( _branchName ),
+        buildNumber( _buildNumber )
+    {}
+
+    std::ostream& operator << ( std::ostream& os, Version const& version ) {
+        os  << version.majorVersion << '.'
+            << version.minorVersion << '.'
+            << version.patchNumber;
+        // branchName is never null -> 0th char is \0 if it is empty
+        if (version.branchName[0]) {
+            os << '-' << version.branchName
+               << '.' << version.buildNumber;
+        }
+        return os;
+    }
+
+    Version const& libraryVersion() {
+        static Version version( 2, 5, 0, "", 0 );
+        return version;
+    }
+
+}
+// end catch_version.cpp
+// start catch_wildcard_pattern.cpp
+
+#include <sstream>
+
+namespace Catch {
+
+    WildcardPattern::WildcardPattern( std::string const& pattern,
+                                      CaseSensitive::Choice caseSensitivity )
+    :   m_caseSensitivity( caseSensitivity ),
+        m_pattern( adjustCase( pattern ) )
+    {
+        if( startsWith( m_pattern, '*' ) ) {
+            m_pattern = m_pattern.substr( 1 );
+            m_wildcard = WildcardAtStart;
+        }
+        if( endsWith( m_pattern, '*' ) ) {
+            m_pattern = m_pattern.substr( 0, m_pattern.size()-1 );
+            m_wildcard = static_cast<WildcardPosition>( m_wildcard | WildcardAtEnd );
+        }
+    }
+
+    bool WildcardPattern::matches( std::string const& str ) const {
+        switch( m_wildcard ) {
+            case NoWildcard:
+                return m_pattern == adjustCase( str );
+            case WildcardAtStart:
+                return endsWith( adjustCase( str ), m_pattern );
+            case WildcardAtEnd:
+                return startsWith( adjustCase( str ), m_pattern );
+            case WildcardAtBothEnds:
+                return contains( adjustCase( str ), m_pattern );
+            default:
+                CATCH_INTERNAL_ERROR( "Unknown enum" );
+        }
+    }
+
+    std::string WildcardPattern::adjustCase( std::string const& str ) const {
+        return m_caseSensitivity == CaseSensitive::No ? toLower( str ) : str;
+    }
+}
+// end catch_wildcard_pattern.cpp
+// start catch_xmlwriter.cpp
+
+#include <iomanip>
+
+using uchar = unsigned char;
+
+namespace Catch {
+
+namespace {
+
+    size_t trailingBytes(unsigned char c) {
+        if ((c & 0xE0) == 0xC0) {
+            return 2;
+        }
+        if ((c & 0xF0) == 0xE0) {
+            return 3;
+        }
+        if ((c & 0xF8) == 0xF0) {
+            return 4;
+        }
+        CATCH_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered");
+    }
+
+    uint32_t headerValue(unsigned char c) {
+        if ((c & 0xE0) == 0xC0) {
+            return c & 0x1F;
+        }
+        if ((c & 0xF0) == 0xE0) {
+            return c & 0x0F;
+        }
+        if ((c & 0xF8) == 0xF0) {
+            return c & 0x07;
+        }
+        CATCH_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered");
+    }
+
+    void hexEscapeChar(std::ostream& os, unsigned char c) {
+        os << "\\x"
+            << std::uppercase << std::hex << std::setfill('0') << std::setw(2)
+            << static_cast<int>(c);
+    }
+
+} // anonymous namespace
+
+    XmlEncode::XmlEncode( std::string const& str, ForWhat forWhat )
+    :   m_str( str ),
+        m_forWhat( forWhat )
+    {}
+
+    void XmlEncode::encodeTo( std::ostream& os ) const {
+        // Apostrophe escaping not necessary if we always use " to write attributes
+        // (see: http://www.w3.org/TR/xml/#syntax)
+
+        for( std::size_t idx = 0; idx < m_str.size(); ++ idx ) {
+            uchar c = m_str[idx];
+            switch (c) {
+            case '<':   os << "&lt;"; break;
+            case '&':   os << "&amp;"; break;
+
+            case '>':
+                // See: http://www.w3.org/TR/xml/#syntax
+                if (idx > 2 && m_str[idx - 1] == ']' && m_str[idx - 2] == ']')
+                    os << "&gt;";
+                else
+                    os << c;
+                break;
+
+            case '\"':
+                if (m_forWhat == ForAttributes)
+                    os << "&quot;";
+                else
+                    os << c;
+                break;
+
+            default:
+                // Check for control characters and invalid utf-8
+
+                // Escape control characters in standard ascii
+                // see http://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0
+                if (c < 0x09 || (c > 0x0D && c < 0x20) || c == 0x7F) {
+                    hexEscapeChar(os, c);
+                    break;
+                }
+
+                // Plain ASCII: Write it to stream
+                if (c < 0x7F) {
+                    os << c;
+                    break;
+                }
+
+                // UTF-8 territory
+                // Check if the encoding is valid and if it is not, hex escape bytes.
+                // Important: We do not check the exact decoded values for validity, only the encoding format
+                // First check that this bytes is a valid lead byte:
+                // This means that it is not encoded as 1111 1XXX
+                // Or as 10XX XXXX
+                if (c <  0xC0 ||
+                    c >= 0xF8) {
+                    hexEscapeChar(os, c);
+                    break;
+                }
+
+                auto encBytes = trailingBytes(c);
+                // Are there enough bytes left to avoid accessing out-of-bounds memory?
+                if (idx + encBytes - 1 >= m_str.size()) {
+                    hexEscapeChar(os, c);
+                    break;
+                }
+                // The header is valid, check data
+                // The next encBytes bytes must together be a valid utf-8
+                // This means: bitpattern 10XX XXXX and the extracted value is sane (ish)
+                bool valid = true;
+                uint32_t value = headerValue(c);
+                for (std::size_t n = 1; n < encBytes; ++n) {
+                    uchar nc = m_str[idx + n];
+                    valid &= ((nc & 0xC0) == 0x80);
+                    value = (value << 6) | (nc & 0x3F);
+                }
+
+                if (
+                    // Wrong bit pattern of following bytes
+                    (!valid) ||
+                    // Overlong encodings
+                    (value < 0x80) ||
+                    (0x80 <= value && value < 0x800   && encBytes > 2) ||
+                    (0x800 < value && value < 0x10000 && encBytes > 3) ||
+                    // Encoded value out of range
+                    (value >= 0x110000)
+                    ) {
+                    hexEscapeChar(os, c);
+                    break;
+                }
+
+                // If we got here, this is in fact a valid(ish) utf-8 sequence
+                for (std::size_t n = 0; n < encBytes; ++n) {
+                    os << m_str[idx + n];
+                }
+                idx += encBytes - 1;
+                break;
+            }
+        }
+    }
+
+    std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ) {
+        xmlEncode.encodeTo( os );
+        return os;
+    }
+
+    XmlWriter::ScopedElement::ScopedElement( XmlWriter* writer )
+    :   m_writer( writer )
+    {}
+
+    XmlWriter::ScopedElement::ScopedElement( ScopedElement&& other ) noexcept
+    :   m_writer( other.m_writer ){
+        other.m_writer = nullptr;
+    }
+    XmlWriter::ScopedElement& XmlWriter::ScopedElement::operator=( ScopedElement&& other ) noexcept {
+        if ( m_writer ) {
+            m_writer->endElement();
+        }
+        m_writer = other.m_writer;
+        other.m_writer = nullptr;
+        return *this;
+    }
+
+    XmlWriter::ScopedElement::~ScopedElement() {
+        if( m_writer )
+            m_writer->endElement();
+    }
+
+    XmlWriter::ScopedElement& XmlWriter::ScopedElement::writeText( std::string const& text, bool indent ) {
+        m_writer->writeText( text, indent );
+        return *this;
+    }
+
+    XmlWriter::XmlWriter( std::ostream& os ) : m_os( os )
+    {
+        writeDeclaration();
+    }
+
+    XmlWriter::~XmlWriter() {
+        while( !m_tags.empty() )
+            endElement();
+    }
+
+    XmlWriter& XmlWriter::startElement( std::string const& name ) {
+        ensureTagClosed();
+        newlineIfNecessary();
+        m_os << m_indent << '<' << name;
+        m_tags.push_back( name );
+        m_indent += "  ";
+        m_tagIsOpen = true;
+        return *this;
+    }
+
+    XmlWriter::ScopedElement XmlWriter::scopedElement( std::string const& name ) {
+        ScopedElement scoped( this );
+        startElement( name );
+        return scoped;
+    }
+
+    XmlWriter& XmlWriter::endElement() {
+        newlineIfNecessary();
+        m_indent = m_indent.substr( 0, m_indent.size()-2 );
+        if( m_tagIsOpen ) {
+            m_os << "/>";
+            m_tagIsOpen = false;
+        }
+        else {
+            m_os << m_indent << "</" << m_tags.back() << ">";
+        }
+        m_os << std::endl;
+        m_tags.pop_back();
+        return *this;
+    }
+
+    XmlWriter& XmlWriter::writeAttribute( std::string const& name, std::string const& attribute ) {
+        if( !name.empty() && !attribute.empty() )
+            m_os << ' ' << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '"';
+        return *this;
+    }
+
+    XmlWriter& XmlWriter::writeAttribute( std::string const& name, bool attribute ) {
+        m_os << ' ' << name << "=\"" << ( attribute ? "true" : "false" ) << '"';
+        return *this;
+    }
+
+    XmlWriter& XmlWriter::writeText( std::string const& text, bool indent ) {
+        if( !text.empty() ){
+            bool tagWasOpen = m_tagIsOpen;
+            ensureTagClosed();
+            if( tagWasOpen && indent )
+                m_os << m_indent;
+            m_os << XmlEncode( text );
+            m_needsNewline = true;
+        }
+        return *this;
+    }
+
+    XmlWriter& XmlWriter::writeComment( std::string const& text ) {
+        ensureTagClosed();
+        m_os << m_indent << "<!--" << text << "-->";
+        m_needsNewline = true;
+        return *this;
+    }
+
+    void XmlWriter::writeStylesheetRef( std::string const& url ) {
+        m_os << "<?xml-stylesheet type=\"text/xsl\" href=\"" << url << "\"?>\n";
+    }
+
+    XmlWriter& XmlWriter::writeBlankLine() {
+        ensureTagClosed();
+        m_os << '\n';
+        return *this;
+    }
+
+    void XmlWriter::ensureTagClosed() {
+        if( m_tagIsOpen ) {
+            m_os << ">" << std::endl;
+            m_tagIsOpen = false;
+        }
+    }
+
+    void XmlWriter::writeDeclaration() {
+        m_os << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
+    }
+
+    void XmlWriter::newlineIfNecessary() {
+        if( m_needsNewline ) {
+            m_os << std::endl;
+            m_needsNewline = false;
+        }
+    }
+}
+// end catch_xmlwriter.cpp
+// start catch_reporter_bases.cpp
+
+#include <cstring>
+#include <cfloat>
+#include <cstdio>
+#include <cassert>
+#include <memory>
+
+namespace Catch {
+    void prepareExpandedExpression(AssertionResult& result) {
+        result.getExpandedExpression();
+    }
+
+    // Because formatting using c++ streams is stateful, drop down to C is required
+    // Alternatively we could use stringstream, but its performance is... not good.
+    std::string getFormattedDuration( double duration ) {
+        // Max exponent + 1 is required to represent the whole part
+        // + 1 for decimal point
+        // + 3 for the 3 decimal places
+        // + 1 for null terminator
+        const std::size_t maxDoubleSize = DBL_MAX_10_EXP + 1 + 1 + 3 + 1;
+        char buffer[maxDoubleSize];
+
+        // Save previous errno, to prevent sprintf from overwriting it
+        ErrnoGuard guard;
+#ifdef _MSC_VER
+        sprintf_s(buffer, "%.3f", duration);
+#else
+        sprintf(buffer, "%.3f", duration);
+#endif
+        return std::string(buffer);
+    }
+
+    TestEventListenerBase::TestEventListenerBase(ReporterConfig const & _config)
+        :StreamingReporterBase(_config) {}
+
+    std::set<Verbosity> TestEventListenerBase::getSupportedVerbosities() {
+        return { Verbosity::Quiet, Verbosity::Normal, Verbosity::High };
+    }
+
+    void TestEventListenerBase::assertionStarting(AssertionInfo const &) {}
+
+    bool TestEventListenerBase::assertionEnded(AssertionStats const &) {
+        return false;
+    }
+
+} // end namespace Catch
+// end catch_reporter_bases.cpp
+// start catch_reporter_compact.cpp
+
+namespace {
+
+#ifdef CATCH_PLATFORM_MAC
+    const char* failedString() { return "FAILED"; }
+    const char* passedString() { return "PASSED"; }
+#else
+    const char* failedString() { return "failed"; }
+    const char* passedString() { return "passed"; }
+#endif
+
+    // Colour::LightGrey
+    Catch::Colour::Code dimColour() { return Catch::Colour::FileName; }
+
+    std::string bothOrAll( std::size_t count ) {
+        return count == 1 ? std::string() :
+               count == 2 ? "both " : "all " ;
+    }
+
+} // anon namespace
+
+namespace Catch {
+namespace {
+// Colour, message variants:
+// - white: No tests ran.
+// -   red: Failed [both/all] N test cases, failed [both/all] M assertions.
+// - white: Passed [both/all] N test cases (no assertions).
+// -   red: Failed N tests cases, failed M assertions.
+// - green: Passed [both/all] N tests cases with M assertions.
+void printTotals(std::ostream& out, const Totals& totals) {
+    if (totals.testCases.total() == 0) {
+        out << "No tests ran.";
+    } else if (totals.testCases.failed == totals.testCases.total()) {
+        Colour colour(Colour::ResultError);
+        const std::string qualify_assertions_failed =
+            totals.assertions.failed == totals.assertions.total() ?
+            bothOrAll(totals.assertions.failed) : std::string();
+        out <<
+            "Failed " << bothOrAll(totals.testCases.failed)
+            << pluralise(totals.testCases.failed, "test case") << ", "
+            "failed " << qualify_assertions_failed <<
+            pluralise(totals.assertions.failed, "assertion") << '.';
+    } else if (totals.assertions.total() == 0) {
+        out <<
+            "Passed " << bothOrAll(totals.testCases.total())
+            << pluralise(totals.testCases.total(), "test case")
+            << " (no assertions).";
+    } else if (totals.assertions.failed) {
+        Colour colour(Colour::ResultError);
+        out <<
+            "Failed " << pluralise(totals.testCases.failed, "test case") << ", "
+            "failed " << pluralise(totals.assertions.failed, "assertion") << '.';
+    } else {
+        Colour colour(Colour::ResultSuccess);
+        out <<
+            "Passed " << bothOrAll(totals.testCases.passed)
+            << pluralise(totals.testCases.passed, "test case") <<
+            " with " << pluralise(totals.assertions.passed, "assertion") << '.';
+    }
+}
+
+// Implementation of CompactReporter formatting
+class AssertionPrinter {
+public:
+    AssertionPrinter& operator= (AssertionPrinter const&) = delete;
+    AssertionPrinter(AssertionPrinter const&) = delete;
+    AssertionPrinter(std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages)
+        : stream(_stream)
+        , result(_stats.assertionResult)
+        , messages(_stats.infoMessages)
+        , itMessage(_stats.infoMessages.begin())
+        , printInfoMessages(_printInfoMessages) {}
+
+    void print() {
+        printSourceInfo();
+
+        itMessage = messages.begin();
+
+        switch (result.getResultType()) {
+        case ResultWas::Ok:
+            printResultType(Colour::ResultSuccess, passedString());
+            printOriginalExpression();
+            printReconstructedExpression();
+            if (!result.hasExpression())
+                printRemainingMessages(Colour::None);
+            else
+                printRemainingMessages();
+            break;
+        case ResultWas::ExpressionFailed:
+            if (result.isOk())
+                printResultType(Colour::ResultSuccess, failedString() + std::string(" - but was ok"));
+            else
+                printResultType(Colour::Error, failedString());
+            printOriginalExpression();
+            printReconstructedExpression();
+            printRemainingMessages();
+            break;
+        case ResultWas::ThrewException:
+            printResultType(Colour::Error, failedString());
+            printIssue("unexpected exception with message:");
+            printMessage();
+            printExpressionWas();
+            printRemainingMessages();
+            break;
+        case ResultWas::FatalErrorCondition:
+            printResultType(Colour::Error, failedString());
+            printIssue("fatal error condition with message:");
+            printMessage();
+            printExpressionWas();
+            printRemainingMessages();
+            break;
+        case ResultWas::DidntThrowException:
+            printResultType(Colour::Error, failedString());
+            printIssue("expected exception, got none");
+            printExpressionWas();
+            printRemainingMessages();
+            break;
+        case ResultWas::Info:
+            printResultType(Colour::None, "info");
+            printMessage();
+            printRemainingMessages();
+            break;
+        case ResultWas::Warning:
+            printResultType(Colour::None, "warning");
+            printMessage();
+            printRemainingMessages();
+            break;
+        case ResultWas::ExplicitFailure:
+            printResultType(Colour::Error, failedString());
+            printIssue("explicitly");
+            printRemainingMessages(Colour::None);
+            break;
+            // These cases are here to prevent compiler warnings
+        case ResultWas::Unknown:
+        case ResultWas::FailureBit:
+        case ResultWas::Exception:
+            printResultType(Colour::Error, "** internal error **");
+            break;
+        }
+    }
+
+private:
+    void printSourceInfo() const {
+        Colour colourGuard(Colour::FileName);
+        stream << result.getSourceInfo() << ':';
+    }
+
+    void printResultType(Colour::Code colour, std::string const& passOrFail) const {
+        if (!passOrFail.empty()) {
+            {
+                Colour colourGuard(colour);
+                stream << ' ' << passOrFail;
+            }
+            stream << ':';
+        }
+    }
+
+    void printIssue(std::string const& issue) const {
+        stream << ' ' << issue;
+    }
+
+    void printExpressionWas() {
+        if (result.hasExpression()) {
+            stream << ';';
+            {
+                Colour colour(dimColour());
+                stream << " expression was:";
+            }
+            printOriginalExpression();
+        }
+    }
+
+    void printOriginalExpression() const {
+        if (result.hasExpression()) {
+            stream << ' ' << result.getExpression();
+        }
+    }
+
+    void printReconstructedExpression() const {
+        if (result.hasExpandedExpression()) {
+            {
+                Colour colour(dimColour());
+                stream << " for: ";
+            }
+            stream << result.getExpandedExpression();
+        }
+    }
+
+    void printMessage() {
+        if (itMessage != messages.end()) {
+            stream << " '" << itMessage->message << '\'';
+            ++itMessage;
+        }
+    }
+
+    void printRemainingMessages(Colour::Code colour = dimColour()) {
+        if (itMessage == messages.end())
+            return;
+
+        // using messages.end() directly yields (or auto) compilation error:
+        std::vector<MessageInfo>::const_iterator itEnd = messages.end();
+        const std::size_t N = static_cast<std::size_t>(std::distance(itMessage, itEnd));
+
+        {
+            Colour colourGuard(colour);
+            stream << " with " << pluralise(N, "message") << ':';
+        }
+
+        for (; itMessage != itEnd; ) {
+            // If this assertion is a warning ignore any INFO messages
+            if (printInfoMessages || itMessage->type != ResultWas::Info) {
+                stream << " '" << itMessage->message << '\'';
+                if (++itMessage != itEnd) {
+                    Colour colourGuard(dimColour());
+                    stream << " and";
+                }
+            }
+        }
+    }
+
+private:
+    std::ostream& stream;
+    AssertionResult const& result;
+    std::vector<MessageInfo> messages;
+    std::vector<MessageInfo>::const_iterator itMessage;
+    bool printInfoMessages;
+};
+
+} // anon namespace
+
+        std::string CompactReporter::getDescription() {
+            return "Reports test results on a single line, suitable for IDEs";
+        }
+
+        ReporterPreferences CompactReporter::getPreferences() const {
+            return m_reporterPrefs;
+        }
+
+        void CompactReporter::noMatchingTestCases( std::string const& spec ) {
+            stream << "No test cases matched '" << spec << '\'' << std::endl;
+        }
+
+        void CompactReporter::assertionStarting( AssertionInfo const& ) {}
+
+        bool CompactReporter::assertionEnded( AssertionStats const& _assertionStats ) {
+            AssertionResult const& result = _assertionStats.assertionResult;
+
+            bool printInfoMessages = true;
+
+            // Drop out if result was successful and we're not printing those
+            if( !m_config->includeSuccessfulResults() && result.isOk() ) {
+                if( result.getResultType() != ResultWas::Warning )
+                    return false;
+                printInfoMessages = false;
+            }
+
+            AssertionPrinter printer( stream, _assertionStats, printInfoMessages );
+            printer.print();
+
+            stream << std::endl;
+            return true;
+        }
+
+        void CompactReporter::sectionEnded(SectionStats const& _sectionStats) {
+            if (m_config->showDurations() == ShowDurations::Always) {
+                stream << getFormattedDuration(_sectionStats.durationInSeconds) << " s: " << _sectionStats.sectionInfo.name << std::endl;
+            }
+        }
+
+        void CompactReporter::testRunEnded( TestRunStats const& _testRunStats ) {
+            printTotals( stream, _testRunStats.totals );
+            stream << '\n' << std::endl;
+            StreamingReporterBase::testRunEnded( _testRunStats );
+        }
+
+        CompactReporter::~CompactReporter() {}
+
+    CATCH_REGISTER_REPORTER( "compact", CompactReporter )
+
+} // end namespace Catch
+// end catch_reporter_compact.cpp
+// start catch_reporter_console.cpp
+
+#include <cfloat>
+#include <cstdio>
+
+#if defined(_MSC_VER)
+#pragma warning(push)
+#pragma warning(disable:4061) // Not all labels are EXPLICITLY handled in switch
+ // Note that 4062 (not all labels are handled
+ // and default is missing) is enabled
+#endif
+
+namespace Catch {
+
+namespace {
+
+// Formatter impl for ConsoleReporter
+class ConsoleAssertionPrinter {
+public:
+    ConsoleAssertionPrinter& operator= (ConsoleAssertionPrinter const&) = delete;
+    ConsoleAssertionPrinter(ConsoleAssertionPrinter const&) = delete;
+    ConsoleAssertionPrinter(std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages)
+        : stream(_stream),
+        stats(_stats),
+        result(_stats.assertionResult),
+        colour(Colour::None),
+        message(result.getMessage()),
+        messages(_stats.infoMessages),
+        printInfoMessages(_printInfoMessages) {
+        switch (result.getResultType()) {
+        case ResultWas::Ok:
+            colour = Colour::Success;
+            passOrFail = "PASSED";
+            //if( result.hasMessage() )
+            if (_stats.infoMessages.size() == 1)
+                messageLabel = "with message";
+            if (_stats.infoMessages.size() > 1)
+                messageLabel = "with messages";
+            break;
+        case ResultWas::ExpressionFailed:
+            if (result.isOk()) {
+                colour = Colour::Success;
+                passOrFail = "FAILED - but was ok";
+            } else {
+                colour = Colour::Error;
+                passOrFail = "FAILED";
+            }
+            if (_stats.infoMessages.size() == 1)
+                messageLabel = "with message";
+            if (_stats.infoMessages.size() > 1)
+                messageLabel = "with messages";
+            break;
+        case ResultWas::ThrewException:
+            colour = Colour::Error;
+            passOrFail = "FAILED";
+            messageLabel = "due to unexpected exception with ";
+            if (_stats.infoMessages.size() == 1)
+                messageLabel += "message";
+            if (_stats.infoMessages.size() > 1)
+                messageLabel += "messages";
+            break;
+        case ResultWas::FatalErrorCondition:
+            colour = Colour::Error;
+            passOrFail = "FAILED";
+            messageLabel = "due to a fatal error condition";
+            break;
+        case ResultWas::DidntThrowException:
+            colour = Colour::Error;
+            passOrFail = "FAILED";
+            messageLabel = "because no exception was thrown where one was expected";
+            break;
+        case ResultWas::Info:
+            messageLabel = "info";
+            break;
+        case ResultWas::Warning:
+            messageLabel = "warning";
+            break;
+        case ResultWas::ExplicitFailure:
+            passOrFail = "FAILED";
+            colour = Colour::Error;
+            if (_stats.infoMessages.size() == 1)
+                messageLabel = "explicitly with message";
+            if (_stats.infoMessages.size() > 1)
+                messageLabel = "explicitly with messages";
+            break;
+            // These cases are here to prevent compiler warnings
+        case ResultWas::Unknown:
+        case ResultWas::FailureBit:
+        case ResultWas::Exception:
+            passOrFail = "** internal error **";
+            colour = Colour::Error;
+            break;
+        }
+    }
+
+    void print() const {
+        printSourceInfo();
+        if (stats.totals.assertions.total() > 0) {
+            printResultType();
+            printOriginalExpression();
+            printReconstructedExpression();
+        } else {
+            stream << '\n';
+        }
+        printMessage();
+    }
+
+private:
+    void printResultType() const {
+        if (!passOrFail.empty()) {
+            Colour colourGuard(colour);
+            stream << passOrFail << ":\n";
+        }
+    }
+    void printOriginalExpression() const {
+        if (result.hasExpression()) {
+            Colour colourGuard(Colour::OriginalExpression);
+            stream << "  ";
+            stream << result.getExpressionInMacro();
+            stream << '\n';
+        }
+    }
+    void printReconstructedExpression() const {
+        if (result.hasExpandedExpression()) {
+            stream << "with expansion:\n";
+            Colour colourGuard(Colour::ReconstructedExpression);
+            stream << Column(result.getExpandedExpression()).indent(2) << '\n';
+        }
+    }
+    void printMessage() const {
+        if (!messageLabel.empty())
+            stream << messageLabel << ':' << '\n';
+        for (auto const& msg : messages) {
+            // If this assertion is a warning ignore any INFO messages
+            if (printInfoMessages || msg.type != ResultWas::Info)
+                stream << Column(msg.message).indent(2) << '\n';
+        }
+    }
+    void printSourceInfo() const {
+        Colour colourGuard(Colour::FileName);
+        stream << result.getSourceInfo() << ": ";
+    }
+
+    std::ostream& stream;
+    AssertionStats const& stats;
+    AssertionResult const& result;
+    Colour::Code colour;
+    std::string passOrFail;
+    std::string messageLabel;
+    std::string message;
+    std::vector<MessageInfo> messages;
+    bool printInfoMessages;
+};
+
+std::size_t makeRatio(std::size_t number, std::size_t total) {
+    std::size_t ratio = total > 0 ? CATCH_CONFIG_CONSOLE_WIDTH * number / total : 0;
+    return (ratio == 0 && number > 0) ? 1 : ratio;
+}
+
+std::size_t& findMax(std::size_t& i, std::size_t& j, std::size_t& k) {
+    if (i > j && i > k)
+        return i;
+    else if (j > k)
+        return j;
+    else
+        return k;
+}
+
+struct ColumnInfo {
+    enum Justification { Left, Right };
+    std::string name;
+    int width;
+    Justification justification;
+};
+struct ColumnBreak {};
+struct RowBreak {};
+
+class Duration {
+    enum class Unit {
+        Auto,
+        Nanoseconds,
+        Microseconds,
+        Milliseconds,
+        Seconds,
+        Minutes
+    };
+    static const uint64_t s_nanosecondsInAMicrosecond = 1000;
+    static const uint64_t s_nanosecondsInAMillisecond = 1000 * s_nanosecondsInAMicrosecond;
+    static const uint64_t s_nanosecondsInASecond = 1000 * s_nanosecondsInAMillisecond;
+    static const uint64_t s_nanosecondsInAMinute = 60 * s_nanosecondsInASecond;
+
+    uint64_t m_inNanoseconds;
+    Unit m_units;
+
+public:
+    explicit Duration(uint64_t inNanoseconds, Unit units = Unit::Auto)
+        : m_inNanoseconds(inNanoseconds),
+        m_units(units) {
+        if (m_units == Unit::Auto) {
+            if (m_inNanoseconds < s_nanosecondsInAMicrosecond)
+                m_units = Unit::Nanoseconds;
+            else if (m_inNanoseconds < s_nanosecondsInAMillisecond)
+                m_units = Unit::Microseconds;
+            else if (m_inNanoseconds < s_nanosecondsInASecond)
+                m_units = Unit::Milliseconds;
+            else if (m_inNanoseconds < s_nanosecondsInAMinute)
+                m_units = Unit::Seconds;
+            else
+                m_units = Unit::Minutes;
+        }
+
+    }
+
+    auto value() const -> double {
+        switch (m_units) {
+        case Unit::Microseconds:
+            return m_inNanoseconds / static_cast<double>(s_nanosecondsInAMicrosecond);
+        case Unit::Milliseconds:
+            return m_inNanoseconds / static_cast<double>(s_nanosecondsInAMillisecond);
+        case Unit::Seconds:
+            return m_inNanoseconds / static_cast<double>(s_nanosecondsInASecond);
+        case Unit::Minutes:
+            return m_inNanoseconds / static_cast<double>(s_nanosecondsInAMinute);
+        default:
+            return static_cast<double>(m_inNanoseconds);
+        }
+    }
+    auto unitsAsString() const -> std::string {
+        switch (m_units) {
+        case Unit::Nanoseconds:
+            return "ns";
+        case Unit::Microseconds:
+            return "µs";
+        case Unit::Milliseconds:
+            return "ms";
+        case Unit::Seconds:
+            return "s";
+        case Unit::Minutes:
+            return "m";
+        default:
+            return "** internal error **";
+        }
+
+    }
+    friend auto operator << (std::ostream& os, Duration const& duration) -> std::ostream& {
+        return os << duration.value() << " " << duration.unitsAsString();
+    }
+};
+} // end anon namespace
+
+class TablePrinter {
+    std::ostream& m_os;
+    std::vector<ColumnInfo> m_columnInfos;
+    std::ostringstream m_oss;
+    int m_currentColumn = -1;
+    bool m_isOpen = false;
+
+public:
+    TablePrinter( std::ostream& os, std::vector<ColumnInfo> columnInfos )
+    :   m_os( os ),
+        m_columnInfos( std::move( columnInfos ) ) {}
+
+    auto columnInfos() const -> std::vector<ColumnInfo> const& {
+        return m_columnInfos;
+    }
+
+    void open() {
+        if (!m_isOpen) {
+            m_isOpen = true;
+            *this << RowBreak();
+            for (auto const& info : m_columnInfos)
+                *this << info.name << ColumnBreak();
+            *this << RowBreak();
+            m_os << Catch::getLineOfChars<'-'>() << "\n";
+        }
+    }
+    void close() {
+        if (m_isOpen) {
+            *this << RowBreak();
+            m_os << std::endl;
+            m_isOpen = false;
+        }
+    }
+
+    template<typename T>
+    friend TablePrinter& operator << (TablePrinter& tp, T const& value) {
+        tp.m_oss << value;
+        return tp;
+    }
+
+    friend TablePrinter& operator << (TablePrinter& tp, ColumnBreak) {
+        auto colStr = tp.m_oss.str();
+        // This takes account of utf8 encodings
+        auto strSize = Catch::StringRef(colStr).numberOfCharacters();
+        tp.m_oss.str("");
+        tp.open();
+        if (tp.m_currentColumn == static_cast<int>(tp.m_columnInfos.size() - 1)) {
+            tp.m_currentColumn = -1;
+            tp.m_os << "\n";
+        }
+        tp.m_currentColumn++;
+
+        auto colInfo = tp.m_columnInfos[tp.m_currentColumn];
+        auto padding = (strSize + 2 < static_cast<std::size_t>(colInfo.width))
+            ? std::string(colInfo.width - (strSize + 2), ' ')
+            : std::string();
+        if (colInfo.justification == ColumnInfo::Left)
+            tp.m_os << colStr << padding << " ";
+        else
+            tp.m_os << padding << colStr << " ";
+        return tp;
+    }
+
+    friend TablePrinter& operator << (TablePrinter& tp, RowBreak) {
+        if (tp.m_currentColumn > 0) {
+            tp.m_os << "\n";
+            tp.m_currentColumn = -1;
+        }
+        return tp;
+    }
+};
+
+ConsoleReporter::ConsoleReporter(ReporterConfig const& config)
+    : StreamingReporterBase(config),
+    m_tablePrinter(new TablePrinter(config.stream(),
+    {
+        { "benchmark name", CATCH_CONFIG_CONSOLE_WIDTH - 32, ColumnInfo::Left },
+        { "iters", 8, ColumnInfo::Right },
+        { "elapsed ns", 14, ColumnInfo::Right },
+        { "average", 14, ColumnInfo::Right }
+    })) {}
+ConsoleReporter::~ConsoleReporter() = default;
+
+std::string ConsoleReporter::getDescription() {
+    return "Reports test results as plain lines of text";
+}
+
+void ConsoleReporter::noMatchingTestCases(std::string const& spec) {
+    stream << "No test cases matched '" << spec << '\'' << std::endl;
+}
+
+void ConsoleReporter::assertionStarting(AssertionInfo const&) {}
+
+bool ConsoleReporter::assertionEnded(AssertionStats const& _assertionStats) {
+    AssertionResult const& result = _assertionStats.assertionResult;
+
+    bool includeResults = m_config->includeSuccessfulResults() || !result.isOk();
+
+    // Drop out if result was successful but we're not printing them.
+    if (!includeResults && result.getResultType() != ResultWas::Warning)
+        return false;
+
+    lazyPrint();
+
+    ConsoleAssertionPrinter printer(stream, _assertionStats, includeResults);
+    printer.print();
+    stream << std::endl;
+    return true;
+}
+
+void ConsoleReporter::sectionStarting(SectionInfo const& _sectionInfo) {
+    m_headerPrinted = false;
+    StreamingReporterBase::sectionStarting(_sectionInfo);
+}
+void ConsoleReporter::sectionEnded(SectionStats const& _sectionStats) {
+    m_tablePrinter->close();
+    if (_sectionStats.missingAssertions) {
+        lazyPrint();
+        Colour colour(Colour::ResultError);
+        if (m_sectionStack.size() > 1)
+            stream << "\nNo assertions in section";
+        else
+            stream << "\nNo assertions in test case";
+        stream << " '" << _sectionStats.sectionInfo.name << "'\n" << std::endl;
+    }
+    if (m_config->showDurations() == ShowDurations::Always) {
+        stream << getFormattedDuration(_sectionStats.durationInSeconds) << " s: " << _sectionStats.sectionInfo.name << std::endl;
+    }
+    if (m_headerPrinted) {
+        m_headerPrinted = false;
+    }
+    StreamingReporterBase::sectionEnded(_sectionStats);
+}
+
+void ConsoleReporter::benchmarkStarting(BenchmarkInfo const& info) {
+    lazyPrintWithoutClosingBenchmarkTable();
+
+    auto nameCol = Column( info.name ).width( static_cast<std::size_t>( m_tablePrinter->columnInfos()[0].width - 2 ) );
+
+    bool firstLine = true;
+    for (auto line : nameCol) {
+        if (!firstLine)
+            (*m_tablePrinter) << ColumnBreak() << ColumnBreak() << ColumnBreak();
+        else
+            firstLine = false;
+
+        (*m_tablePrinter) << line << ColumnBreak();
+    }
+}
+void ConsoleReporter::benchmarkEnded(BenchmarkStats const& stats) {
+    Duration average(stats.elapsedTimeInNanoseconds / stats.iterations);
+    (*m_tablePrinter)
+        << stats.iterations << ColumnBreak()
+        << stats.elapsedTimeInNanoseconds << ColumnBreak()
+        << average << ColumnBreak();
+}
+
+void ConsoleReporter::testCaseEnded(TestCaseStats const& _testCaseStats) {
+    m_tablePrinter->close();
+    StreamingReporterBase::testCaseEnded(_testCaseStats);
+    m_headerPrinted = false;
+}
+void ConsoleReporter::testGroupEnded(TestGroupStats const& _testGroupStats) {
+    if (currentGroupInfo.used) {
+        printSummaryDivider();
+        stream << "Summary for group '" << _testGroupStats.groupInfo.name << "':\n";
+        printTotals(_testGroupStats.totals);
+        stream << '\n' << std::endl;
+    }
+    StreamingReporterBase::testGroupEnded(_testGroupStats);
+}
+void ConsoleReporter::testRunEnded(TestRunStats const& _testRunStats) {
+    printTotalsDivider(_testRunStats.totals);
+    printTotals(_testRunStats.totals);
+    stream << std::endl;
+    StreamingReporterBase::testRunEnded(_testRunStats);
+}
+
+void ConsoleReporter::lazyPrint() {
+
+    m_tablePrinter->close();
+    lazyPrintWithoutClosingBenchmarkTable();
+}
+
+void ConsoleReporter::lazyPrintWithoutClosingBenchmarkTable() {
+
+    if (!currentTestRunInfo.used)
+        lazyPrintRunInfo();
+    if (!currentGroupInfo.used)
+        lazyPrintGroupInfo();
+
+    if (!m_headerPrinted) {
+        printTestCaseAndSectionHeader();
+        m_headerPrinted = true;
+    }
+}
+void ConsoleReporter::lazyPrintRunInfo() {
+    stream << '\n' << getLineOfChars<'~'>() << '\n';
+    Colour colour(Colour::SecondaryText);
+    stream << currentTestRunInfo->name
+        << " is a Catch v" << libraryVersion() << " host application.\n"
+        << "Run with -? for options\n\n";
+
+    if (m_config->rngSeed() != 0)
+        stream << "Randomness seeded to: " << m_config->rngSeed() << "\n\n";
+
+    currentTestRunInfo.used = true;
+}
+void ConsoleReporter::lazyPrintGroupInfo() {
+    if (!currentGroupInfo->name.empty() && currentGroupInfo->groupsCounts > 1) {
+        printClosedHeader("Group: " + currentGroupInfo->name);
+        currentGroupInfo.used = true;
+    }
+}
+void ConsoleReporter::printTestCaseAndSectionHeader() {
+    assert(!m_sectionStack.empty());
+    printOpenHeader(currentTestCaseInfo->name);
+
+    if (m_sectionStack.size() > 1) {
+        Colour colourGuard(Colour::Headers);
+
+        auto
+            it = m_sectionStack.begin() + 1, // Skip first section (test case)
+            itEnd = m_sectionStack.end();
+        for (; it != itEnd; ++it)
+            printHeaderString(it->name, 2);
+    }
+
+    SourceLineInfo lineInfo = m_sectionStack.back().lineInfo;
+
+    if (!lineInfo.empty()) {
+        stream << getLineOfChars<'-'>() << '\n';
+        Colour colourGuard(Colour::FileName);
+        stream << lineInfo << '\n';
+    }
+    stream << getLineOfChars<'.'>() << '\n' << std::endl;
+}
+
+void ConsoleReporter::printClosedHeader(std::string const& _name) {
+    printOpenHeader(_name);
+    stream << getLineOfChars<'.'>() << '\n';
+}
+void ConsoleReporter::printOpenHeader(std::string const& _name) {
+    stream << getLineOfChars<'-'>() << '\n';
+    {
+        Colour colourGuard(Colour::Headers);
+        printHeaderString(_name);
+    }
+}
+
+// if string has a : in first line will set indent to follow it on
+// subsequent lines
+void ConsoleReporter::printHeaderString(std::string const& _string, std::size_t indent) {
+    std::size_t i = _string.find(": ");
+    if (i != std::string::npos)
+        i += 2;
+    else
+        i = 0;
+    stream << Column(_string).indent(indent + i).initialIndent(indent) << '\n';
+}
+
+struct SummaryColumn {
+
+    SummaryColumn( std::string _label, Colour::Code _colour )
+    :   label( std::move( _label ) ),
+        colour( _colour ) {}
+    SummaryColumn addRow( std::size_t count ) {
+        ReusableStringStream rss;
+        rss << count;
+        std::string row = rss.str();
+        for (auto& oldRow : rows) {
+            while (oldRow.size() < row.size())
+                oldRow = ' ' + oldRow;
+            while (oldRow.size() > row.size())
+                row = ' ' + row;
+        }
+        rows.push_back(row);
+        return *this;
+    }
+
+    std::string label;
+    Colour::Code colour;
+    std::vector<std::string> rows;
+
+};
+
+void ConsoleReporter::printTotals( Totals const& totals ) {
+    if (totals.testCases.total() == 0) {
+        stream << Colour(Colour::Warning) << "No tests ran\n";
+    } else if (totals.assertions.total() > 0 && totals.testCases.allPassed()) {
+        stream << Colour(Colour::ResultSuccess) << "All tests passed";
+        stream << " ("
+            << pluralise(totals.assertions.passed, "assertion") << " in "
+            << pluralise(totals.testCases.passed, "test case") << ')'
+            << '\n';
+    } else {
+
+        std::vector<SummaryColumn> columns;
+        columns.push_back(SummaryColumn("", Colour::None)
+                          .addRow(totals.testCases.total())
+                          .addRow(totals.assertions.total()));
+        columns.push_back(SummaryColumn("passed", Colour::Success)
+                          .addRow(totals.testCases.passed)
+                          .addRow(totals.assertions.passed));
+        columns.push_back(SummaryColumn("failed", Colour::ResultError)
+                          .addRow(totals.testCases.failed)
+                          .addRow(totals.assertions.failed));
+        columns.push_back(SummaryColumn("failed as expected", Colour::ResultExpectedFailure)
+                          .addRow(totals.testCases.failedButOk)
+                          .addRow(totals.assertions.failedButOk));
+
+        printSummaryRow("test cases", columns, 0);
+        printSummaryRow("assertions", columns, 1);
+    }
+}
+void ConsoleReporter::printSummaryRow(std::string const& label, std::vector<SummaryColumn> const& cols, std::size_t row) {
+    for (auto col : cols) {
+        std::string value = col.rows[row];
+        if (col.label.empty()) {
+            stream << label << ": ";
+            if (value != "0")
+                stream << value;
+            else
+                stream << Colour(Colour::Warning) << "- none -";
+        } else if (value != "0") {
+            stream << Colour(Colour::LightGrey) << " | ";
+            stream << Colour(col.colour)
+                << value << ' ' << col.label;
+        }
+    }
+    stream << '\n';
+}
+
+void ConsoleReporter::printTotalsDivider(Totals const& totals) {
+    if (totals.testCases.total() > 0) {
+        std::size_t failedRatio = makeRatio(totals.testCases.failed, totals.testCases.total());
+        std::size_t failedButOkRatio = makeRatio(totals.testCases.failedButOk, totals.testCases.total());
+        std::size_t passedRatio = makeRatio(totals.testCases.passed, totals.testCases.total());
+        while (failedRatio + failedButOkRatio + passedRatio < CATCH_CONFIG_CONSOLE_WIDTH - 1)
+            findMax(failedRatio, failedButOkRatio, passedRatio)++;
+        while (failedRatio + failedButOkRatio + passedRatio > CATCH_CONFIG_CONSOLE_WIDTH - 1)
+            findMax(failedRatio, failedButOkRatio, passedRatio)--;
+
+        stream << Colour(Colour::Error) << std::string(failedRatio, '=');
+        stream << Colour(Colour::ResultExpectedFailure) << std::string(failedButOkRatio, '=');
+        if (totals.testCases.allPassed())
+            stream << Colour(Colour::ResultSuccess) << std::string(passedRatio, '=');
+        else
+            stream << Colour(Colour::Success) << std::string(passedRatio, '=');
+    } else {
+        stream << Colour(Colour::Warning) << std::string(CATCH_CONFIG_CONSOLE_WIDTH - 1, '=');
+    }
+    stream << '\n';
+}
+void ConsoleReporter::printSummaryDivider() {
+    stream << getLineOfChars<'-'>() << '\n';
+}
+
+CATCH_REGISTER_REPORTER("console", ConsoleReporter)
+
+} // end namespace Catch
+
+#if defined(_MSC_VER)
+#pragma warning(pop)
+#endif
+// end catch_reporter_console.cpp
+// start catch_reporter_junit.cpp
+
+#include <cassert>
+#include <sstream>
+#include <ctime>
+#include <algorithm>
+
+namespace Catch {
+
+    namespace {
+        std::string getCurrentTimestamp() {
+            // Beware, this is not reentrant because of backward compatibility issues
+            // Also, UTC only, again because of backward compatibility (%z is C++11)
+            time_t rawtime;
+            std::time(&rawtime);
+            auto const timeStampSize = sizeof("2017-01-16T17:06:45Z");
+
+#ifdef _MSC_VER
+            std::tm timeInfo = {};
+            gmtime_s(&timeInfo, &rawtime);
+#else
+            std::tm* timeInfo;
+            timeInfo = std::gmtime(&rawtime);
+#endif
+
+            char timeStamp[timeStampSize];
+            const char * const fmt = "%Y-%m-%dT%H:%M:%SZ";
+
+#ifdef _MSC_VER
+            std::strftime(timeStamp, timeStampSize, fmt, &timeInfo);
+#else
+            std::strftime(timeStamp, timeStampSize, fmt, timeInfo);
+#endif
+            return std::string(timeStamp);
+        }
+
+        std::string fileNameTag(const std::vector<std::string> &tags) {
+            auto it = std::find_if(begin(tags),
+                                   end(tags),
+                                   [] (std::string const& tag) {return tag.front() == '#'; });
+            if (it != tags.end())
+                return it->substr(1);
+            return std::string();
+        }
+    } // anonymous namespace
+
+    JunitReporter::JunitReporter( ReporterConfig const& _config )
+        :   CumulativeReporterBase( _config ),
+            xml( _config.stream() )
+        {
+            m_reporterPrefs.shouldRedirectStdOut = true;
+            m_reporterPrefs.shouldReportAllAssertions = true;
+        }
+
+    JunitReporter::~JunitReporter() {}
+
+    std::string JunitReporter::getDescription() {
+        return "Reports test results in an XML format that looks like Ant's junitreport target";
+    }
+
+    void JunitReporter::noMatchingTestCases( std::string const& /*spec*/ ) {}
+
+    void JunitReporter::testRunStarting( TestRunInfo const& runInfo )  {
+        CumulativeReporterBase::testRunStarting( runInfo );
+        xml.startElement( "testsuites" );
+    }
+
+    void JunitReporter::testGroupStarting( GroupInfo const& groupInfo ) {
+        suiteTimer.start();
+        stdOutForSuite.clear();
+        stdErrForSuite.clear();
+        unexpectedExceptions = 0;
+        CumulativeReporterBase::testGroupStarting( groupInfo );
+    }
+
+    void JunitReporter::testCaseStarting( TestCaseInfo const& testCaseInfo ) {
+        m_okToFail = testCaseInfo.okToFail();
+    }
+
+    bool JunitReporter::assertionEnded( AssertionStats const& assertionStats ) {
+        if( assertionStats.assertionResult.getResultType() == ResultWas::ThrewException && !m_okToFail )
+            unexpectedExceptions++;
+        return CumulativeReporterBase::assertionEnded( assertionStats );
+    }
+
+    void JunitReporter::testCaseEnded( TestCaseStats const& testCaseStats ) {
+        stdOutForSuite += testCaseStats.stdOut;
+        stdErrForSuite += testCaseStats.stdErr;
+        CumulativeReporterBase::testCaseEnded( testCaseStats );
+    }
+
+    void JunitReporter::testGroupEnded( TestGroupStats const& testGroupStats ) {
+        double suiteTime = suiteTimer.getElapsedSeconds();
+        CumulativeReporterBase::testGroupEnded( testGroupStats );
+        writeGroup( *m_testGroups.back(), suiteTime );
+    }
+
+    void JunitReporter::testRunEndedCumulative() {
+        xml.endElement();
+    }
+
+    void JunitReporter::writeGroup( TestGroupNode const& groupNode, double suiteTime ) {
+        XmlWriter::ScopedElement e = xml.scopedElement( "testsuite" );
+        TestGroupStats const& stats = groupNode.value;
+        xml.writeAttribute( "name", stats.groupInfo.name );
+        xml.writeAttribute( "errors", unexpectedExceptions );
+        xml.writeAttribute( "failures", stats.totals.assertions.failed-unexpectedExceptions );
+        xml.writeAttribute( "tests", stats.totals.assertions.total() );
+        xml.writeAttribute( "hostname", "tbd" ); // !TBD
+        if( m_config->showDurations() == ShowDurations::Never )
+            xml.writeAttribute( "time", "" );
+        else
+            xml.writeAttribute( "time", suiteTime );
+        xml.writeAttribute( "timestamp", getCurrentTimestamp() );
+
+        // Write test cases
+        for( auto const& child : groupNode.children )
+            writeTestCase( *child );
+
+        xml.scopedElement( "system-out" ).writeText( trim( stdOutForSuite ), false );
+        xml.scopedElement( "system-err" ).writeText( trim( stdErrForSuite ), false );
+    }
+
+    void JunitReporter::writeTestCase( TestCaseNode const& testCaseNode ) {
+        TestCaseStats const& stats = testCaseNode.value;
+
+        // All test cases have exactly one section - which represents the
+        // test case itself. That section may have 0-n nested sections
+        assert( testCaseNode.children.size() == 1 );
+        SectionNode const& rootSection = *testCaseNode.children.front();
+
+        std::string className = stats.testInfo.className;
+
+        if( className.empty() ) {
+            className = fileNameTag(stats.testInfo.tags);
+            if ( className.empty() )
+                className = "global";
+        }
+
+        if ( !m_config->name().empty() )
+            className = m_config->name() + "." + className;
+
+        writeSection( className, "", rootSection );
+    }
+
+    void JunitReporter::writeSection(  std::string const& className,
+                        std::string const& rootName,
+                        SectionNode const& sectionNode ) {
+        std::string name = trim( sectionNode.stats.sectionInfo.name );
+        if( !rootName.empty() )
+            name = rootName + '/' + name;
+
+        if( !sectionNode.assertions.empty() ||
+            !sectionNode.stdOut.empty() ||
+            !sectionNode.stdErr.empty() ) {
+            XmlWriter::ScopedElement e = xml.scopedElement( "testcase" );
+            if( className.empty() ) {
+                xml.writeAttribute( "classname", name );
+                xml.writeAttribute( "name", "root" );
+            }
+            else {
+                xml.writeAttribute( "classname", className );
+                xml.writeAttribute( "name", name );
+            }
+            xml.writeAttribute( "time", ::Catch::Detail::stringify( sectionNode.stats.durationInSeconds ) );
+
+            writeAssertions( sectionNode );
+
+            if( !sectionNode.stdOut.empty() )
+                xml.scopedElement( "system-out" ).writeText( trim( sectionNode.stdOut ), false );
+            if( !sectionNode.stdErr.empty() )
+                xml.scopedElement( "system-err" ).writeText( trim( sectionNode.stdErr ), false );
+        }
+        for( auto const& childNode : sectionNode.childSections )
+            if( className.empty() )
+                writeSection( name, "", *childNode );
+            else
+                writeSection( className, name, *childNode );
+    }
+
+    void JunitReporter::writeAssertions( SectionNode const& sectionNode ) {
+        for( auto const& assertion : sectionNode.assertions )
+            writeAssertion( assertion );
+    }
+
+    void JunitReporter::writeAssertion( AssertionStats const& stats ) {
+        AssertionResult const& result = stats.assertionResult;
+        if( !result.isOk() ) {
+            std::string elementName;
+            switch( result.getResultType() ) {
+                case ResultWas::ThrewException:
+                case ResultWas::FatalErrorCondition:
+                    elementName = "error";
+                    break;
+                case ResultWas::ExplicitFailure:
+                    elementName = "failure";
+                    break;
+                case ResultWas::ExpressionFailed:
+                    elementName = "failure";
+                    break;
+                case ResultWas::DidntThrowException:
+                    elementName = "failure";
+                    break;
+
+                // We should never see these here:
+                case ResultWas::Info:
+                case ResultWas::Warning:
+                case ResultWas::Ok:
+                case ResultWas::Unknown:
+                case ResultWas::FailureBit:
+                case ResultWas::Exception:
+                    elementName = "internalError";
+                    break;
+            }
+
+            XmlWriter::ScopedElement e = xml.scopedElement( elementName );
+
+            xml.writeAttribute( "message", result.getExpandedExpression() );
+            xml.writeAttribute( "type", result.getTestMacroName() );
+
+            ReusableStringStream rss;
+            if( !result.getMessage().empty() )
+                rss << result.getMessage() << '\n';
+            for( auto const& msg : stats.infoMessages )
+                if( msg.type == ResultWas::Info )
+                    rss << msg.message << '\n';
+
+            rss << "at " << result.getSourceInfo();
+            xml.writeText( rss.str(), false );
+        }
+    }
+
+    CATCH_REGISTER_REPORTER( "junit", JunitReporter )
+
+} // end namespace Catch
+// end catch_reporter_junit.cpp
+// start catch_reporter_listening.cpp
+
+#include <cassert>
+
+namespace Catch {
+
+    ListeningReporter::ListeningReporter() {
+        // We will assume that listeners will always want all assertions
+        m_preferences.shouldReportAllAssertions = true;
+    }
+
+    void ListeningReporter::addListener( IStreamingReporterPtr&& listener ) {
+        m_listeners.push_back( std::move( listener ) );
+    }
+
+    void ListeningReporter::addReporter(IStreamingReporterPtr&& reporter) {
+        assert(!m_reporter && "Listening reporter can wrap only 1 real reporter");
+        m_reporter = std::move( reporter );
+        m_preferences.shouldRedirectStdOut = m_reporter->getPreferences().shouldRedirectStdOut;
+    }
+
+    ReporterPreferences ListeningReporter::getPreferences() const {
+        return m_preferences;
+    }
+
+    std::set<Verbosity> ListeningReporter::getSupportedVerbosities() {
+        return std::set<Verbosity>{ };
+    }
+
+    void ListeningReporter::noMatchingTestCases( std::string const& spec ) {
+        for ( auto const& listener : m_listeners ) {
+            listener->noMatchingTestCases( spec );
+        }
+        m_reporter->noMatchingTestCases( spec );
+    }
+
+    void ListeningReporter::benchmarkStarting( BenchmarkInfo const& benchmarkInfo ) {
+        for ( auto const& listener : m_listeners ) {
+            listener->benchmarkStarting( benchmarkInfo );
+        }
+        m_reporter->benchmarkStarting( benchmarkInfo );
+    }
+    void ListeningReporter::benchmarkEnded( BenchmarkStats const& benchmarkStats ) {
+        for ( auto const& listener : m_listeners ) {
+            listener->benchmarkEnded( benchmarkStats );
+        }
+        m_reporter->benchmarkEnded( benchmarkStats );
+    }
+
+    void ListeningReporter::testRunStarting( TestRunInfo const& testRunInfo ) {
+        for ( auto const& listener : m_listeners ) {
+            listener->testRunStarting( testRunInfo );
+        }
+        m_reporter->testRunStarting( testRunInfo );
+    }
+
+    void ListeningReporter::testGroupStarting( GroupInfo const& groupInfo ) {
+        for ( auto const& listener : m_listeners ) {
+            listener->testGroupStarting( groupInfo );
+        }
+        m_reporter->testGroupStarting( groupInfo );
+    }
+
+    void ListeningReporter::testCaseStarting( TestCaseInfo const& testInfo ) {
+        for ( auto const& listener : m_listeners ) {
+            listener->testCaseStarting( testInfo );
+        }
+        m_reporter->testCaseStarting( testInfo );
+    }
+
+    void ListeningReporter::sectionStarting( SectionInfo const& sectionInfo ) {
+        for ( auto const& listener : m_listeners ) {
+            listener->sectionStarting( sectionInfo );
+        }
+        m_reporter->sectionStarting( sectionInfo );
+    }
+
+    void ListeningReporter::assertionStarting( AssertionInfo const& assertionInfo ) {
+        for ( auto const& listener : m_listeners ) {
+            listener->assertionStarting( assertionInfo );
+        }
+        m_reporter->assertionStarting( assertionInfo );
+    }
+
+    // The return value indicates if the messages buffer should be cleared:
+    bool ListeningReporter::assertionEnded( AssertionStats const& assertionStats ) {
+        for( auto const& listener : m_listeners ) {
+            static_cast<void>( listener->assertionEnded( assertionStats ) );
+        }
+        return m_reporter->assertionEnded( assertionStats );
+    }
+
+    void ListeningReporter::sectionEnded( SectionStats const& sectionStats ) {
+        for ( auto const& listener : m_listeners ) {
+            listener->sectionEnded( sectionStats );
+        }
+        m_reporter->sectionEnded( sectionStats );
+    }
+
+    void ListeningReporter::testCaseEnded( TestCaseStats const& testCaseStats ) {
+        for ( auto const& listener : m_listeners ) {
+            listener->testCaseEnded( testCaseStats );
+        }
+        m_reporter->testCaseEnded( testCaseStats );
+    }
+
+    void ListeningReporter::testGroupEnded( TestGroupStats const& testGroupStats ) {
+        for ( auto const& listener : m_listeners ) {
+            listener->testGroupEnded( testGroupStats );
+        }
+        m_reporter->testGroupEnded( testGroupStats );
+    }
+
+    void ListeningReporter::testRunEnded( TestRunStats const& testRunStats ) {
+        for ( auto const& listener : m_listeners ) {
+            listener->testRunEnded( testRunStats );
+        }
+        m_reporter->testRunEnded( testRunStats );
+    }
+
+    void ListeningReporter::skipTest( TestCaseInfo const& testInfo ) {
+        for ( auto const& listener : m_listeners ) {
+            listener->skipTest( testInfo );
+        }
+        m_reporter->skipTest( testInfo );
+    }
+
+    bool ListeningReporter::isMulti() const {
+        return true;
+    }
+
+} // end namespace Catch
+// end catch_reporter_listening.cpp
+// start catch_reporter_xml.cpp
+
+#if defined(_MSC_VER)
+#pragma warning(push)
+#pragma warning(disable:4061) // Not all labels are EXPLICITLY handled in switch
+                              // Note that 4062 (not all labels are handled
+                              // and default is missing) is enabled
+#endif
+
+namespace Catch {
+    XmlReporter::XmlReporter( ReporterConfig const& _config )
+    :   StreamingReporterBase( _config ),
+        m_xml(_config.stream())
+    {
+        m_reporterPrefs.shouldRedirectStdOut = true;
+        m_reporterPrefs.shouldReportAllAssertions = true;
+    }
+
+    XmlReporter::~XmlReporter() = default;
+
+    std::string XmlReporter::getDescription() {
+        return "Reports test results as an XML document";
+    }
+
+    std::string XmlReporter::getStylesheetRef() const {
+        return std::string();
+    }
+
+    void XmlReporter::writeSourceInfo( SourceLineInfo const& sourceInfo ) {
+        m_xml
+            .writeAttribute( "filename", sourceInfo.file )
+            .writeAttribute( "line", sourceInfo.line );
+    }
+
+    void XmlReporter::noMatchingTestCases( std::string const& s ) {
+        StreamingReporterBase::noMatchingTestCases( s );
+    }
+
+    void XmlReporter::testRunStarting( TestRunInfo const& testInfo ) {
+        StreamingReporterBase::testRunStarting( testInfo );
+        std::string stylesheetRef = getStylesheetRef();
+        if( !stylesheetRef.empty() )
+            m_xml.writeStylesheetRef( stylesheetRef );
+        m_xml.startElement( "Catch" );
+        if( !m_config->name().empty() )
+            m_xml.writeAttribute( "name", m_config->name() );
+        if( m_config->rngSeed() != 0 )
+            m_xml.scopedElement( "Randomness" )
+                .writeAttribute( "seed", m_config->rngSeed() );
+    }
+
+    void XmlReporter::testGroupStarting( GroupInfo const& groupInfo ) {
+        StreamingReporterBase::testGroupStarting( groupInfo );
+        m_xml.startElement( "Group" )
+            .writeAttribute( "name", groupInfo.name );
+    }
+
+    void XmlReporter::testCaseStarting( TestCaseInfo const& testInfo ) {
+        StreamingReporterBase::testCaseStarting(testInfo);
+        m_xml.startElement( "TestCase" )
+            .writeAttribute( "name", trim( testInfo.name ) )
+            .writeAttribute( "description", testInfo.description )
+            .writeAttribute( "tags", testInfo.tagsAsString() );
+
+        writeSourceInfo( testInfo.lineInfo );
+
+        if ( m_config->showDurations() == ShowDurations::Always )
+            m_testCaseTimer.start();
+        m_xml.ensureTagClosed();
+    }
+
+    void XmlReporter::sectionStarting( SectionInfo const& sectionInfo ) {
+        StreamingReporterBase::sectionStarting( sectionInfo );
+        if( m_sectionDepth++ > 0 ) {
+            m_xml.startElement( "Section" )
+                .writeAttribute( "name", trim( sectionInfo.name ) );
+            writeSourceInfo( sectionInfo.lineInfo );
+            m_xml.ensureTagClosed();
+        }
+    }
+
+    void XmlReporter::assertionStarting( AssertionInfo const& ) { }
+
+    bool XmlReporter::assertionEnded( AssertionStats const& assertionStats ) {
+
+        AssertionResult const& result = assertionStats.assertionResult;
+
+        bool includeResults = m_config->includeSuccessfulResults() || !result.isOk();
+
+        if( includeResults || result.getResultType() == ResultWas::Warning ) {
+            // Print any info messages in <Info> tags.
+            for( auto const& msg : assertionStats.infoMessages ) {
+                if( msg.type == ResultWas::Info && includeResults ) {
+                    m_xml.scopedElement( "Info" )
+                            .writeText( msg.message );
+                } else if ( msg.type == ResultWas::Warning ) {
+                    m_xml.scopedElement( "Warning" )
+                            .writeText( msg.message );
+                }
+            }
+        }
+
+        // Drop out if result was successful but we're not printing them.
+        if( !includeResults && result.getResultType() != ResultWas::Warning )
+            return true;
+
+        // Print the expression if there is one.
+        if( result.hasExpression() ) {
+            m_xml.startElement( "Expression" )
+                .writeAttribute( "success", result.succeeded() )
+                .writeAttribute( "type", result.getTestMacroName() );
+
+            writeSourceInfo( result.getSourceInfo() );
+
+            m_xml.scopedElement( "Original" )
+                .writeText( result.getExpression() );
+            m_xml.scopedElement( "Expanded" )
+                .writeText( result.getExpandedExpression() );
+        }
+
+        // And... Print a result applicable to each result type.
+        switch( result.getResultType() ) {
+            case ResultWas::ThrewException:
+                m_xml.startElement( "Exception" );
+                writeSourceInfo( result.getSourceInfo() );
+                m_xml.writeText( result.getMessage() );
+                m_xml.endElement();
+                break;
+            case ResultWas::FatalErrorCondition:
+                m_xml.startElement( "FatalErrorCondition" );
+                writeSourceInfo( result.getSourceInfo() );
+                m_xml.writeText( result.getMessage() );
+                m_xml.endElement();
+                break;
+            case ResultWas::Info:
+                m_xml.scopedElement( "Info" )
+                    .writeText( result.getMessage() );
+                break;
+            case ResultWas::Warning:
+                // Warning will already have been written
+                break;
+            case ResultWas::ExplicitFailure:
+                m_xml.startElement( "Failure" );
+                writeSourceInfo( result.getSourceInfo() );
+                m_xml.writeText( result.getMessage() );
+                m_xml.endElement();
+                break;
+            default:
+                break;
+        }
+
+        if( result.hasExpression() )
+            m_xml.endElement();
+
+        return true;
+    }
+
+    void XmlReporter::sectionEnded( SectionStats const& sectionStats ) {
+        StreamingReporterBase::sectionEnded( sectionStats );
+        if( --m_sectionDepth > 0 ) {
+            XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResults" );
+            e.writeAttribute( "successes", sectionStats.assertions.passed );
+            e.writeAttribute( "failures", sectionStats.assertions.failed );
+            e.writeAttribute( "expectedFailures", sectionStats.assertions.failedButOk );
+
+            if ( m_config->showDurations() == ShowDurations::Always )
+                e.writeAttribute( "durationInSeconds", sectionStats.durationInSeconds );
+
+            m_xml.endElement();
+        }
+    }
+
+    void XmlReporter::testCaseEnded( TestCaseStats const& testCaseStats ) {
+        StreamingReporterBase::testCaseEnded( testCaseStats );
+        XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResult" );
+        e.writeAttribute( "success", testCaseStats.totals.assertions.allOk() );
+
+        if ( m_config->showDurations() == ShowDurations::Always )
+            e.writeAttribute( "durationInSeconds", m_testCaseTimer.getElapsedSeconds() );
+
+        if( !testCaseStats.stdOut.empty() )
+            m_xml.scopedElement( "StdOut" ).writeText( trim( testCaseStats.stdOut ), false );
+        if( !testCaseStats.stdErr.empty() )
+            m_xml.scopedElement( "StdErr" ).writeText( trim( testCaseStats.stdErr ), false );
+
+        m_xml.endElement();
+    }
+
+    void XmlReporter::testGroupEnded( TestGroupStats const& testGroupStats ) {
+        StreamingReporterBase::testGroupEnded( testGroupStats );
+        // TODO: Check testGroupStats.aborting and act accordingly.
+        m_xml.scopedElement( "OverallResults" )
+            .writeAttribute( "successes", testGroupStats.totals.assertions.passed )
+            .writeAttribute( "failures", testGroupStats.totals.assertions.failed )
+            .writeAttribute( "expectedFailures", testGroupStats.totals.assertions.failedButOk );
+        m_xml.endElement();
+    }
+
+    void XmlReporter::testRunEnded( TestRunStats const& testRunStats ) {
+        StreamingReporterBase::testRunEnded( testRunStats );
+        m_xml.scopedElement( "OverallResults" )
+            .writeAttribute( "successes", testRunStats.totals.assertions.passed )
+            .writeAttribute( "failures", testRunStats.totals.assertions.failed )
+            .writeAttribute( "expectedFailures", testRunStats.totals.assertions.failedButOk );
+        m_xml.endElement();
+    }
+
+    CATCH_REGISTER_REPORTER( "xml", XmlReporter )
+
+} // end namespace Catch
+
+#if defined(_MSC_VER)
+#pragma warning(pop)
+#endif
+// end catch_reporter_xml.cpp
+
+namespace Catch {
+    LeakDetector leakDetector;
+}
+
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+// end catch_impl.hpp
+#endif
+
+#ifdef CATCH_CONFIG_MAIN
+// start catch_default_main.hpp
+
+#ifndef __OBJC__
+
+#if defined(CATCH_CONFIG_WCHAR) && defined(WIN32) && defined(_UNICODE) && !defined(DO_NOT_USE_WMAIN)
+// Standard C/C++ Win32 Unicode wmain entry point
+extern "C" int wmain (int argc, wchar_t * argv[], wchar_t * []) {
+#else
+// Standard C/C++ main entry point
+int main (int argc, char * argv[]) {
+#endif
+
+    return Catch::Session().run( argc, argv );
+}
+
+#else // __OBJC__
+
+// Objective-C entry point
+int main (int argc, char * const argv[]) {
+#if !CATCH_ARC_ENABLED
+    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
+#endif
+
+    Catch::registerTestMethods();
+    int result = Catch::Session().run( argc, (char**)argv );
+
+#if !CATCH_ARC_ENABLED
+    [pool drain];
+#endif
+
+    return result;
+}
+
+#endif // __OBJC__
+
+// end catch_default_main.hpp
+#endif
+
+#if !defined(CATCH_CONFIG_IMPL_ONLY)
+
+#ifdef CLARA_CONFIG_MAIN_NOT_DEFINED
+#  undef CLARA_CONFIG_MAIN
+#endif
+
+#if !defined(CATCH_CONFIG_DISABLE)
+//////
+// If this config identifier is defined then all CATCH macros are prefixed with CATCH_
+#ifdef CATCH_CONFIG_PREFIX_ALL
+
+#define CATCH_REQUIRE( ... ) INTERNAL_CATCH_TEST( "CATCH_REQUIRE", Catch::ResultDisposition::Normal, __VA_ARGS__ )
+#define CATCH_REQUIRE_FALSE( ... ) INTERNAL_CATCH_TEST( "CATCH_REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, __VA_ARGS__ )
+
+#define CATCH_REQUIRE_THROWS( ... ) INTERNAL_CATCH_THROWS( "CATCH_REQUIRE_THROWS", Catch::ResultDisposition::Normal, "", __VA_ARGS__ )
+#define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CATCH_REQUIRE_THROWS_AS", exceptionType, Catch::ResultDisposition::Normal, expr )
+#define CATCH_REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "CATCH_REQUIRE_THROWS_WITH", Catch::ResultDisposition::Normal, matcher, expr )
+#if !defined(CATCH_CONFIG_DISABLE_MATCHERS)
+#define CATCH_REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( "CATCH_REQUIRE_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::Normal, matcher, expr )
+#endif// CATCH_CONFIG_DISABLE_MATCHERS
+#define CATCH_REQUIRE_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "CATCH_REQUIRE_NOTHROW", Catch::ResultDisposition::Normal, __VA_ARGS__ )
+
+#define CATCH_CHECK( ... ) INTERNAL_CATCH_TEST( "CATCH_CHECK", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )
+#define CATCH_CHECK_FALSE( ... ) INTERNAL_CATCH_TEST( "CATCH_CHECK_FALSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, __VA_ARGS__ )
+#define CATCH_CHECKED_IF( ... ) INTERNAL_CATCH_IF( "CATCH_CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )
+#define CATCH_CHECKED_ELSE( ... ) INTERNAL_CATCH_ELSE( "CATCH_CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )
+#define CATCH_CHECK_NOFAIL( ... ) INTERNAL_CATCH_TEST( "CATCH_CHECK_NOFAIL", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ )
+
+#define CATCH_CHECK_THROWS( ... )  INTERNAL_CATCH_THROWS( "CATCH_CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, "", __VA_ARGS__ )
+#define CATCH_CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CATCH_CHECK_THROWS_AS", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr )
+#define CATCH_CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "CATCH_CHECK_THROWS_WITH", Catch::ResultDisposition::ContinueOnFailure, matcher, expr )
+#if !defined(CATCH_CONFIG_DISABLE_MATCHERS)
+#define CATCH_CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( "CATCH_CHECK_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::ContinueOnFailure, matcher, expr )
+#endif // CATCH_CONFIG_DISABLE_MATCHERS
+#define CATCH_CHECK_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "CATCH_CHECK_NOTHROW", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )
+
+#if !defined(CATCH_CONFIG_DISABLE_MATCHERS)
+#define CATCH_CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CATCH_CHECK_THAT", matcher, Catch::ResultDisposition::ContinueOnFailure, arg )
+
+#define CATCH_REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CATCH_REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg )
+#endif // CATCH_CONFIG_DISABLE_MATCHERS
+
+#define CATCH_INFO( msg ) INTERNAL_CATCH_INFO( "CATCH_INFO", msg )
+#define CATCH_WARN( msg ) INTERNAL_CATCH_MSG( "CATCH_WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg )
+#define CATCH_CAPTURE( ... ) INTERNAL_CATCH_CAPTURE( INTERNAL_CATCH_UNIQUE_NAME(capturer), "CATCH_CAPTURE",__VA_ARGS__ )
+
+#define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ )
+#define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ )
+#define CATCH_METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ )
+#define CATCH_REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ )
+#define CATCH_SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ )
+#define CATCH_DYNAMIC_SECTION( ... ) INTERNAL_CATCH_DYNAMIC_SECTION( __VA_ARGS__ )
+#define CATCH_FAIL( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ )
+#define CATCH_FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )
+#define CATCH_SUCCEED( ... ) INTERNAL_CATCH_MSG( "CATCH_SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )
+
+#define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE()
+
+#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR
+#define CATCH_TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ )
+#define CATCH_TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ )
+#else
+#define CATCH_TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ ) )
+#define CATCH_TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) )
+#endif
+
+#if !defined(CATCH_CONFIG_RUNTIME_STATIC_REQUIRE)
+#define CATCH_STATIC_REQUIRE( ... )       static_assert(   __VA_ARGS__ ,      #__VA_ARGS__ );     CATCH_SUCCEED( #__VA_ARGS__ )
+#define CATCH_STATIC_REQUIRE_FALSE( ... ) static_assert( !(__VA_ARGS__), "!(" #__VA_ARGS__ ")" ); CATCH_SUCCEED( #__VA_ARGS__ )
+#else
+#define CATCH_STATIC_REQUIRE( ... )       CATCH_REQUIRE( __VA_ARGS__ )
+#define CATCH_STATIC_REQUIRE_FALSE( ... ) CATCH_REQUIRE_FALSE( __VA_ARGS__ )
+#endif
+
+// "BDD-style" convenience wrappers
+#define CATCH_SCENARIO( ... ) CATCH_TEST_CASE( "Scenario: " __VA_ARGS__ )
+#define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ )
+#define CATCH_GIVEN( desc )     INTERNAL_CATCH_DYNAMIC_SECTION( "    Given: " << desc )
+#define CATCH_AND_GIVEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( "And given: " << desc )
+#define CATCH_WHEN( desc )      INTERNAL_CATCH_DYNAMIC_SECTION( "     When: " << desc )
+#define CATCH_AND_WHEN( desc )  INTERNAL_CATCH_DYNAMIC_SECTION( " And when: " << desc )
+#define CATCH_THEN( desc )      INTERNAL_CATCH_DYNAMIC_SECTION( "     Then: " << desc )
+#define CATCH_AND_THEN( desc )  INTERNAL_CATCH_DYNAMIC_SECTION( "      And: " << desc )
+
+// If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required
+#else
+
+#define REQUIRE( ... ) INTERNAL_CATCH_TEST( "REQUIRE", Catch::ResultDisposition::Normal, __VA_ARGS__  )
+#define REQUIRE_FALSE( ... ) INTERNAL_CATCH_TEST( "REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, __VA_ARGS__ )
+
+#define REQUIRE_THROWS( ... ) INTERNAL_CATCH_THROWS( "REQUIRE_THROWS", Catch::ResultDisposition::Normal, __VA_ARGS__ )
+#define REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "REQUIRE_THROWS_AS", exceptionType, Catch::ResultDisposition::Normal, expr )
+#define REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "REQUIRE_THROWS_WITH", Catch::ResultDisposition::Normal, matcher, expr )
+#if !defined(CATCH_CONFIG_DISABLE_MATCHERS)
+#define REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( "REQUIRE_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::Normal, matcher, expr )
+#endif // CATCH_CONFIG_DISABLE_MATCHERS
+#define REQUIRE_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "REQUIRE_NOTHROW", Catch::ResultDisposition::Normal, __VA_ARGS__ )
+
+#define CHECK( ... ) INTERNAL_CATCH_TEST( "CHECK", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )
+#define CHECK_FALSE( ... ) INTERNAL_CATCH_TEST( "CHECK_FALSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, __VA_ARGS__ )
+#define CHECKED_IF( ... ) INTERNAL_CATCH_IF( "CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )
+#define CHECKED_ELSE( ... ) INTERNAL_CATCH_ELSE( "CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )
+#define CHECK_NOFAIL( ... ) INTERNAL_CATCH_TEST( "CHECK_NOFAIL", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ )
+
+#define CHECK_THROWS( ... )  INTERNAL_CATCH_THROWS( "CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )
+#define CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CHECK_THROWS_AS", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr )
+#define CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "CHECK_THROWS_WITH", Catch::ResultDisposition::ContinueOnFailure, matcher, expr )
+#if !defined(CATCH_CONFIG_DISABLE_MATCHERS)
+#define CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( "CHECK_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::ContinueOnFailure, matcher, expr )
+#endif // CATCH_CONFIG_DISABLE_MATCHERS
+#define CHECK_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "CHECK_NOTHROW", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )
+
+#if !defined(CATCH_CONFIG_DISABLE_MATCHERS)
+#define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CHECK_THAT", matcher, Catch::ResultDisposition::ContinueOnFailure, arg )
+
+#define REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg )
+#endif // CATCH_CONFIG_DISABLE_MATCHERS
+
+#define INFO( msg ) INTERNAL_CATCH_INFO( "INFO", msg )
+#define WARN( msg ) INTERNAL_CATCH_MSG( "WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg )
+#define CAPTURE( ... ) INTERNAL_CATCH_CAPTURE( INTERNAL_CATCH_UNIQUE_NAME(capturer), "CAPTURE",__VA_ARGS__ )
+
+#define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ )
+#define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ )
+#define METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ )
+#define REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ )
+#define SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ )
+#define DYNAMIC_SECTION( ... ) INTERNAL_CATCH_DYNAMIC_SECTION( __VA_ARGS__ )
+#define FAIL( ... ) INTERNAL_CATCH_MSG( "FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ )
+#define FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )
+#define SUCCEED( ... ) INTERNAL_CATCH_MSG( "SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )
+#define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE()
+
+#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR
+#define TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ )
+#define TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ )
+#else
+#define TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ ) )
+#define TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) )
+#endif
+
+#if !defined(CATCH_CONFIG_RUNTIME_STATIC_REQUIRE)
+#define STATIC_REQUIRE( ... )       static_assert(   __VA_ARGS__,  #__VA_ARGS__ ); SUCCEED( #__VA_ARGS__ )
+#define STATIC_REQUIRE_FALSE( ... ) static_assert( !(__VA_ARGS__), "!(" #__VA_ARGS__ ")" ); SUCCEED( "!(" #__VA_ARGS__ ")" )
+#else
+#define STATIC_REQUIRE( ... )       REQUIRE( __VA_ARGS__ )
+#define STATIC_REQUIRE_FALSE( ... ) REQUIRE_FALSE( __VA_ARGS__ )
+#endif
+
+#endif
+
+#define CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature )
+
+// "BDD-style" convenience wrappers
+#define SCENARIO( ... ) TEST_CASE( "Scenario: " __VA_ARGS__ )
+#define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ )
+
+#define GIVEN( desc )     INTERNAL_CATCH_DYNAMIC_SECTION( "    Given: " << desc )
+#define AND_GIVEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( "And given: " << desc )
+#define WHEN( desc )      INTERNAL_CATCH_DYNAMIC_SECTION( "     When: " << desc )
+#define AND_WHEN( desc )  INTERNAL_CATCH_DYNAMIC_SECTION( " And when: " << desc )
+#define THEN( desc )      INTERNAL_CATCH_DYNAMIC_SECTION( "     Then: " << desc )
+#define AND_THEN( desc )  INTERNAL_CATCH_DYNAMIC_SECTION( "      And: " << desc )
+
+using Catch::Detail::Approx;
+
+#else // CATCH_CONFIG_DISABLE
+
+//////
+// If this config identifier is defined then all CATCH macros are prefixed with CATCH_
+#ifdef CATCH_CONFIG_PREFIX_ALL
+
+#define CATCH_REQUIRE( ... )        (void)(0)
+#define CATCH_REQUIRE_FALSE( ... )  (void)(0)
+
+#define CATCH_REQUIRE_THROWS( ... ) (void)(0)
+#define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) (void)(0)
+#define CATCH_REQUIRE_THROWS_WITH( expr, matcher )     (void)(0)
+#if !defined(CATCH_CONFIG_DISABLE_MATCHERS)
+#define CATCH_REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0)
+#endif// CATCH_CONFIG_DISABLE_MATCHERS
+#define CATCH_REQUIRE_NOTHROW( ... ) (void)(0)
+
+#define CATCH_CHECK( ... )         (void)(0)
+#define CATCH_CHECK_FALSE( ... )   (void)(0)
+#define CATCH_CHECKED_IF( ... )    if (__VA_ARGS__)
+#define CATCH_CHECKED_ELSE( ... )  if (!(__VA_ARGS__))
+#define CATCH_CHECK_NOFAIL( ... )  (void)(0)
+
+#define CATCH_CHECK_THROWS( ... )  (void)(0)
+#define CATCH_CHECK_THROWS_AS( expr, exceptionType ) (void)(0)
+#define CATCH_CHECK_THROWS_WITH( expr, matcher )     (void)(0)
+#if !defined(CATCH_CONFIG_DISABLE_MATCHERS)
+#define CATCH_CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0)
+#endif // CATCH_CONFIG_DISABLE_MATCHERS
+#define CATCH_CHECK_NOTHROW( ... ) (void)(0)
+
+#if !defined(CATCH_CONFIG_DISABLE_MATCHERS)
+#define CATCH_CHECK_THAT( arg, matcher )   (void)(0)
+
+#define CATCH_REQUIRE_THAT( arg, matcher ) (void)(0)
+#endif // CATCH_CONFIG_DISABLE_MATCHERS
+
+#define CATCH_INFO( msg )    (void)(0)
+#define CATCH_WARN( msg )    (void)(0)
+#define CATCH_CAPTURE( msg ) (void)(0)
+
+#define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ))
+#define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ))
+#define CATCH_METHOD_AS_TEST_CASE( method, ... )
+#define CATCH_REGISTER_TEST_CASE( Function, ... ) (void)(0)
+#define CATCH_SECTION( ... )
+#define CATCH_DYNAMIC_SECTION( ... )
+#define CATCH_FAIL( ... ) (void)(0)
+#define CATCH_FAIL_CHECK( ... ) (void)(0)
+#define CATCH_SUCCEED( ... ) (void)(0)
+
+#define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ))
+
+#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR
+#define CATCH_TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) )
+#define CATCH_TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), className )
+#else
+#define CATCH_TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) ) )
+#define CATCH_TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), className ) )
+#endif
+
+// "BDD-style" convenience wrappers
+#define CATCH_SCENARIO( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ))
+#define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), className )
+#define CATCH_GIVEN( desc )
+#define CATCH_AND_GIVEN( desc )
+#define CATCH_WHEN( desc )
+#define CATCH_AND_WHEN( desc )
+#define CATCH_THEN( desc )
+#define CATCH_AND_THEN( desc )
+
+#define CATCH_STATIC_REQUIRE( ... )       (void)(0)
+#define CATCH_STATIC_REQUIRE_FALSE( ... ) (void)(0)
+
+// If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required
+#else
+
+#define REQUIRE( ... )       (void)(0)
+#define REQUIRE_FALSE( ... ) (void)(0)
+
+#define REQUIRE_THROWS( ... ) (void)(0)
+#define REQUIRE_THROWS_AS( expr, exceptionType ) (void)(0)
+#define REQUIRE_THROWS_WITH( expr, matcher ) (void)(0)
+#if !defined(CATCH_CONFIG_DISABLE_MATCHERS)
+#define REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0)
+#endif // CATCH_CONFIG_DISABLE_MATCHERS
+#define REQUIRE_NOTHROW( ... ) (void)(0)
+
+#define CHECK( ... ) (void)(0)
+#define CHECK_FALSE( ... ) (void)(0)
+#define CHECKED_IF( ... ) if (__VA_ARGS__)
+#define CHECKED_ELSE( ... ) if (!(__VA_ARGS__))
+#define CHECK_NOFAIL( ... ) (void)(0)
+
+#define CHECK_THROWS( ... )  (void)(0)
+#define CHECK_THROWS_AS( expr, exceptionType ) (void)(0)
+#define CHECK_THROWS_WITH( expr, matcher ) (void)(0)
+#if !defined(CATCH_CONFIG_DISABLE_MATCHERS)
+#define CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0)
+#endif // CATCH_CONFIG_DISABLE_MATCHERS
+#define CHECK_NOTHROW( ... ) (void)(0)
+
+#if !defined(CATCH_CONFIG_DISABLE_MATCHERS)
+#define CHECK_THAT( arg, matcher ) (void)(0)
+
+#define REQUIRE_THAT( arg, matcher ) (void)(0)
+#endif // CATCH_CONFIG_DISABLE_MATCHERS
+
+#define INFO( msg ) (void)(0)
+#define WARN( msg ) (void)(0)
+#define CAPTURE( msg ) (void)(0)
+
+#define TEST_CASE( ... )  INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ))
+#define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ))
+#define METHOD_AS_TEST_CASE( method, ... )
+#define REGISTER_TEST_CASE( Function, ... ) (void)(0)
+#define SECTION( ... )
+#define DYNAMIC_SECTION( ... )
+#define FAIL( ... ) (void)(0)
+#define FAIL_CHECK( ... ) (void)(0)
+#define SUCCEED( ... ) (void)(0)
+#define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ))
+
+#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR
+#define TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) )
+#define TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), className )
+#else
+#define TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) ) )
+#define TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), className ) )
+#endif
+
+#define STATIC_REQUIRE( ... )       (void)(0)
+#define STATIC_REQUIRE_FALSE( ... ) (void)(0)
+
+#endif
+
+#define CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION_NO_REG( INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ), signature )
+
+// "BDD-style" convenience wrappers
+#define SCENARIO( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ) )
+#define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), className )
+
+#define GIVEN( desc )
+#define AND_GIVEN( desc )
+#define WHEN( desc )
+#define AND_WHEN( desc )
+#define THEN( desc )
+#define AND_THEN( desc )
+
+using Catch::Detail::Approx;
+
+#endif
+
+#endif // ! CATCH_CONFIG_IMPL_ONLY
+
+// start catch_reenable_warnings.h
+
+
+#ifdef __clang__
+#    ifdef __ICC // icpc defines the __clang__ macro
+#        pragma warning(pop)
+#    else
+#        pragma clang diagnostic pop
+#    endif
+#elif defined __GNUC__
+#    pragma GCC diagnostic pop
+#endif
+
+// end catch_reenable_warnings.h
+// end catch.hpp
+#endif // TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED
+
diff --git a/components/codecs/test/nvpipe_codec_unit.cpp b/components/codecs/test/nvpipe_codec_unit.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..86f773df64f8fcdd55d493305ffa2505d356a22c
--- /dev/null
+++ b/components/codecs/test/nvpipe_codec_unit.cpp
@@ -0,0 +1,121 @@
+#include "catch.hpp"
+#include <ftl/codecs/nvpipe_encoder.hpp>
+#include <ftl/codecs/nvpipe_decoder.hpp>
+#include <ftl/threads.hpp>
+
+using ftl::codecs::CodecPreset;
+using ftl::codecs::preset_t;
+using ftl::codecs::definition_t;
+using ftl::codecs::codec_t;
+
+ctpl::thread_pool ftl::pool(4);
+
+namespace ftl {
+	bool running = true;
+
+	namespace codecs {
+	namespace internal {
+	
+	void init_encoders() {}
+
+	}
+	}
+}
+
+TEST_CASE( "NvPipeEncoder::encode() - A colour test image at preset 0" ) {
+	ftl::codecs::NvPipeEncoder encoder(definition_t::HD1080, definition_t::SD480);
+	cv::Mat m(cv::Size(1920,1080), CV_8UC3, cv::Scalar(0,0,0));
+
+	int block_total = 0;
+	std::atomic<int> block_count = 0;
+
+	const CodecPreset &preset = ftl::codecs::getPreset(ftl::codecs::kPreset0);
+
+	bool r = encoder.encode(m, ftl::codecs::kPreset0, [&block_total, &block_count, preset, m](const ftl::codecs::Packet &pkt) {
+		REQUIRE( pkt.codec == codec_t::HEVC );
+		REQUIRE( pkt.data.size() > 0 );
+		REQUIRE( pkt.definition == preset.colour_res );
+
+		block_total = pkt.block_total;
+		block_count++;
+	});
+
+	REQUIRE( r );
+	REQUIRE( block_count == block_total );
+}
+
+TEST_CASE( "NvPipeEncoder::encode() - A depth test image at preset 0" ) {
+	ftl::codecs::NvPipeEncoder encoder(definition_t::HD1080, definition_t::SD480);
+	cv::Mat m(cv::Size(1920,1080), CV_32F, cv::Scalar(0.0f));
+
+	int block_total = 0;
+	std::atomic<int> block_count = 0;
+
+	const CodecPreset &preset = ftl::codecs::getPreset(ftl::codecs::kPreset0);
+
+	bool r = encoder.encode(m, ftl::codecs::kPreset0, [&block_total, &block_count, preset](const ftl::codecs::Packet &pkt) {
+		REQUIRE( pkt.codec == codec_t::HEVC );
+		REQUIRE( pkt.data.size() > 0 );
+		REQUIRE( pkt.definition == preset.depth_res );
+
+		block_total = pkt.block_total;
+		block_count++;
+	});
+
+	REQUIRE( r );
+	REQUIRE( block_count == block_total );
+}
+
+TEST_CASE( "NvPipeDecoder::decode() - A colour test image" ) {
+	ftl::codecs::NvPipeEncoder encoder(definition_t::HD1080, definition_t::SD480);
+	ftl::codecs::NvPipeDecoder decoder;
+
+	cv::Mat in;
+	cv::Mat out;
+	bool r = false;
+
+	SECTION("FHD in and out, FHD encoding") {
+		in = cv::Mat(cv::Size(1920,1080), CV_8UC3, cv::Scalar(255,0,0));
+		out = cv::Mat(cv::Size(1920,1080), CV_8UC3, cv::Scalar(0,0,0));
+
+		r = encoder.encode(in, ftl::codecs::kPreset0, [&out,&decoder](const ftl::codecs::Packet &pkt) {
+			REQUIRE( decoder.decode(pkt, out) );
+		});
+	}
+
+	SECTION("Full HD in, 720 out, FHD encoding") {
+		in = cv::Mat(cv::Size(1920,1080), CV_8UC3, cv::Scalar(255,0,0));
+		out = cv::Mat(cv::Size(1280,720), CV_8UC3, cv::Scalar(0,0,0));
+
+		r = encoder.encode(in, ftl::codecs::kPreset0, [&out,&decoder](const ftl::codecs::Packet &pkt) {
+			REQUIRE( decoder.decode(pkt, out) );
+		});
+
+		REQUIRE( (out.rows == 720) );
+	}
+
+	SECTION("HHD in, FHD out, FHD encoding") {
+		in = cv::Mat(cv::Size(1280,720), CV_8UC3, cv::Scalar(255,0,0));
+		out = cv::Mat(cv::Size(1920,1080), CV_8UC3, cv::Scalar(0,0,0));
+
+		r = encoder.encode(in, ftl::codecs::kPreset0, [&out,&decoder](const ftl::codecs::Packet &pkt) {
+			REQUIRE( decoder.decode(pkt, out) );
+		});
+
+		REQUIRE( (out.rows == 1080) );
+	}
+
+	SECTION("FHD in, HHD out, SD encoding") {
+		in = cv::Mat(cv::Size(1920,1080), CV_8UC3, cv::Scalar(255,0,0));
+		out = cv::Mat(cv::Size(1280,720), CV_8UC3, cv::Scalar(0,0,0));
+
+		r = encoder.encode(in, ftl::codecs::kPreset4, [&out,&decoder](const ftl::codecs::Packet &pkt) {
+			REQUIRE( decoder.decode(pkt, out) );
+		});
+
+		REQUIRE( (out.rows == 720) );
+	}
+
+	REQUIRE( r );
+	REQUIRE( (cv::sum(out) != cv::Scalar(0,0,0)) );
+}
diff --git a/components/codecs/test/opencv_codec_unit.cpp b/components/codecs/test/opencv_codec_unit.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..dd85140dbfcddbd9c48f0c83795a84b0d4914882
--- /dev/null
+++ b/components/codecs/test/opencv_codec_unit.cpp
@@ -0,0 +1,96 @@
+#include "catch.hpp"
+#include <ftl/codecs/opencv_encoder.hpp>
+#include <ftl/codecs/opencv_decoder.hpp>
+#include <ftl/threads.hpp>
+
+using ftl::codecs::CodecPreset;
+using ftl::codecs::preset_t;
+using ftl::codecs::definition_t;
+using ftl::codecs::codec_t;
+
+ctpl::thread_pool ftl::pool(4);
+
+namespace ftl {
+	bool running = true;
+
+	namespace codecs {
+	namespace internal {
+	
+	void init_encoders() {}
+
+	}
+	}
+}
+
+TEST_CASE( "OpenCVEncoder::encode() - A colour test image at preset 0" ) {
+	ftl::codecs::OpenCVEncoder encoder(definition_t::HD1080, definition_t::SD480);
+	cv::Mat m(cv::Size(1024,576), CV_8UC3, cv::Scalar(0,0,0));
+
+	int block_total = 0;
+	std::atomic<int> block_count = 0;
+
+	const CodecPreset &preset = ftl::codecs::getPreset(ftl::codecs::kPreset4);
+
+	std::mutex mtx;
+
+	bool r = encoder.encode(m, ftl::codecs::kPreset4, [&mtx, &block_total, &block_count, preset, m](const ftl::codecs::Packet &pkt) {
+		std::unique_lock<std::mutex> lk(mtx);
+		REQUIRE( pkt.codec == codec_t::JPG );
+		REQUIRE( pkt.data.size() > 0 );
+		REQUIRE( pkt.definition == preset.colour_res );
+
+		block_total = pkt.block_total;
+		block_count++;
+
+		cv::Mat d = cv::imdecode(pkt.data, cv::IMREAD_UNCHANGED);
+		REQUIRE( !d.empty() );
+		REQUIRE( d.cols * d.rows * pkt.block_total == m.cols * m.rows );
+	});
+
+	REQUIRE( r );
+	REQUIRE( block_count == block_total );
+}
+
+TEST_CASE( "OpenCVEncoder::encode() - A depth test image at preset 0" ) {
+	ftl::codecs::OpenCVEncoder encoder(definition_t::HD1080, definition_t::SD480);
+	cv::Mat m(cv::Size(1024,576), CV_32F, cv::Scalar(0.0f));
+
+	int block_total = 0;
+	std::atomic<int> block_count = 0;
+
+	const CodecPreset &preset = ftl::codecs::getPreset(ftl::codecs::kPreset4);
+
+	std::mutex mtx;
+
+	bool r = encoder.encode(m, ftl::codecs::kPreset4, [&mtx, &block_total, &block_count, preset](const ftl::codecs::Packet &pkt) {
+		std::unique_lock<std::mutex> lk(mtx);
+		REQUIRE( pkt.codec == codec_t::PNG );
+		REQUIRE( pkt.data.size() > 0 );
+		REQUIRE( pkt.definition == preset.depth_res );
+
+		block_total = pkt.block_total;
+		block_count++;
+
+		cv::Mat d = cv::imdecode(pkt.data, cv::IMREAD_UNCHANGED);
+		REQUIRE( !d.empty() );
+	});
+
+	REQUIRE( r );
+	REQUIRE( block_count == block_total );
+}
+
+TEST_CASE( "OpenCVDecoder::decode() - A colour test image no resolution change" ) {
+	ftl::codecs::OpenCVEncoder encoder(definition_t::HD1080, definition_t::SD480);
+	ftl::codecs::OpenCVDecoder decoder;
+	cv::Mat in(cv::Size(1024,576), CV_8UC3, cv::Scalar(255,0,0));
+	cv::Mat out(cv::Size(1024,576), CV_8UC3, cv::Scalar(0,0,0));
+
+	std::mutex mtx;
+
+	bool r = encoder.encode(in, ftl::codecs::kPreset4, [&mtx, &out,&decoder](const ftl::codecs::Packet &pkt) {
+		std::unique_lock<std::mutex> lk(mtx);
+		REQUIRE( decoder.decode(pkt, out) );
+	});
+
+	REQUIRE( (cv::sum(out) != cv::Scalar(0,0,0)) );
+}
diff --git a/components/codecs/test/tests.cpp b/components/codecs/test/tests.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..178916eab8b9c7aabb87ff99894b48443ad6ecb6
--- /dev/null
+++ b/components/codecs/test/tests.cpp
@@ -0,0 +1,3 @@
+#define CATCH_CONFIG_MAIN
+#include "catch.hpp"
+
diff --git a/components/common/cpp/CMakeLists.txt b/components/common/cpp/CMakeLists.txt
index 60577c96040dae74e93ccfe6f8ee4ae0c7abc856..de0f7707c504447a16967625c2f01ab76e1218bb 100644
--- a/components/common/cpp/CMakeLists.txt
+++ b/components/common/cpp/CMakeLists.txt
@@ -6,6 +6,8 @@ set(COMMONSRC
 	src/loguru.cpp
 	src/opencv_to_pcl.cpp
 	src/cuda_common.cpp
+	src/ctpl_stl.cpp
+	src/timer.cpp
 )
 
 check_function_exists(uriParseSingleUriA HAVE_URIPARSESINGLE)
@@ -19,7 +21,7 @@ target_include_directories(ftlcommon PUBLIC
 	$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
 	$<INSTALL_INTERFACE:include>
 	PRIVATE src)
-target_link_libraries(ftlcommon Threads::Threads ${OS_LIBS} ${OpenCV_LIBS} ${PCL_LIBRARIES} ${URIPARSER_LIBRARIES})
+target_link_libraries(ftlcommon Threads::Threads ${OS_LIBS} ${OpenCV_LIBS} ${PCL_LIBRARIES} ${URIPARSER_LIBRARIES} ${CUDA_LIBRARIES})
 
 add_subdirectory(test)
 
diff --git a/components/common/cpp/include/ctpl_stl.h b/components/common/cpp/include/ctpl_stl.h
index 82d8029850bad486e3e1acfcb9b3f51886e8f6d2..fac0de42a001711a7033dfc5142ff33a54323525 100644
--- a/components/common/cpp/include/ctpl_stl.h
+++ b/components/common/cpp/include/ctpl_stl.h
@@ -62,6 +62,10 @@ namespace ctpl {
                 std::unique_lock<std::mutex> lock(this->mutex);
                 return this->q.empty();
             }
+            size_t size() {
+                std::unique_lock<std::mutex> lock(this->mutex);
+                return this->q.size();
+            }
         private:
             std::queue<T> q;
             std::mutex mutex;
@@ -87,6 +91,8 @@ namespace ctpl {
         int n_idle() { return this->nWaiting; }
         std::thread & get_thread(int i) { return *this->threads[i]; }
 
+        size_t q_size() { return this->q.size(); }
+
         // change the number of threads in the pool
         // should be called from one thread, otherwise be careful to not interleave, also with this->stop()
         // nThreads must be >= 0
@@ -206,32 +212,7 @@ namespace ctpl {
         thread_pool & operator=(const thread_pool &);// = delete;
         thread_pool & operator=(thread_pool &&);// = delete;
 
-        void set_thread(int i) {
-            std::shared_ptr<std::atomic<bool>> flag(this->flags[i]); // a copy of the shared ptr to the flag
-            auto f = [this, i, flag/* a copy of the shared ptr to the flag */]() {
-                std::atomic<bool> & _flag = *flag;
-                std::function<void(int id)> * _f;
-                bool isPop = this->q.pop(_f);
-                while (true) {
-                    while (isPop) {  // if there is anything in the queue
-                        std::unique_ptr<std::function<void(int id)>> func(_f); // at return, delete the function even if an exception occurred
-                        (*_f)(i);
-                        if (_flag)
-                            return;  // the thread is wanted to stop, return even if the queue is not empty yet
-                        else
-                            isPop = this->q.pop(_f);
-                    }
-                    // the queue is empty here, wait for the next command
-                    std::unique_lock<std::mutex> lock(this->mutex);
-                    ++this->nWaiting;
-                    this->cv.wait(lock, [this, &_f, &isPop, &_flag](){ isPop = this->q.pop(_f); return isPop || this->isDone || _flag; });
-                    --this->nWaiting;
-                    if (!isPop)
-                        return;  // if the queue is empty and this->isDone == true or *flag then return
-                }
-            };
-            this->threads[i].reset(new std::thread(f)); // compiler may not support std::make_unique()
-        }
+        void set_thread(int i);
 
         void init() { this->nWaiting = 0; this->isStop = false; this->isDone = false; }
 
diff --git a/components/common/cpp/include/ftl/config.h.in b/components/common/cpp/include/ftl/config.h.in
index ff965cbb011c5a52d9e7aed4b45c9e73774e847a..540b332035a2ad5f671a51ed80f0de31d60f1ce7 100644
--- a/components/common/cpp/include/ftl/config.h.in
+++ b/components/common/cpp/include/ftl/config.h.in
@@ -16,6 +16,7 @@
 #cmakedefine HAVE_URIPARSESINGLE
 #cmakedefine HAVE_CUDA
 #cmakedefine HAVE_OPENCV
+#cmakedefine HAVE_OPTFLOW
 #cmakedefine HAVE_PCL
 #cmakedefine HAVE_RENDER
 #cmakedefine HAVE_LIBSGM
@@ -23,6 +24,7 @@
 #cmakedefine HAVE_NANOGUI
 #cmakedefine HAVE_LIBARCHIVE
 #cmakedefine HAVE_OPENVR
+#cmakedefine HAVE_NVPIPE
 
 extern const char *FTL_BRANCH;
 extern const char *FTL_VERSION_LONG;
diff --git a/components/common/cpp/include/ftl/configurable.hpp b/components/common/cpp/include/ftl/configurable.hpp
index 5bae67b4a9972d5f69d0947871d5be259df36be3..94a080eaed480fa2e57b113e9c8c3d7bc04388bd 100644
--- a/components/common/cpp/include/ftl/configurable.hpp
+++ b/components/common/cpp/include/ftl/configurable.hpp
@@ -68,7 +68,9 @@ class Configurable {
 	template <typename T>
 	T value(const std::string &name, T def) {
 		auto r = get<T>(name);
-		return (r) ? *r : def;
+		if (r) return *r;
+		(*config_)[name] = def;
+		return def;
 	}
 
 	/**
diff --git a/components/common/cpp/include/ftl/cuda_common.hpp b/components/common/cpp/include/ftl/cuda_common.hpp
index 3f004d0c8ffefaf692b84106dea3810e223d70f3..70a6a4ad6d4dc0def715979f9eaf348e621d103e 100644
--- a/components/common/cpp/include/ftl/cuda_common.hpp
+++ b/components/common/cpp/include/ftl/cuda_common.hpp
@@ -2,12 +2,19 @@
 #define _FTL_CUDA_COMMON_HPP_
 
 #include <ftl/config.h>
+#include <ftl/traits.hpp>
 
 #if defined HAVE_CUDA
 
+#include <ftl/cuda_util.hpp>
 #include <opencv2/core/cuda.hpp>
 #include <opencv2/core/cuda/common.hpp>
 
+#ifndef __CUDACC__
+#include <loguru.hpp>
+#include <exception>
+#endif
+
 /* Grid stride loop macros */
 #define STRIDE_Y(I,N) int I = blockIdx.y * blockDim.y + threadIdx.y; I < N; I += blockDim.y * gridDim.y
 #define STRIDE_X(I,N) int I = blockIdx.x * blockDim.x + threadIdx.x; I < N; I += blockDim.x * gridDim.x
@@ -15,69 +22,151 @@
 namespace ftl {
 namespace cuda {
 
-/*template <typename T>
-class HisteresisTexture {
+bool initialise();
+
+bool hasCompute(int major, int minor);
+
+int deviceCount();
+
+/**
+ * Represent a CUDA texture object. Instances of this class can be used on both
+ * host and device. A texture object base cannot be constructed directly, it
+ * must be constructed via a template TextureObject class.
+ */
+class TextureObjectBase {
 	public:
-	HisteresisTexture();
-	~HisteresisTexture();
+	__host__ __device__ TextureObjectBase()
+			: texobj_(0), pitch_(0), pitch2_(0), width_(0), height_(0),
+			  ptr_(nullptr), needsfree_(false), needsdestroy_(false),
+			  cvType_(-1) {};
+	~TextureObjectBase();
+
+	// Remove ability to copy object directly, instead must use
+	// templated derivative TextureObject.
+	TextureObjectBase(const TextureObjectBase &)=delete;
+	TextureObjectBase &operator=(const TextureObjectBase &)=delete;
+
+	TextureObjectBase(TextureObjectBase &&);
+	TextureObjectBase &operator=(TextureObjectBase &&);
+
+	inline size_t pitch() const { return pitch_; }
+	inline size_t pixelPitch() const { return pitch2_; }
+	inline uchar *devicePtr() const { return ptr_; };
+	__host__ __device__ inline uchar *devicePtr(int v) const { return &ptr_[v*pitch_]; }
+	__host__ __device__ inline int width() const { return width_; }
+	__host__ __device__ inline int height() const { return height_; }
+	__host__ __device__ inline cudaTextureObject_t cudaTexture() const { return texobj_; }
+
+	void upload(const cv::Mat &, cudaStream_t stream=0);
+	void download(cv::Mat &, cudaStream_t stream=0) const;
 	
-	HisteresisTexture<T> &operator=(TextureObject<T> &t);
-};*/
+	__host__ void free();
 
+	inline int cvType() const { return cvType_; }
+	
+	protected:
+	cudaTextureObject_t texobj_;
+	size_t pitch_;
+	size_t pitch2_; 		// in T units
+	int width_;
+	int height_;
+	uchar *ptr_;			// Device memory pointer
+	bool needsfree_;		// We manage memory, so free it
+	bool needsdestroy_;		// The texture object needs to be destroyed
+	int cvType_;				// Used to validate casting
+};
+
+/**
+ * Create and manage CUDA texture objects with a particular pixel data type.
+ * Note: it is not possible to create texture objects for certain types,
+ * specificially for 3 channel types.
+ */
 template <typename T>
-class TextureObject {
+class TextureObject : public TextureObjectBase {
 	public:
-	__host__ __device__ TextureObject()
-			: texobj_(0), pitch_(0), pitch2_(0), width_(0), height_(0), ptr_(nullptr), needsfree_(false) {};
-	TextureObject(const cv::cuda::PtrStepSz<T> &d);
+	typedef T type;
+
+	static_assert((16u % sizeof(T)) == 0, "Channel format must be aligned with 16 bytes");
+
+	__host__ __device__ TextureObject() : TextureObjectBase() {};
+	explicit TextureObject(const cv::cuda::GpuMat &d);
+	explicit TextureObject(const cv::cuda::PtrStepSz<T> &d);
 	TextureObject(T *ptr, int pitch, int width, int height);
 	TextureObject(size_t width, size_t height);
 	TextureObject(const TextureObject<T> &t);
 	__host__ __device__ TextureObject(TextureObject<T> &&);
 	~TextureObject();
 
-	__host__ TextureObject<T> &operator=(const TextureObject<T> &);
+	TextureObject<T> &operator=(const TextureObject<T> &);
 	__host__ __device__ TextureObject<T> &operator=(TextureObject<T> &&);
-	
-	size_t pitch() const { return pitch_; }
-	size_t pixelPitch() const { return pitch2_; }
-	T *devicePtr() const { return ptr_; };
-	__host__ __device__ T *devicePtr(int v) const { return &ptr_[v*pitch2_]; }
-	__host__ __device__ int width() const { return width_; }
-	__host__ __device__ int height() const { return height_; }
-	__host__ __device__ cudaTextureObject_t cudaTexture() const { return texobj_; }
+
+	operator cv::cuda::GpuMat();
+
+	__host__ __device__ T *devicePtr() const { return (T*)(ptr_); };
+	__host__ __device__ T *devicePtr(int v) const { return &(T*)(ptr_)[v*pitch2_]; }
 
 	#ifdef __CUDACC__
 	__device__ inline T tex2D(int u, int v) const { return ::tex2D<T>(texobj_, u, v); }
 	__device__ inline T tex2D(float u, float v) const { return ::tex2D<T>(texobj_, u, v); }
 	#endif
 
-	__host__ __device__ inline const T &operator()(int u, int v) const { return ptr_[u+v*pitch2_]; }
-	__host__ __device__ inline T &operator()(int u, int v) { return ptr_[u+v*pitch2_]; }
+	__host__ __device__ inline const T &operator()(int u, int v) const { return reinterpret_cast<T*>(ptr_)[u+v*pitch2_]; }
+	__host__ __device__ inline T &operator()(int u, int v) { return reinterpret_cast<T*>(ptr_)[u+v*pitch2_]; }
 
-	void upload(const cv::Mat &, cudaStream_t stream=0);
-	void download(cv::Mat &, cudaStream_t stream=0) const;
-	
-	__host__ void free() {
-		if (needsfree_) {
-			if (texobj_ != 0) cudaSafeCall( cudaDestroyTextureObject (texobj_) );
-			if (ptr_) cudaFree(ptr_);
-			ptr_ = nullptr;
-			texobj_ = 0;
-		}
-	}
-	
-	private:
-	cudaTextureObject_t texobj_;
-	size_t pitch_;
-	size_t pitch2_; // in T units
-	int width_;
-	int height_;
-	T *ptr_;
-	bool needsfree_;
-	//bool needsdestroy_;
+	/**
+	 * Cast a base texture object to this type of texture object. If the
+	 * underlying pixel types do not match then a bad_cast exception is thrown.
+	 */
+	static TextureObject<T> &cast(TextureObjectBase &);
 };
 
+#ifndef __CUDACC__
+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";
+		throw std::bad_cast();
+	}
+	return reinterpret_cast<TextureObject<T>&>(b);
+}
+
+/**
+ * Create a 2D array texture from an OpenCV GpuMat object.
+ */
+template <typename T>
+TextureObject<T>::TextureObject(const cv::cuda::GpuMat &d) {
+	// GpuMat must have correct data type
+	CHECK(d.type() == ftl::traits::OpenCVType<T>::value);
+
+	cudaResourceDesc resDesc;
+	memset(&resDesc, 0, sizeof(resDesc));
+	resDesc.resType = cudaResourceTypePitch2D;
+	resDesc.res.pitch2D.devPtr = d.data;
+	resDesc.res.pitch2D.pitchInBytes = d.step;
+	resDesc.res.pitch2D.desc = cudaCreateChannelDesc<T>();
+	resDesc.res.pitch2D.width = d.cols;
+	resDesc.res.pitch2D.height = d.rows;
+
+	cudaTextureDesc texDesc;
+	// cppcheck-suppress memsetClassFloat
+	memset(&texDesc, 0, sizeof(texDesc));
+	texDesc.readMode = cudaReadModeElementType;
+
+	cudaTextureObject_t tex = 0;
+	cudaSafeCall(cudaCreateTextureObject(&tex, &resDesc, &texDesc, NULL));
+	texobj_ = tex;
+	pitch_ = d.step;
+	pitch2_ = pitch_ / sizeof(T);
+	ptr_ = d.data;
+	width_ = d.cols;
+	height_ = d.rows;
+	needsfree_ = false;
+	cvType_ = ftl::traits::OpenCVType<T>::value;
+	//needsdestroy_ = true;
+}
+
+#endif  // __CUDACC__
+
 /**
  * Create a 2D array texture from an OpenCV GpuMat object.
  */
@@ -93,6 +182,7 @@ TextureObject<T>::TextureObject(const cv::cuda::PtrStepSz<T> &d) {
 	resDesc.res.pitch2D.height = d.rows;
 
 	cudaTextureDesc texDesc;
+	// cppcheck-suppress memsetClassFloat
 	memset(&texDesc, 0, sizeof(texDesc));
 	texDesc.readMode = cudaReadModeElementType;
 
@@ -105,6 +195,7 @@ TextureObject<T>::TextureObject(const cv::cuda::PtrStepSz<T> &d) {
 	width_ = d.cols;
 	height_ = d.rows;
 	needsfree_ = false;
+	cvType_ = ftl::traits::OpenCVType<T>::value;
 	//needsdestroy_ = true;
 }
 
@@ -124,6 +215,7 @@ TextureObject<T>::TextureObject(T *ptr, int pitch, int width, int height) {
 	resDesc.res.pitch2D.height = height;
 
 	cudaTextureDesc texDesc;
+	// cppcheck-suppress memsetClassFloat
 	memset(&texDesc, 0, sizeof(texDesc));
 	texDesc.readMode = cudaReadModeElementType;
 
@@ -136,6 +228,7 @@ TextureObject<T>::TextureObject(T *ptr, int pitch, int width, int height) {
 	width_ = width;
 	height_ = height;
 	needsfree_ = false;
+	cvType_ = ftl::traits::OpenCVType<T>::value;
 	//needsdestroy_ = true;
 }
 
@@ -156,6 +249,7 @@ TextureObject<T>::TextureObject(size_t width, size_t height) {
 		resDesc.res.pitch2D.height = height;
 
 		cudaTextureDesc texDesc;
+		// cppcheck-suppress memsetClassFloat
 		memset(&texDesc, 0, sizeof(texDesc));
 		texDesc.readMode = cudaReadModeElementType;
 		cudaCreateTextureObject(&tex, &resDesc, &texDesc, NULL);
@@ -166,6 +260,7 @@ TextureObject<T>::TextureObject(size_t width, size_t height) {
 	height_ = (int)height;
 	needsfree_ = true;
 	pitch2_ = pitch_ / sizeof(T);
+	cvType_ = ftl::traits::OpenCVType<T>::value;
 	//needsdestroy_ = true;
 }
 
@@ -177,6 +272,7 @@ TextureObject<T>::TextureObject(const TextureObject<T> &p) {
 	height_ = p.height_;
 	pitch_ = p.pitch_;
 	pitch2_ = pitch_ / sizeof(T);
+	cvType_ = ftl::traits::OpenCVType<T>::value;
 	needsfree_ = false;
 }
 
@@ -192,6 +288,7 @@ TextureObject<T>::TextureObject(TextureObject<T> &&p) {
 	p.texobj_ = 0;
 	p.needsfree_ = false;
 	p.ptr_ = nullptr;
+	cvType_ = ftl::traits::OpenCVType<T>::value;
 }
 
 template <typename T>
@@ -202,6 +299,7 @@ TextureObject<T> &TextureObject<T>::operator=(const TextureObject<T> &p) {
 	height_ = p.height_;
 	pitch_ = p.pitch_;
 	pitch2_ = pitch_ / sizeof(T);
+	cvType_ = ftl::traits::OpenCVType<T>::value;
 	needsfree_ = false;
 	return *this;
 }
@@ -218,6 +316,7 @@ TextureObject<T> &TextureObject<T>::operator=(TextureObject<T> &&p) {
 	p.texobj_ = 0;
 	p.needsfree_ = false;
 	p.ptr_ = nullptr;
+	cvType_ = ftl::traits::OpenCVType<T>::value;
 	return *this;
 }
 
@@ -228,7 +327,7 @@ TextureObject<T>::~TextureObject() {
 	free();
 }
 
-template <>
+/*template <>
 void TextureObject<uchar4>::upload(const cv::Mat &m, cudaStream_t stream);
 
 template <>
@@ -257,7 +356,7 @@ template <>
 void TextureObject<float4>::download(cv::Mat &m, cudaStream_t stream) const;
 
 template <>
-void TextureObject<uchar>::download(cv::Mat &m, cudaStream_t stream) const;
+void TextureObject<uchar>::download(cv::Mat &m, cudaStream_t stream) const;*/
 
 }
 }
diff --git a/applications/reconstruct/include/ftl/cuda_matrix_util.hpp b/components/common/cpp/include/ftl/cuda_matrix_util.hpp
similarity index 99%
rename from applications/reconstruct/include/ftl/cuda_matrix_util.hpp
rename to components/common/cpp/include/ftl/cuda_matrix_util.hpp
index e8d803e6fdf7b7c62d52d456ddce288bf554b233..4dd1db7fa8a58c84a40f9d93abd8cc0a492b2792 100644
--- a/applications/reconstruct/include/ftl/cuda_matrix_util.hpp
+++ b/components/common/cpp/include/ftl/cuda_matrix_util.hpp
@@ -1606,6 +1606,7 @@ inline __device__ __host__ matNxM<2, 2> matNxM<2, 2>::getInverse() const
 
 // To Matrix from floatNxN
 template<>
+// cppcheck-suppress syntaxError
 template<>
 inline __device__ __host__  matNxM<1, 1>::matNxM(const float& other)
 {
diff --git a/applications/reconstruct/include/ftl/cuda_operators.hpp b/components/common/cpp/include/ftl/cuda_operators.hpp
similarity index 99%
rename from applications/reconstruct/include/ftl/cuda_operators.hpp
rename to components/common/cpp/include/ftl/cuda_operators.hpp
index b7a3b44426bf6fcf490af67f38461ca849ade428..5fc84fbcb158bc599b8bca55e38035757a857648 100644
--- a/applications/reconstruct/include/ftl/cuda_operators.hpp
+++ b/components/common/cpp/include/ftl/cuda_operators.hpp
@@ -19,7 +19,8 @@
 #ifndef _FTL_CUDA_OPERATORS_HPP_
 #define _FTL_CUDA_OPERATORS_HPP_
 
-#include <cuda_runtime.h>
+//#include <cuda_runtime.h>
+#include <ftl/cuda_util.hpp>
 
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -406,6 +407,12 @@ inline __host__ __device__ float length(float3 v)
     return sqrtf(dot(v, v));
 }
 
+// length squared
+inline __host__ __device__ float length2(const float3 &v)
+{
+    return dot(v, v);
+}
+
 // normalize
 inline __host__ __device__ float3 normalize(float3 v)
 {
diff --git a/applications/reconstruct/include/ftl/cuda_util.hpp b/components/common/cpp/include/ftl/cuda_util.hpp
similarity index 86%
rename from applications/reconstruct/include/ftl/cuda_util.hpp
rename to components/common/cpp/include/ftl/cuda_util.hpp
index bf018f07919fe52c71a1104a6bf029ce52f17b8e..e55c1430c1c013780e4b5d9fc0381492da281e49 100644
--- a/applications/reconstruct/include/ftl/cuda_util.hpp
+++ b/components/common/cpp/include/ftl/cuda_util.hpp
@@ -6,7 +6,12 @@
 #undef max
 #undef min
 
+#ifdef CPPCHECK
+#define __align__(A)
+#endif
+
 #include <cuda_runtime.h>
+#include <vector_types.h>
 #include <ftl/cuda_operators.hpp>
 
 // Enable run time assertion checking in kernel code
diff --git a/components/common/cpp/include/ftl/exception.hpp b/components/common/cpp/include/ftl/exception.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..6719158bba5f5f53abfcdeb0fb39a5b5dda0d5f2
--- /dev/null
+++ b/components/common/cpp/include/ftl/exception.hpp
@@ -0,0 +1,19 @@
+#ifndef _FTL_EXCEPTION_HPP_
+#define _FTL_EXCEPTION_HPP_
+
+namespace ftl {
+class exception : public std::exception
+{
+	public:
+	explicit exception(const char *msg) : msg_(msg) {};
+
+	const char * what () const throw () {
+    	return msg_;
+    }
+
+	private:
+	const char *msg_;
+};
+}
+
+#endif  // _FTL_EXCEPTION_HPP_
diff --git a/components/common/cpp/include/ftl/threads.hpp b/components/common/cpp/include/ftl/threads.hpp
index 5de4f5f75b6ff8e7012bd45b5f2aca8a41bc7b63..83086135a4e535d7f2c4f8ce03ab07dadbe871e4 100644
--- a/components/common/cpp/include/ftl/threads.hpp
+++ b/components/common/cpp/include/ftl/threads.hpp
@@ -7,8 +7,8 @@
 
 #define POOL_SIZE 10
 
-#define DEBUG_MUTEX
-#define MUTEX_TIMEOUT 15
+//#define DEBUG_MUTEX
+#define MUTEX_TIMEOUT 5
 
 #if defined DEBUG_MUTEX
 #include <loguru.hpp>
diff --git a/components/common/cpp/include/ftl/timer.hpp b/components/common/cpp/include/ftl/timer.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..97776c2c3ee9bcbb62b8d81909d87a7dc43ecd28
--- /dev/null
+++ b/components/common/cpp/include/ftl/timer.hpp
@@ -0,0 +1,114 @@
+#ifndef _FTL_COMMON_TIMER_HPP_
+#define _FTL_COMMON_TIMER_HPP_
+
+#include <functional>
+
+namespace ftl {
+
+/**
+ * A single global timer mechanism to call functions with either high or low
+ * precision timing accuracy. This is used to provide accurate frame timing for
+ * capture and rendering sync and it is deliberately a namespace and not a class
+ * since the entire system should be operating at a single frame rate. It
+ * controls the entire pipelines behaviour. It uses timestamps that are
+ * multiples of the millisecond interval between frames.
+ */
+namespace timer {
+
+enum timerlevel_t {
+	kTimerHighPrecision = 0,
+	kTimerSwap,
+	kTimerMain,
+	kTimerIdle1,	// 1ms jobs to optionally do whilst idle
+	kTimerIdle10,	// 10ms jobs to optionally do whilst idle
+	kTimerMAXLEVEL
+};
+
+/**
+ * Represents a timer job for control purposes. Use to remove timer jobs in
+ * a destructor, for example.
+ */
+struct TimerHandle {
+	TimerHandle() : id_(-1) {}
+	explicit TimerHandle(int i) : id_(i) {}
+	TimerHandle(const TimerHandle &t) : id_(t.id()) {}
+
+	/**
+	 * Cancel the timer job. If currently executing it will block and wait for
+	 * the job to complete.
+	 */
+	void cancel() const;
+	void pause() const;
+	void unpause() const;
+
+	/**
+	 * Do the timer job every N frames.
+	 */
+	void setMultiplier(unsigned int) const;
+
+	/**
+	 * Give the timer job a name for logging output.
+	 */
+	void setName(const std::string &) const;
+
+	/**
+	 * Allow copy assignment.
+	 */
+	TimerHandle &operator=(const TimerHandle &h) { id_ = h.id(); return *this; }
+
+	inline int id() const { return id_; }
+
+	private:
+	int id_;
+};
+
+int64_t get_time();
+
+/**
+ * Milliseconds between calls.
+ */
+void setInterval(int ms);
+
+int getInterval();
+
+int64_t get_time();
+
+/**
+ * Add the specified number of milliseconds to the clock when generating
+ * timestamps. This is used to synchronise clocks on multiple machines as it
+ * influences the behaviour of the timer.
+ */
+void setClockAdjustment(int64_t ms);
+
+/**
+ * Add a timer callback with a given precision and ordering. The highest
+ * precision callbacks occur within 1ms of required time and should return
+ * almost immediately to prevent delays to other callbacks. Other precisions
+ * occur later and in separate thread jobs for each callback. If a callback
+ * fails to return before the next time step, it is skipped for that timestep.
+ * If all high precision callbacks together take more than 1ms to complete, a
+ * warning is produced.
+ */
+const TimerHandle add(timerlevel_t, const std::function<bool(int64_t ts)> &);
+
+/**
+ * Initiate the timer and optionally block the current process.
+ */
+void start(bool block=false);
+
+/**
+ * Stop the timer after any current callbacks complete. Blocks until stopped.
+ */
+void stop(bool wait=true);
+
+size_t count(timerlevel_t);
+
+/**
+ * Stop and clear all callbacks. Used for testing purposes.
+ */
+void reset();
+
+}
+}
+
+#endif  // _FTL_COMMON_TIMER_HPP_
diff --git a/components/common/cpp/include/ftl/traits.hpp b/components/common/cpp/include/ftl/traits.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..6ac54e8e66f6a12aa13e39e1228f5cf17ca69312
--- /dev/null
+++ b/components/common/cpp/include/ftl/traits.hpp
@@ -0,0 +1,44 @@
+#ifndef _FTL_TRAITS_HPP_
+#define _FTL_TRAITS_HPP_
+
+#include <opencv2/core.hpp>
+#include <ftl/cuda_util.hpp>
+
+namespace ftl {
+namespace traits {
+
+template <typename T>
+struct AlwaysFalse : std::false_type {};
+
+template <typename T> struct OpenCVType {
+	static_assert(AlwaysFalse<T>::value, "Not a valid format type");
+};
+template <> struct OpenCVType<uchar> { static const int value = CV_8UC1; };
+template <> struct OpenCVType<uchar2> { static const int value = CV_8UC2; };
+template <> struct OpenCVType<uchar3> { static const int value = CV_8UC3; };
+template <> struct OpenCVType<uchar4> { static const int value = CV_8UC4; };
+template <> struct OpenCVType<char> { static const int value = CV_8SC1; };
+template <> struct OpenCVType<char2> { static const int value = CV_8SC2; };
+template <> struct OpenCVType<char3> { static const int value = CV_8SC3; };
+template <> struct OpenCVType<char4> { static const int value = CV_8SC4; };
+template <> struct OpenCVType<ushort> { static const int value = CV_16UC1; };
+template <> struct OpenCVType<ushort2> { static const int value = CV_16UC2; };
+template <> struct OpenCVType<ushort3> { static const int value = CV_16UC3; };
+template <> struct OpenCVType<ushort4> { static const int value = CV_16UC4; };
+template <> struct OpenCVType<short> { static const int value = CV_16SC1; };
+template <> struct OpenCVType<short2> { static const int value = CV_16SC2; };
+template <> struct OpenCVType<short3> { static const int value = CV_16SC3; };
+template <> struct OpenCVType<short4> { static const int value = CV_16SC4; };
+template <> struct OpenCVType<int> { static const int value = CV_32SC1; };
+template <> struct OpenCVType<int2> { static const int value = CV_32SC2; };
+template <> struct OpenCVType<int3> { static const int value = CV_32SC3; };
+template <> struct OpenCVType<int4> { static const int value = CV_32SC4; };
+template <> struct OpenCVType<float> { static const int value = CV_32FC1; };
+template <> struct OpenCVType<float2> { static const int value = CV_32FC2; };
+template <> struct OpenCVType<float3> { static const int value = CV_32FC3; };
+template <> struct OpenCVType<float4> { static const int value = CV_32FC4; };
+
+}
+}
+
+#endif  // _FTL_TRAITS_HPP_
diff --git a/components/common/cpp/src/configuration.cpp b/components/common/cpp/src/configuration.cpp
index 991fdc120b46b5f123bf31a89bb9a25df097aafc..8a3849e8ee8799119f3d15700dc277e1dece7654 100644
--- a/components/common/cpp/src/configuration.cpp
+++ b/components/common/cpp/src/configuration.cpp
@@ -19,6 +19,8 @@
 #include <ftl/configurable.hpp>
 #include <ftl/uri.hpp>
 #include <ftl/threads.hpp>
+#include <ftl/timer.hpp>
+#include <ftl/cuda_common.hpp>
 
 #include <fstream>
 #include <string>
@@ -35,7 +37,7 @@ using ftl::is_file;
 using ftl::is_directory;
 using ftl::Configurable;
 
-ctpl::thread_pool ftl::pool(POOL_SIZE);
+ctpl::thread_pool ftl::pool(std::thread::hardware_concurrency()*2);
 
 // Store loaded configuration
 namespace ftl {
@@ -437,9 +439,14 @@ static void process_options(Configurable *root, const map<string, string> &opts)
 	}
 }
 
+static bool sig_int_called = false;
+
 static void signalIntHandler( int signum ) {
    std::cout << "Closing...\n";
 
+   if (sig_int_called) quick_exit(-1);
+   sig_int_called = true;
+
    // cleanup and close up stuff here  
    // terminate program  
 
@@ -451,7 +458,7 @@ Configurable *ftl::config::configure(ftl::config::json_t &cfg) {
 	loguru::g_preamble_uptime = false;
 	loguru::g_preamble_thread = false;
 	int argc = 1;
-	const char *argv[] = {"d",0};
+	const char *argv[]{"d",0};
 	loguru::init(argc, const_cast<char**>(argv), "--verbosity");
 
 	config_index.clear();
@@ -510,6 +517,16 @@ Configurable *ftl::config::configure(int argc, char **argv, const std::string &r
 		}
 	});
 
+	// Some global settings
+	ftl::timer::setInterval(1000 / rootcfg->value("fps",20));
+
+	// Check CUDA
+	ftl::cuda::initialise();
+
+	int pool_size = rootcfg->value("thread_pool_factor", 2.0f)*std::thread::hardware_concurrency();
+	if (pool_size != ftl::pool.size()) ftl::pool.resize(pool_size);
+
+
 	//LOG(INFO) << "CONFIG: " << config["vision_default"];
 	//CHECK_EQ( &config, config_index["ftl://utu.fi"] );
 
diff --git a/components/common/cpp/src/ctpl_stl.cpp b/components/common/cpp/src/ctpl_stl.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..08308a975dfc9fd92b1a0a5d22336f6d68f82a97
--- /dev/null
+++ b/components/common/cpp/src/ctpl_stl.cpp
@@ -0,0 +1,59 @@
+#ifdef WIN32
+#include <Ws2tcpip.h>
+#include <windows.h>
+#endif
+
+#include <ctpl_stl.h>
+
+void ctpl::thread_pool::set_thread(int i) {
+    std::shared_ptr<std::atomic<bool>> flag(this->flags[i]); // a copy of the shared ptr to the flag
+    auto f = [this, i, flag/* a copy of the shared ptr to the flag */]() {
+        std::atomic<bool> & _flag = *flag;
+        std::function<void(int id)> * _f;
+        bool isPop = this->q.pop(_f);
+        while (true) {
+            while (isPop) {  // if there is anything in the queue
+                std::unique_ptr<std::function<void(int id)>> func(_f); // at return, delete the function even if an exception occurred
+                (*_f)(i);
+                if (_flag)
+                    return;  // the thread is wanted to stop, return even if the queue is not empty yet
+                else
+                    isPop = this->q.pop(_f);
+            }
+            // the queue is empty here, wait for the next command
+            std::unique_lock<std::mutex> lock(this->mutex);
+            ++this->nWaiting;
+            this->cv.wait(lock, [this, &_f, &isPop, &_flag](){ isPop = this->q.pop(_f); return isPop || this->isDone || _flag; });
+            --this->nWaiting;
+            if (!isPop)
+                return;  // if the queue is empty and this->isDone == true or *flag then return
+        }
+    };
+    this->threads[i].reset(new std::thread(f)); // compiler may not support std::make_unique()
+
+	// For excess threads, ensure they only operate if needed.
+	/*if (i >= std::thread::hardware_concurrency()-1) {
+		#ifndef WIN32
+		sched_param p;
+		p.sched_priority = sched_get_priority_min(SCHED_FIFO);
+		pthread_setschedparam(threads[i]->native_handle(), SCHED_FIFO, &p);
+		#endif
+	} else {
+		#ifndef WIN32
+		sched_param p;
+		p.sched_priority = sched_get_priority_max(SCHED_FIFO);
+		pthread_setschedparam(threads[i]->native_handle(), SCHED_FIFO, &p);
+		#endif
+	}*/
+
+	/*
+    #ifdef WIN32
+    SetThreadAffinityMask(this->threads[i]->native_handle(), 1 << i);
+    #else
+    cpu_set_t cpus;
+    CPU_ZERO(&cpus);
+    CPU_SET(i, &cpus);
+    pthread_setaffinity_np(this->threads[i]->native_handle(), sizeof(cpus), &cpus);
+    #endif
+	*/
+}
diff --git a/components/common/cpp/src/cuda_common.cpp b/components/common/cpp/src/cuda_common.cpp
index 571d6816b63413163471ef9006ae1d28031511fd..b29c1df08ba14d61a17d8b7afce290b353c25ce6 100644
--- a/components/common/cpp/src/cuda_common.cpp
+++ b/components/common/cpp/src/cuda_common.cpp
@@ -1,8 +1,106 @@
 #include <ftl/cuda_common.hpp>
 
-using ftl::cuda::TextureObject;
+using ftl::cuda::TextureObjectBase;
 
-template <>
+static int dev_count = 0;
+static std::vector<cudaDeviceProp> properties;
+
+bool ftl::cuda::initialise() {
+	// Do an initial CUDA check
+	cudaSafeCall(cudaGetDeviceCount(&dev_count));
+	CHECK_GE(dev_count, 1) << "No CUDA devices found";
+
+	LOG(INFO) << "CUDA Devices (" << dev_count << "):";
+
+	properties.resize(dev_count);
+	for (int i=0; i<dev_count; i++) {
+		cudaSafeCall(cudaGetDeviceProperties(&properties[i], i));
+		LOG(INFO) << " - " << properties[i].name;
+	}
+
+	return true;
+}
+
+bool ftl::cuda::hasCompute(int major, int minor) {
+	int dev = -1;
+	cudaSafeCall(cudaGetDevice(&dev));
+
+	if (dev > 0) {
+		return properties[dev].major > major ||
+			(properties[dev].major == major && properties[dev].minor >= minor);
+	}
+	return false;
+}
+
+int ftl::cuda::deviceCount() {
+	return dev_count;
+}
+
+TextureObjectBase::~TextureObjectBase() {
+	free();
+}
+
+TextureObjectBase::TextureObjectBase(TextureObjectBase &&o) {
+	needsfree_ = o.needsfree_;
+	needsdestroy_ = o.needsdestroy_;
+	ptr_ = o.ptr_;
+	texobj_ = o.texobj_;
+	cvType_ = o.cvType_;
+	width_ = o.width_;
+	height_ = o.height_;
+	pitch_ = o.pitch_;
+	pitch2_ = o.pitch2_;
+
+	o.ptr_ = nullptr;
+	o.needsfree_ = false;
+	o.texobj_ = 0;
+	o.needsdestroy_ = false;
+}
+
+TextureObjectBase &TextureObjectBase::operator=(TextureObjectBase &&o) {
+	free();
+
+	needsfree_ = o.needsfree_;
+	needsdestroy_ = o.needsdestroy_;
+	ptr_ = o.ptr_;
+	texobj_ = o.texobj_;
+	cvType_ = o.cvType_;
+	width_ = o.width_;
+	height_ = o.height_;
+	pitch_ = o.pitch_;
+	pitch2_ = o.pitch2_;
+
+	o.ptr_ = nullptr;
+	o.needsfree_ = false;
+	o.texobj_ = 0;
+	o.needsdestroy_ = false;
+	return *this;
+}
+
+void TextureObjectBase::free() {
+	if (needsfree_) {
+		if (texobj_ != 0) cudaSafeCall( cudaDestroyTextureObject (texobj_) );
+		if (ptr_) cudaFree(ptr_);
+		ptr_ = nullptr;
+		texobj_ = 0;
+		cvType_ = -1;
+	} else if (needsdestroy_) {
+		if (texobj_ != 0) cudaSafeCall( cudaDestroyTextureObject (texobj_) );
+		texobj_ = 0;
+		cvType_ = -1;
+	}
+}
+
+void TextureObjectBase::upload(const cv::Mat &m, cudaStream_t stream) {
+    cudaSafeCall(cudaMemcpy2DAsync(devicePtr(), pitch(), m.data, m.step, m.cols * m.elemSize(), m.rows, cudaMemcpyHostToDevice, stream));
+}
+
+void TextureObjectBase::download(cv::Mat &m, cudaStream_t stream) const {
+	m.create(height(), width(), cvType_);
+	cudaSafeCall(cudaMemcpy2DAsync(m.data, m.step, devicePtr(), pitch(), m.cols * m.elemSize(), m.rows, cudaMemcpyDeviceToHost, stream));
+}
+
+/*template <>
 void TextureObject<uchar4>::upload(const cv::Mat &m, cudaStream_t stream) {
     cudaSafeCall(cudaMemcpy2DAsync(devicePtr(), pitch(), m.data, m.step, m.cols * sizeof(uchar4), m.rows, cudaMemcpyHostToDevice, stream));
 }
@@ -56,4 +154,4 @@ template <>
 void TextureObject<uchar>::download(cv::Mat &m, cudaStream_t stream) const {
 	m.create(height(), width(), CV_8UC1);
 	cudaSafeCall(cudaMemcpy2DAsync(m.data, m.step, devicePtr(), pitch(), m.cols * sizeof(uchar), m.rows, cudaMemcpyDeviceToHost, stream));
-}
+}*/
diff --git a/components/common/cpp/src/timer.cpp b/components/common/cpp/src/timer.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..328006ab82041d9613ab9ef7847d74727e3aa7f2
--- /dev/null
+++ b/components/common/cpp/src/timer.cpp
@@ -0,0 +1,238 @@
+#include <ftl/timer.hpp>
+#include <ftl/threads.hpp>
+#include <loguru.hpp>
+
+#include <vector>
+#include <list>
+#include <chrono>
+
+#include <xmmintrin.h>
+
+using std::vector;
+using std::list;
+using std::function;
+using std::chrono::time_point_cast;
+using std::chrono::milliseconds;
+using std::chrono::high_resolution_clock;
+using std::this_thread::sleep_for;
+
+using namespace ftl::timer;
+
+static int64_t last_frame = 0;
+static int64_t mspf = 50;
+static int64_t clock_adjust = 0;
+static bool active = false;
+static std::atomic<int> active_jobs = 0;
+static MUTEX mtx;
+static int last_id = 0;
+
+struct TimerJob {
+	int id;
+	function<bool(int64_t)> job;
+	volatile bool active;
+	// TODO: (Nick) Implement richer forms of timer
+	//bool paused;
+	//int multiplier;
+	//int countdown;
+	std::string name;
+};
+
+static list<TimerJob> jobs[kTimerMAXLEVEL];
+
+int64_t ftl::timer::get_time() {
+	return time_point_cast<milliseconds>(high_resolution_clock::now()).time_since_epoch().count()+clock_adjust;
+}
+
+static void waitTimePoint() {
+	auto start = high_resolution_clock::now();
+	int64_t now = get_time();
+	int64_t target = now / mspf;
+	int64_t msdelay = mspf - (now % mspf);
+	int64_t sincelast = now - last_frame*mspf;
+
+	if (sincelast > mspf) LOG(WARNING) << "Frame " << "(" << (target-last_frame) << ") dropped by " << sincelast << "ms";
+
+	// Use sleep_for for larger delays
+	
+	//LOG(INFO) << "Required Delay: " << (now / 40) << " = " << msdelay;
+	while (msdelay >= 35 && sincelast != mspf) {
+		sleep_for(milliseconds(10));
+		now = get_time();
+		msdelay = mspf - (now % mspf);
+	}
+
+	// Still lots of time so do some idle jobs
+	if (msdelay >= 10 && sincelast != mspf) {
+		UNIQUE_LOCK(mtx, lk);
+		auto idle_job = jobs[kTimerIdle10].begin();
+		while (idle_job != jobs[kTimerIdle10].end() && msdelay >= 10 && sincelast != mspf) {
+			(*idle_job).active = true;
+			bool doremove = !(*idle_job).job(now);
+
+			if (doremove) {
+				idle_job = jobs[kTimerIdle10].erase(idle_job);
+				LOG(INFO) << "Timer job removed";
+			} else {
+				(*idle_job++).active = false;
+			}
+			now = get_time();
+			msdelay = mspf - (now % mspf);
+		}
+	}
+
+	// Spin loop until exact grab time
+	//LOG(INFO) << "Spin Delay: " << (now / 40) << " = " << (40 - (now%40));
+
+	if (sincelast != mspf) {
+		target = now / mspf;
+		while ((now/mspf) == target) {
+			_mm_pause();  // SSE2 nano pause intrinsic
+			now = get_time();
+		};
+	}
+	last_frame = now/mspf;
+}
+
+void ftl::timer::setInterval(int ms) {
+	mspf = ms;
+}
+
+int ftl::timer::getInterval() {
+	return mspf;
+}
+
+void ftl::timer::setClockAdjustment(int64_t ms) {
+	clock_adjust += ms;
+}
+
+const TimerHandle ftl::timer::add(timerlevel_t l, const std::function<bool(int64_t ts)> &f) {
+	if (l < 0 || l >= kTimerMAXLEVEL) return {};
+
+	UNIQUE_LOCK(mtx, lk);
+	int newid = last_id++;
+	jobs[l].push_back({newid, f, false, "NoName"});
+	return TimerHandle(newid);
+}
+
+static void removeJob(int id) {
+	UNIQUE_LOCK(mtx, lk);
+	if (id < 0) return;
+	for (size_t j=0; j<kTimerMAXLEVEL; ++j) {
+		for (auto i=jobs[j].begin(); i!=jobs[j].end(); i++) {
+			if ((*i).id == id) {
+				while ((*i).active) {
+					sleep_for(milliseconds(10));
+				}
+
+				jobs[j].erase(i);
+				return;
+			}
+		}
+	}
+}
+
+static void trigger_jobs() {
+	UNIQUE_LOCK(mtx, lk);
+	const int64_t ts = last_frame*mspf;
+
+	// First do non-blocking high precision callbacks
+	const int64_t before = get_time();
+	for (auto &j : jobs[kTimerHighPrecision]) {
+		j.job(ts);
+	}
+	const int64_t after = get_time();
+	if (after - before > 0) LOG(WARNING) << "Precision jobs took too long (" << (after-before) << "ms)";
+
+	// Then do also non-blocking swap callbacks
+	for (auto &j : jobs[kTimerSwap]) {
+		j.job(ts);
+	}
+
+	// Now use thread jobs to do more intensive callbacks
+	for (auto &j : jobs[kTimerMain]) {
+		if (j.active) {
+			//LOG(WARNING) << "Timer job too slow ... skipped for " << ts;
+			continue;
+		}
+		j.active = true;
+		active_jobs++;
+		ftl::pool.push([&j,ts](int id) {
+			bool doremove = !j.job(ts);
+			j.active = false;
+			active_jobs--;
+			if (doremove) removeJob(j.id);
+		});
+	}
+}
+
+namespace ftl {
+	extern bool running;
+}
+
+void ftl::timer::start(bool block) {
+	if (active) return;
+	active = true;
+
+	if (block) {
+		active_jobs++;
+		while (ftl::running && active) {
+			waitTimePoint();
+			trigger_jobs();
+		}
+		active_jobs--;
+	} else {
+		ftl::pool.push([](int id) {
+			active_jobs++;
+			while (ftl::running && active) {
+				waitTimePoint();
+				trigger_jobs();
+			}
+			active_jobs--;
+		});
+	}
+}
+
+void ftl::timer::stop(bool wait) {
+	active = false;
+
+	if (wait) {
+		// All callbacks must complete before returning.
+		while (active_jobs > 0) {
+			sleep_for(milliseconds(10));
+		}
+	}
+}
+
+size_t ftl::timer::count(ftl::timer::timerlevel_t l) {
+	if (l < 0 || l >= kTimerMAXLEVEL) return 0;
+	return jobs[l].size();
+}
+
+void ftl::timer::reset() {
+	stop(true);
+	for (int i=0; i<ftl::timer::kTimerMAXLEVEL; i++) {
+		jobs[i].clear();
+	}
+}
+
+// ===== TimerHandle ===========================================================
+
+void ftl::timer::TimerHandle::cancel() const {
+	removeJob(id());
+}
+
+void ftl::timer::TimerHandle::pause() const {
+
+}
+
+void ftl::timer::TimerHandle::unpause() const {
+
+}
+
+void ftl::timer::TimerHandle::setMultiplier(unsigned int N) const {
+
+}
+
+void ftl::timer::TimerHandle::setName(const std::string &name) const {
+
+}
diff --git a/components/common/cpp/src/uri.cpp b/components/common/cpp/src/uri.cpp
index 0db7a4af505f28bd162d81eed763241ed6f5ff9d..90fb33522c1c2d701fac47b0a6d43a5a6afee3a7 100644
--- a/components/common/cpp/src/uri.cpp
+++ b/components/common/cpp/src/uri.cpp
@@ -41,8 +41,9 @@ void URI::_parse(uri_t puri) {
 	// NOTE: Non-standard additions to allow for Unix style relative file names.
 	if (suri[0] == '.') {
 		char cwdbuf[1024];
-		getcwd(cwdbuf, 1024);
-		suri = string("file://") + string(cwdbuf) + suri.substr(1);
+		if (getcwd(cwdbuf, 1024)) {
+			suri = string("file://") + string(cwdbuf) + suri.substr(1);
+		}
 	} else if (suri[0] == '~') {
 #ifdef WIN32
 		suri = string("file://") + string(std::getenv("HOMEDRIVE")) + string(std::getenv("HOMEPATH")) + suri.substr(1);
diff --git a/components/common/cpp/test/CMakeLists.txt b/components/common/cpp/test/CMakeLists.txt
index e2a54abbf11716aa0077e21ba25e9ce58dfa521a..f9c1773b92718fc79eb1e26c1d63c7668f12fa53 100644
--- a/components/common/cpp/test/CMakeLists.txt
+++ b/components/common/cpp/test/CMakeLists.txt
@@ -6,12 +6,14 @@ add_executable(configurable_unit
 	../src/config.cpp
 	../src/configuration.cpp
 	../src/loguru.cpp
+	../src/ctpl_stl.cpp
+	../src/cuda_common.cpp
 	./configurable_unit.cpp
 )
 target_include_directories(configurable_unit PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../include")
 target_link_libraries(configurable_unit
 	${URIPARSER_LIBRARIES}
-	Threads::Threads ${OS_LIBS})
+	Threads::Threads ${OS_LIBS} ${OpenCV_LIBS} ${CUDA_LIBRARIES})
 
 ### URI ########################################################################
 add_executable(uri_unit
@@ -24,9 +26,22 @@ target_link_libraries(uri_unit
 	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
+	./timer_unit.cpp
+)
+target_include_directories(timer_unit PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../include")
+target_link_libraries(timer_unit
+	Threads::Threads ${OS_LIBS})
 
 
 add_test(ConfigurableUnitTest configurable_unit)
 add_test(URIUnitTest uri_unit)
+# add_test(TimerUnitTest timer_unit) CI server can't achieve this
 
 
diff --git a/components/common/cpp/test/configurable_unit.cpp b/components/common/cpp/test/configurable_unit.cpp
index 428208f7465a85421eb1c7864f384a9dcc3f7a98..af44e026a552279da53986dde38079e7695a889b 100644
--- a/components/common/cpp/test/configurable_unit.cpp
+++ b/components/common/cpp/test/configurable_unit.cpp
@@ -6,6 +6,12 @@
 using ftl::Configurable;
 using std::string;
 
+namespace ftl {
+namespace timer {
+void setInterval(int i) {}
+}
+}
+
 SCENARIO( "Configurable::get()" ) {
 	GIVEN( "a non-existent property" ) {
 		// cppcheck-suppress constStatement
diff --git a/components/common/cpp/test/timer_unit.cpp b/components/common/cpp/test/timer_unit.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..6cdea157e9228b920ce2220c0122c8dcad9cf76e
--- /dev/null
+++ b/components/common/cpp/test/timer_unit.cpp
@@ -0,0 +1,249 @@
+#include "catch.hpp"
+#define LOGURU_REPLACE_GLOG 1
+#include <loguru.hpp>
+#include <ftl/timer.hpp>
+#include <ftl/threads.hpp>
+
+ctpl::thread_pool ftl::pool(4);
+
+namespace ftl {
+	bool running = true;
+}
+
+TEST_CASE( "Timer::add() High Precision Accuracy" ) {
+	SECTION( "An instantly returning callback" ) {
+		bool didrun = false;
+
+		ftl::timer::reset();
+
+		auto rc = ftl::timer::add(ftl::timer::kTimerHighPrecision, [&didrun](int64_t ts) {
+			didrun = true;
+			ftl::timer::stop(false);
+			return true;
+		});
+
+		REQUIRE( (rc.id() >= 0) );
+
+		ftl::timer::start(true);
+		REQUIRE( didrun == true );
+	}
+
+	SECTION( "A slow returning callback" ) {
+		bool didrun = false;
+
+		ftl::timer::reset();
+
+		auto rc = ftl::timer::add(ftl::timer::kTimerHighPrecision, [&didrun](int64_t ts) {
+			didrun = true;
+			std::this_thread::sleep_for(std::chrono::milliseconds(5));
+			ftl::timer::stop(false);
+			return true;
+		});
+
+		REQUIRE( (rc.id() >= 0) );
+
+		ftl::timer::start(true);
+		REQUIRE( didrun == true );
+	}
+
+	SECTION( "Multiple callback" ) {
+		bool didrun[3] = {false};
+
+		ftl::timer::reset();
+
+		auto rc = ftl::timer::add(ftl::timer::kTimerHighPrecision, [&didrun](int64_t ts) {
+			didrun[0] = true;
+			ftl::timer::stop(false);
+			return true;
+		});
+
+		REQUIRE( (rc.id() >= 0) );
+
+		ftl::timer::add(ftl::timer::kTimerHighPrecision, [&didrun](int64_t ts) {
+			didrun[1] = true;
+			ftl::timer::stop(false);
+			return true;
+		});
+
+		ftl::timer::add(ftl::timer::kTimerHighPrecision, [&didrun](int64_t ts) {
+			didrun[2] = true;
+			ftl::timer::stop(false);
+			return true;
+		});
+
+		ftl::timer::start(true);
+		REQUIRE( didrun[0] == true );
+		REQUIRE( didrun[1] == true );
+		REQUIRE( didrun[2] == true );
+	}
+}
+
+TEST_CASE( "Timer::add() Idle10 job" ) {
+	SECTION( "Quick idle job" ) {
+		bool didrun = false;
+
+		ftl::timer::reset();
+
+		auto rc = ftl::timer::add(ftl::timer::kTimerIdle10, [&didrun](int64_t ts) {
+			didrun = true;
+			ftl::timer::stop(false);
+			return true;
+		});
+
+		REQUIRE( (rc.id() >= 0) );
+
+		ftl::timer::start(true);
+		REQUIRE( didrun == true );
+	}
+
+	SECTION( "Slow idle job" ) {
+		bool didrun = false;
+
+		ftl::timer::reset();
+
+		auto rc = ftl::timer::add(ftl::timer::kTimerIdle10, [&didrun](int64_t ts) {
+			didrun = true;
+			std::this_thread::sleep_for(std::chrono::milliseconds(60));
+			ftl::timer::stop(false);
+			return true;
+		});
+
+		REQUIRE( (rc.id() >= 0) );
+
+		ftl::timer::start(true);
+		REQUIRE( didrun == true );
+	}
+
+	SECTION( "Return remove idle job" ) {
+		bool didrun = false;
+
+		ftl::timer::reset();
+
+		auto rc = ftl::timer::add(ftl::timer::kTimerIdle10, [&didrun](int64_t ts) {
+			didrun = true;
+			ftl::timer::stop(false);
+			return false;
+		});
+
+		REQUIRE( (rc.id() >= 0) );
+
+		ftl::timer::start(true);
+		REQUIRE( didrun == true );
+		REQUIRE( ftl::timer::count(ftl::timer::kTimerIdle10) == 0 );
+	}
+}
+
+TEST_CASE( "Timer::add() Main job" ) {
+	SECTION( "Quick main job" ) {
+		bool didrun = false;
+
+		ftl::timer::reset();
+
+		auto rc = ftl::timer::add(ftl::timer::kTimerMain, [&didrun](int64_t ts) {
+			didrun = true;
+			ftl::timer::stop(false);
+			return true;
+		});
+
+		REQUIRE( (rc.id() >= 0) );
+
+		ftl::timer::start(true);
+		REQUIRE( didrun == true );
+	}
+
+	SECTION( "Slow main job" ) {
+		bool didrun = false;
+
+		ftl::timer::reset();
+
+		auto rc = ftl::timer::add(ftl::timer::kTimerMain, [&didrun](int64_t ts) {
+			didrun = true;
+			std::this_thread::sleep_for(std::chrono::milliseconds(60));
+			ftl::timer::stop(false);
+			return true;
+		});
+
+		REQUIRE( (rc.id() >= 0) );
+
+		ftl::timer::start(true);
+		REQUIRE( didrun == true );
+	}
+
+	SECTION( "Slow and fast main jobs" ) {
+		int job1 = 0;
+		int job2 = 0;
+
+		ftl::timer::reset();
+
+		auto rc = ftl::timer::add(ftl::timer::kTimerMain, [&job1](int64_t ts) {
+			job1++;
+			std::this_thread::sleep_for(std::chrono::milliseconds(60));
+			ftl::timer::stop(false);
+			return true;
+		});
+
+		REQUIRE( (rc.id() >= 0) );
+
+		ftl::timer::add(ftl::timer::kTimerMain, [&job2](int64_t ts) {
+			job2++;
+			return true;
+		});
+
+		ftl::timer::start(true);
+		REQUIRE( (job1 == 1 && job2 == 2) );
+	}
+
+	SECTION( "Return remove main job" ) {
+		bool didrun = false;
+
+		ftl::timer::reset();
+
+		auto rc = ftl::timer::add(ftl::timer::kTimerMain, [&didrun](int64_t ts) {
+			didrun = true;
+			ftl::timer::stop(false);
+			return false;
+		});
+
+		REQUIRE( (rc.id() >= 0) );
+
+		ftl::timer::start(true);
+		REQUIRE( didrun == true );
+		REQUIRE( ftl::timer::count(ftl::timer::kTimerMain) == 0 );
+	}
+}
+
+TEST_CASE( "TimerHandle::cancel()" ) {
+	SECTION( "Invalid id" ) {
+		bool didjob = false;
+		ftl::timer::reset();
+
+		ftl::timer::add(ftl::timer::kTimerMain, [&didjob](int64_t ts) {
+			didjob = true;
+			ftl::timer::stop(false);
+			return true;
+		});
+
+		// Fake Handle
+		ftl::timer::TimerHandle h(44);
+		h.cancel();
+		ftl::timer::start(true);
+		REQUIRE( didjob );
+	}
+
+	SECTION( "Valid id" ) {
+		bool didjob = false;
+		ftl::timer::reset();
+
+		auto id = ftl::timer::add(ftl::timer::kTimerMain, [&didjob](int64_t ts) {
+			didjob = true;
+			ftl::timer::stop(false);
+			return true;
+		});
+
+		id.cancel();
+		ftl::timer::start(false);
+		std::this_thread::sleep_for(std::chrono::milliseconds(10));
+		ftl::timer::stop();
+		REQUIRE( !didjob );
+	}
+}
diff --git a/components/net/README.md b/components/net/README.md
index 4c3deb62e21b8ab25902c478cb9616411a6097fd..97ceed68e57e0e9441c68e1dbd24e1a18fb300d5 100644
--- a/components/net/README.md
+++ b/components/net/README.md
@@ -1,13 +1,27 @@
 # FTL Network Library
-FTL Net provides stream, RPC and Peer-2-Peer functionality for the FTL system.
-The idea is to allow an efficient mapping to operating system sockets to
-minimise userspace copy operations, whilst still allowing for data packing for
-smaller RPC calls. The P2P component implements different rpc search strategies
-to allow calls to find one, all, many or specific results across the network.
-
-Multiple protocols are supported, and it is intended that NAT traversal will be
-included. However, security, whether encryption or identification, is not
-considered presently.
-
-There are two supported languages: C++ and Javascript. See each respective
-language folder for more details.
+FTL Net provides an easy to use C++17 network library based around a mix of
+streams and RPC. It is now highly optimised to minimise memory copies and
+locking, whilst fully taking advantage of all CPU cores for processing
+messages received over the network. Each message received is dispatched into
+a thread pool. The optimisation works on the basis of a relatively low number
+of socket connections but with a high bandwidth and low latency requirement
+in sending and receiving on those sockets. Further work would be needed to
+be efficient with large or massive numbers of sockets.
+
+The protocol is based on top of [MsgPack](https://github.com/msgpack/msgpack-c)
+which works in both C++ and JavaScript. To work with JavaScript the protocol
+works over TCP and TCP+Websockets. The library is also cross platform,
+supporting Windows and Linux.
+
+It is a template library, allowing simple RPC calls and bindings using the
+latest in C++ features such as optionals, lambdas and futures.
+
+## Universe
+A [Universe class](cpp/include/ftl/net/universe.hpp) represents a network group
+and is the primary means of interaction for the user of the library. It supports
+`bind`, `connect`, `call`, `send`, `disconnect`, `asyncCall` and more.
+
+## Peer
+A [Peer object](cpp/include/ftl/net/peer.hpp) is a fairly internal object that
+wraps a socket connection and deals with all actual sending and receiving over
+the network.
diff --git a/components/net/cpp/include/ftl/net/common.hpp b/components/net/cpp/include/ftl/net/common.hpp
index 78325d09b717d232751c21040be591f259fe7cf8..1ff4c5bbd73f5888b7141a7be988e162d7e279a6 100644
--- a/components/net/cpp/include/ftl/net/common.hpp
+++ b/components/net/cpp/include/ftl/net/common.hpp
@@ -3,6 +3,7 @@
 
 #ifndef WIN32
 #include <unistd.h>
+#include <sys/poll.h>
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <netinet/in.h>
diff --git a/components/net/cpp/include/ftl/net/peer.hpp b/components/net/cpp/include/ftl/net/peer.hpp
index 438fc171425b1f1b2cab12317d05eed2f95d8372..2c3e1fd636edd16772b3bf44ecb0e15fefa3b16b 100644
--- a/components/net/cpp/include/ftl/net/peer.hpp
+++ b/components/net/cpp/include/ftl/net/peer.hpp
@@ -168,6 +168,9 @@ class Peer {
 	 */
 	template <typename... ARGS>
 	int send(const std::string &name, ARGS... args);
+
+	template <typename... ARGS>
+	int try_send(const std::string &name, ARGS... args);
 	
 	/**
 	 * Bind a function to an RPC call name. Note: if an overriding dispatcher
@@ -240,7 +243,7 @@ class Peer {
 	ftl::net::Universe *universe_;	// Origin net universe
 	
 	// Receive buffers
-	bool is_waiting_;
+	volatile bool is_waiting_;
 	msgpack::unpacker recv_buf_;
 	RECURSIVE_MUTEX recv_mtx_;
 	bool ws_read_header_;
@@ -277,6 +280,32 @@ int Peer::send(const std::string &s, ARGS... args) {
 	return rc;
 }
 
+template <typename... ARGS>
+int Peer::try_send(const std::string &s, ARGS... args) {
+#ifdef WIN32
+	WSAPOLLFD fds;
+	fds.fd = sock_;
+	fds.events = POLLOUT;
+	int rc = WSAPoll(&fds, 1, 0);
+#else
+	pollfd fds;
+	fds.fd = sock_;
+	fds.events = POLLOUT;
+	int rc = poll(&fds, 1, 0);
+#endif
+	if (rc == SOCKET_ERROR) return -1;
+	if (rc == 0) return 0;
+
+	UNIQUE_LOCK(send_mtx_, lk);
+	// Leave a blank entry for websocket header
+	if (scheme_ == ftl::URI::SCHEME_WS) send_buf_.append_ref(nullptr,0);
+	auto args_obj = std::make_tuple(args...);
+	auto call_obj = std::make_tuple(0,s,args_obj);
+	msgpack::pack(send_buf_, call_obj);
+	rc = _send();
+	return (rc < 0) ? -1 : 1;
+}
+
 template <typename F>
 void Peer::bind(const std::string &name, F func) {
 	disp_->bind(name, func,
@@ -301,11 +330,11 @@ R Peer::call(const std::string &name, ARGS... args) {
 	}, std::forward<ARGS>(args)...);
 	
 	// While waiting, do some other thread jobs...
-	std::function<void(int)> j;
+	/*std::function<void(int)> j;
 	while (!hasreturned && (bool)(j=ftl::pool.pop())) {
 			LOG(INFO) << "Doing job whilst waiting...";
 			j(-1);
-	}
+	}*/
 
 	{  // Block thread until async callback notifies us
 		std::unique_lock<std::mutex> lk(m);
diff --git a/components/net/cpp/include/ftl/net/universe.hpp b/components/net/cpp/include/ftl/net/universe.hpp
index b45163006d9755c3c3a713a23b1b9ccdf704cb8c..b4419b1f7fc713259b0d9f1a14f00ff12332dd6b 100644
--- a/components/net/cpp/include/ftl/net/universe.hpp
+++ b/components/net/cpp/include/ftl/net/universe.hpp
@@ -141,6 +141,9 @@ class Universe : public ftl::Configurable {
 	template <typename... ARGS>
 	bool send(const UUID &pid, const std::string &name, ARGS... args);
 
+	template <typename... ARGS>
+	int try_send(const UUID &pid, const std::string &name, ARGS... args);
+
 	template <typename R, typename... ARGS>
 	std::optional<R> findOne(const std::string &name, ARGS... args);
 
@@ -185,6 +188,9 @@ class Universe : public ftl::Configurable {
 	ftl::net::callback_t onError(const std::function<void(ftl::net::Peer*, const ftl::net::Error &)>&);
 
 	void removeCallback(ftl::net::callback_t cbid);
+
+	size_t getSendBufferSize() const { return send_size_; }
+	size_t getRecvBufferSize() const { return recv_size_; }
 	
 	private:
 	void _run();
@@ -203,7 +209,7 @@ class Universe : public ftl::Configurable {
 	private:
 	bool active_;
 	ftl::UUID this_peer;
-	SHARED_MUTEX net_mutex_;
+	mutable SHARED_MUTEX net_mutex_;
 	RECURSIVE_MUTEX handler_mutex_;
 	fd_set sfderror_;
 	fd_set sfdread_;
@@ -214,11 +220,18 @@ class Universe : public ftl::Configurable {
 	std::map<ftl::UUID, ftl::net::Peer*> peer_ids_;
 	ftl::UUID id_;
 	ftl::net::Dispatcher disp_;
-	std::thread thread_;
 	std::list<ReconnectInfo> reconnects_;
 	size_t phase_;
 	std::list<ftl::net::Peer*> garbage_;
 
+	size_t send_size_;
+	size_t recv_size_;
+	double periodic_time_;
+	int reconnect_attempts_;
+
+	// NOTE: Must always be last member
+	std::thread thread_;
+
 	struct ConnHandler {
 		callback_t id;
 		std::function<void(ftl::net::Peer*)> h;
@@ -385,6 +398,17 @@ bool Universe::send(const ftl::UUID &pid, const std::string &name, ARGS... args)
 
 }
 
+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();
+		return false;
+	}
+
+	return (p->isConnected()) ? p->try_send(name, args...) : -1;
+}
+
 /*template <typename... ARGS>
 void Universe::publish(const std::string &res, ARGS... args) {
 	ftl::URI uri(res);
diff --git a/components/net/cpp/src/dispatcher.cpp b/components/net/cpp/src/dispatcher.cpp
index 34b01238accbe92e5b32c74171b429506a201552..3231b8ddc4604c7929a04c5c33a36385e1bd94ea 100644
--- a/components/net/cpp/src/dispatcher.cpp
+++ b/components/net/cpp/src/dispatcher.cpp
@@ -65,15 +65,15 @@ void ftl::net::Dispatcher::dispatch_call(Peer &s, const msgpack::object &msg) {
     // assert(type == 0);
     
     if (type == 1) {
-    	DLOG(INFO) << "RPC return for " << id;
+    	//DLOG(INFO) << "RPC return for " << id;
     	s._dispatchResponse(id, args);
     } else if (type == 0) {
-		DLOG(INFO) << "RPC " << name << "() <- " << s.getURI();
+		//DLOG(INFO) << "RPC " << name << "() <- " << s.getURI();
 
 		auto func = _locateHandler(name);
 
 		if (func) {
-			DLOG(INFO) << "Found binding for " << name;
+			//DLOG(INFO) << "Found binding for " << name;
 		    try {
 		        auto result = (*func)(args); //->get();
 		        s._sendResponse(id, result->get());
@@ -149,7 +149,7 @@ 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 - " << found << " != " << expected;
+    	LOG(FATAL) << "RPC argument missmatch for '" << func << "' - " << found << " != " << expected;
         throw -1;
     }
 }
diff --git a/components/net/cpp/src/peer.cpp b/components/net/cpp/src/peer.cpp
index 8adbeae8c42ed7b3bbd3215a561ae45aacffebf3..0335ca67f3a284294b4973c83aea62100d56a5c2 100644
--- a/components/net/cpp/src/peer.cpp
+++ b/components/net/cpp/src/peer.cpp
@@ -18,6 +18,11 @@
 #pragma comment(lib, "Rpcrt4.lib")
 #endif
 
+#ifndef WIN32
+#include <sys/ioctl.h>
+#include <linux/sockios.h>
+#endif
+
 #include <ftl/uri.hpp>
 #include <ftl/net/peer.hpp>
 #include <ftl/net/ws_internal.hpp>
@@ -43,8 +48,6 @@ using ftl::net::Universe;
 using ftl::net::callback_t;
 using std::vector;
 
-#define TCP_BUFFER_SIZE	(1024*1024*10)
-
 /*static std::string hexStr(const std::string &s)
 {
 	const char *data = s.data();
@@ -64,7 +67,7 @@ ftl::UUID ftl::net::this_peer;
 //static ctpl::thread_pool pool(5);
 
 // TODO:(nick) Move to tcp_internal.cpp
-static SOCKET tcpConnect(URI &uri) {
+static SOCKET tcpConnect(URI &uri, int ssize, int rsize) {
 	int rc;
 	//sockaddr_in destAddr;
 
@@ -87,10 +90,11 @@ static SOCKET tcpConnect(URI &uri) {
 	int flags =1; 
     if (setsockopt(csocket, IPPROTO_TCP, TCP_NODELAY, (const char *)&flags, sizeof(flags))) { LOG(ERROR) << "ERROR: setsocketopt(), TCP_NODELAY"; };
 
-	int a = TCP_BUFFER_SIZE;
+	int a = rsize;
 	if (setsockopt(csocket, SOL_SOCKET, SO_RCVBUF, (const char *)&a, sizeof(int)) == -1) {
 		fprintf(stderr, "Error setting socket opts: %s\n", strerror(errno));
 	}
+	a = ssize;
 	if (setsockopt(csocket, SOL_SOCKET, SO_SNDBUF, (const char *)&a, sizeof(int)) == -1) {
 		fprintf(stderr, "Error setting socket opts: %s\n", strerror(errno));
 	}
@@ -178,10 +182,11 @@ Peer::Peer(SOCKET s, Universe *u, Dispatcher *d) : sock_(s), can_reconnect_(fals
 
 	int flags =1; 
     if (setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (const char *)&flags, sizeof(flags))) { LOG(ERROR) << "ERROR: setsocketopt(), TCP_NODELAY"; };
-	int a = TCP_BUFFER_SIZE;
+	int a = u->getRecvBufferSize();
 	if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, (const char *)&a, sizeof(int)) == -1) {
 		fprintf(stderr, "Error setting socket opts: %s\n", strerror(errno));
 	}
+	a = u->getSendBufferSize();
 	if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, (const char *)&a, sizeof(int)) == -1) {
 		fprintf(stderr, "Error setting socket opts: %s\n", strerror(errno));
 	}
@@ -234,12 +239,12 @@ Peer::Peer(const char *pUri, Universe *u, Dispatcher *d) : can_reconnect_(true),
 
 	scheme_ = uri.getProtocol();
 	if (uri.getProtocol() == URI::SCHEME_TCP) {
-		sock_ = tcpConnect(uri);
+		sock_ = tcpConnect(uri, u->getSendBufferSize(), u->getRecvBufferSize());
 		if (sock_ != INVALID_SOCKET) status_ = kConnecting;
 		else status_ = kReconnecting;
 	} else if (uri.getProtocol() == URI::SCHEME_WS) {
 		LOG(INFO) << "Websocket connect " << uri.getPath();
-		sock_ = tcpConnect(uri);
+		sock_ = tcpConnect(uri, u->getSendBufferSize(), u->getRecvBufferSize());
 		if (sock_ != INVALID_SOCKET) {
 			if (!ws_connect(sock_, uri)) {
 				LOG(ERROR) << "Websocket connection failed";
@@ -302,7 +307,7 @@ bool Peer::reconnect() {
 	LOG(INFO) << "Reconnecting to " << uri_ << " ...";
 
 	if (scheme_ == URI::SCHEME_TCP) {
-		sock_ = tcpConnect(uri);
+		sock_ = tcpConnect(uri, universe_->getSendBufferSize(), universe_->getRecvBufferSize());
 		if (sock_ != INVALID_SOCKET) {
 			status_ = kConnecting;
 			is_waiting_ = true;
@@ -311,7 +316,7 @@ bool Peer::reconnect() {
 			return false;
 		}
 	} else if (scheme_ == URI::SCHEME_WS) {
-		sock_ = tcpConnect(uri);
+		sock_ = tcpConnect(uri, universe_->getSendBufferSize(), universe_->getRecvBufferSize());
 		if (sock_ != INVALID_SOCKET) {
 			if (!ws_connect(sock_, uri)) {
 				return false;
@@ -412,46 +417,65 @@ void Peer::error(int e) {
 
 void Peer::data() {
 	{
+		//auto start = std::chrono::high_resolution_clock::now();
+		//int64_t startts = std::chrono::time_point_cast<std::chrono::milliseconds>(start).time_since_epoch().count();
 		UNIQUE_LOCK(recv_mtx_,lk);
 
+		//LOG(INFO) << "Pool size: " << ftl::pool.q_size();
+
 		int rc=0;
-		int c=0;
 
-		//do {
-			recv_buf_.reserve_buffer(kMaxMessage);
+		recv_buf_.reserve_buffer(kMaxMessage);
 
-			if (recv_buf_.buffer_capacity() < (kMaxMessage / 10)) {
-				LOG(WARNING) << "Net buffer at capacity";
-				return;
-			}
+		if (recv_buf_.buffer_capacity() < (kMaxMessage / 10)) {
+			LOG(WARNING) << "Net buffer at capacity";
+			return;
+		}
 
-			int cap = recv_buf_.buffer_capacity();
-			rc = ftl::net::internal::recv(sock_, recv_buf_.buffer(), cap, 0);
-			//if (c > 0 && rc > 0) LOG(INFO) << "RECV: " << rc;
+		int cap = recv_buf_.buffer_capacity();
+		auto buf = recv_buf_.buffer();
+		lk.unlock();
 
-			if (rc >= cap-1) {
-				LOG(WARNING) << "More than buffers worth of data received"; 
-			}
-			if (cap < (kMaxMessage / 10)) LOG(WARNING) << "NO BUFFER";
-
-			if (rc == 0) {
-				close();
-				return;
-			} else if (rc < 0 && c == 0) {
-				socketError();
-				return;
-			}
+		/*#ifndef WIN32
+		int n;
+		unsigned int m = sizeof(n);
+		getsockopt(sock_,SOL_SOCKET,SO_RCVBUF,(void *)&n, &m);
 
-			//if (rc == -1) break;
-			++c;
-			
-			recv_buf_.buffer_consumed(rc);
-		//} while (rc > 0);
-	}
+		int pending;
+		ioctl(sock_, SIOCINQ, &pending);
+		if (pending > 100000) LOG(INFO) << "Buffer usage: " << float(pending) / float(n);
+		#endif*/
+		rc = ftl::net::internal::recv(sock_, buf, cap, 0);
 
-	ftl::pool.push([this](int id) {
-		_data();
-	});
+		if (rc >= cap-1) {
+			LOG(WARNING) << "More than buffers worth of data received"; 
+		}
+		if (cap < (kMaxMessage / 10)) LOG(WARNING) << "NO BUFFER";
+
+		if (rc == 0) {
+			close();
+			return;
+		} else if (rc < 0) {
+			socketError();
+			return;
+		}
+		
+		lk.lock();
+		recv_buf_.buffer_consumed(rc);
+
+		//auto end = std::chrono::high_resolution_clock::now();
+		//int64_t endts = std::chrono::time_point_cast<std::chrono::milliseconds>(end).time_since_epoch().count();
+		//if (endts - startts > 50) LOG(ERROR) << "Excessive delay";
+
+		if (is_waiting_) {
+			is_waiting_ = false;
+			lk.unlock();
+
+			ftl::pool.push([this](int id) {
+				_data();
+			});
+		}
+	}
 }
 
 bool Peer::_data() {
@@ -460,26 +484,35 @@ bool Peer::_data() {
 	UNIQUE_LOCK(recv_mtx_,lk);
 
 	if (scheme_ == ftl::URI::SCHEME_WS && !ws_read_header_) {
+		//LOG(INFO) << "Reading WS Header";
 		wsheader_type ws;
+		ws.header_size = 0;
 		if (ws_parse(recv_buf_, ws) < 0) {
+			//LOG(ERROR) << "Bad WS header " << ws.header_size;
+			is_waiting_ = true;
 			return false;
 		}
 		ws_read_header_ = true;
 	}
 
-	if (!recv_buf_.next(msg)) return false;
-	msgpack::object obj = msg.get();
+	if (!recv_buf_.next(msg)) {
+		is_waiting_ = true;
+		return false;
+	}
+	ws_read_header_ = false;
+	
 	lk.unlock();
 
+	msgpack::object obj = msg.get();
 	ftl::pool.push([this](int id) {
 		_data();
 	});
 
-	if (status_ != kConnected) {
+	if (status_ == kConnecting) {
 		// If not connected, must lock to make sure no other thread performs this step
 		UNIQUE_LOCK(recv_mtx_,lk);
 		// Verify still not connected after lock
-		if (status_ != kConnected) {
+		if (status_ == kConnecting) {
 			// First message must be a handshake
 			try {
 				tuple<uint32_t, std::string, msgpack::object> hs;
@@ -515,7 +548,7 @@ void Peer::_dispatchResponse(uint32_t id, msgpack::object &res) {
 	// TODO: Handle error reporting...
 	UNIQUE_LOCK(cb_mtx_,lk);
 	if (callbacks_.count(id) > 0) {
-		DLOG(1) << "Received return RPC value";
+		//DLOG(1) << "Received return RPC value";
 		
 		// Allow for unlock before callback
 		auto cb = std::move(callbacks_[id]);
diff --git a/components/net/cpp/src/universe.cpp b/components/net/cpp/src/universe.cpp
index 8c50709e2d6253f73849f5560c72eee9a6ff5b83..29cf1254f9a4926190d1753eb4ecc0836dfcbb99 100644
--- a/components/net/cpp/src/universe.cpp
+++ b/components/net/cpp/src/universe.cpp
@@ -1,4 +1,5 @@
 #include <ftl/net/universe.hpp>
+#include <ftl/timer.hpp>
 #include <chrono>
 
 #ifdef WIN32
@@ -22,16 +23,55 @@ using std::optional;
 using ftl::config::json_t;
 using ftl::net::callback_t;
 
+#define TCP_SEND_BUFFER_SIZE	(512*1024)
+#define TCP_RECEIVE_BUFFER_SIZE	(1024*1024*1)
+
 callback_t ftl::net::Universe::cbid__ = 0;
 
-Universe::Universe() : Configurable(), active_(true), this_peer(ftl::net::this_peer), thread_(Universe::__start, this), phase_(0) {
+Universe::Universe() :
+		Configurable(),
+		active_(true),
+		this_peer(ftl::net::this_peer),
+		phase_(0),
+		send_size_(TCP_SEND_BUFFER_SIZE),
+		recv_size_(TCP_RECEIVE_BUFFER_SIZE),
+		periodic_time_(1.0),
+		reconnect_attempts_(50),
+		thread_(Universe::__start, this) {
 	_installBindings();
+
+	LOG(WARNING) << "Deprecated Universe constructor";
 }
 
 Universe::Universe(nlohmann::json &config) :
-		Configurable(config), active_(true), this_peer(ftl::net::this_peer), thread_(Universe::__start, this), phase_(0) {
+		Configurable(config),
+		active_(true),
+		this_peer(ftl::net::this_peer),
+		phase_(0),
+		send_size_(value("tcp_send_buffer",TCP_SEND_BUFFER_SIZE)),
+		recv_size_(value("tcp_recv_buffer",TCP_RECEIVE_BUFFER_SIZE)),
+		periodic_time_(value("periodics", 1.0)),
+		reconnect_attempts_(value("reconnect_attempts",50)),
+		thread_(Universe::__start, this) {
 
 	_installBindings();
+
+	// Add an idle timer job to garbage collect peer objects
+	// Note: Important to be a timer job to ensure no other timer jobs are
+	// using the object.
+	ftl::timer::add(ftl::timer::kTimerIdle10, [this](int64_t ts) {
+		if (garbage_.size() > 0) {
+			UNIQUE_LOCK(net_mutex_,lk);
+			if (ftl::pool.n_idle() == ftl::pool.size()) {
+				if (garbage_.size() > 0) LOG(INFO) << "Garbage collection";
+				while (garbage_.size() > 0) {
+					delete garbage_.front();
+					garbage_.pop_front();
+				}
+			}
+		}
+		return true;
+	});
 }
 
 Universe::~Universe() {
@@ -39,6 +79,11 @@ Universe::~Universe() {
 }
 
 void Universe::start() {
+	/*cpu_set_t cpus;
+    CPU_ZERO(&cpus);
+    CPU_SET(1, &cpus);
+    pthread_setaffinity_np(thread_.native_handle(), sizeof(cpus), &cpus);*/
+
 	auto l = get<json_t>("listen");
 
 	if (l && (*l).is_array()) {
@@ -138,9 +183,7 @@ int Universe::_setDescriptors() {
 				n = s->_socket();
 			}
 
-			//if (s->isWaiting()) {
-				FD_SET(s->_socket(), &sfdread_);
-			//}
+			FD_SET(s->_socket(), &sfdread_);
 			FD_SET(s->_socket(), &sfderror_);
 		}
 	}
@@ -154,30 +197,11 @@ void Universe::_installBindings(Peer *p) {
 }
 
 void Universe::_installBindings() {
-	/*bind("__subscribe__", [this](const UUID &id, const string &uri) -> bool {
-		LOG(INFO) << "Subscription to " << uri << " by " << id.to_string();
-		unique_lock<shared_mutex> lk(net_mutex_);
-		subscribers_[ftl::URI(uri).to_string()].push_back(id);
-		return true;
-	});
-	
-	bind("__owner__", [this](const std::string &res) -> optional<UUID> {
-		if (owned_.count(res) > 0) return this_peer;
-		else return {};
-	});*/
+
 }
 
 // Note: should be called inside a net lock
 void Universe::_cleanupPeers() {
-
-	if (ftl::pool.n_idle() == ftl::pool.size()) {
-		if (garbage_.size() > 0) LOG(INFO) << "Garbage collection";
-		while (garbage_.size() > 0) {
-			delete garbage_.front();
-			garbage_.pop_front();
-		}
-	}
-
 	auto i = peers_.begin();
 	while (i != peers_.end()) {
 		if (!(*i)->isValid()) {
@@ -191,7 +215,7 @@ void Universe::_cleanupPeers() {
 			i = peers_.erase(i);
 
 			if (p->status() == ftl::net::Peer::kReconnecting) {
-				reconnects_.push_back({50, 1.0f, p});
+				reconnects_.push_back({reconnect_attempts_, 1.0f, p});
 			} else {
 				//delete p;
 				garbage_.push_back(p);
@@ -203,6 +227,7 @@ void Universe::_cleanupPeers() {
 }
 
 Peer *Universe::getPeer(const UUID &id) const {
+	SHARED_LOCK(net_mutex_,lk);
 	auto ix = peer_ids_.find(id);
 	if (ix == peer_ids_.end()) return nullptr;
 	else return ix->second;
@@ -255,7 +280,7 @@ void Universe::_run() {
 		// Do periodics
 		auto now = std::chrono::high_resolution_clock::now();
 		std::chrono::duration<double> elapsed = now - start;
-		if (elapsed.count() >= 1.0) {
+		if (elapsed.count() >= periodic_time_) {
 			start = now;
 			_periodic();
 		}
@@ -287,8 +312,8 @@ void Universe::_run() {
 			continue;
 		}
 
-		// CHECK Could this mutex be the problem!?
 		{
+			// TODO:(Nick) Shared lock unless connection is made
 			UNIQUE_LOCK(net_mutex_,lk);
 
 			//If connection request is waiting
@@ -304,7 +329,7 @@ void Universe::_run() {
 						if (csock != INVALID_SOCKET) {
 							auto p = new Peer(csock, this, &disp_);
 							peers_.push_back(p);
-							_installBindings(p);
+							//_installBindings(p);
 						}
 					}
 				}
diff --git a/components/net/cpp/test/peer_unit.cpp b/components/net/cpp/test/peer_unit.cpp
index 09eb4bef9e70171b84e57f1e954e833aa9768566..c4ace71797063eef78bafee6e3784f0f5e4fa124 100644
--- a/components/net/cpp/test/peer_unit.cpp
+++ b/components/net/cpp/test/peer_unit.cpp
@@ -50,6 +50,9 @@ class Universe {
 
 	callback_t onConnect(const std::function<void(Peer*)> &f) { return 0; }
 	callback_t onDisconnect(const std::function<void(Peer*)> &f) { return 0; }
+
+	size_t getSendBufferSize() const { return 10*1024; }
+	size_t getRecvBufferSize() const { return 10*1024; }
 };
 }
 }
diff --git a/components/renderers/cpp/CMakeLists.txt b/components/renderers/cpp/CMakeLists.txt
index 33f910ca0342096bb551b430374776a86de89b2f..b575721587262e2e468d6cb48cf8c44c6771e6fc 100644
--- a/components/renderers/cpp/CMakeLists.txt
+++ b/components/renderers/cpp/CMakeLists.txt
@@ -1,6 +1,7 @@
 add_library(ftlrender
-	src/display.cpp
-	src/rgbd_display.cpp
+	src/splat_render.cpp
+	src/splatter.cu
+	src/points.cu
 )
 
 # These cause errors in CI build and are being removed from PCL in newer versions
@@ -11,6 +12,6 @@ target_include_directories(ftlrender PUBLIC
 	$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
 	$<INSTALL_INTERFACE:include>
 	PRIVATE src)
-target_link_libraries(ftlrender ftlrgbd ftlcommon ftlnet Eigen3::Eigen Threads::Threads glog::glog ${OpenCV_LIBS} ${PCL_LIBRARIES})
+target_link_libraries(ftlrender ftlrgbd ftlcommon Eigen3::Eigen Threads::Threads ${OpenCV_LIBS})
 
 #ADD_SUBDIRECTORY(test)
diff --git a/components/renderers/cpp/include/ftl/cuda/intersections.hpp b/components/renderers/cpp/include/ftl/cuda/intersections.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..9cfdbc2544d9c1bd32c9f5e12a0a161f45c50d54
--- /dev/null
+++ b/components/renderers/cpp/include/ftl/cuda/intersections.hpp
@@ -0,0 +1,88 @@
+#ifndef _FTL_CUDA_INTERSECTIONS_HPP_
+#define _FTL_CUDA_INTERSECTIONS_HPP_
+
+#ifndef PINF
+#define PINF __int_as_float(0x7f800000)
+#endif
+
+namespace ftl {
+namespace cuda {
+
+__device__ inline bool intersectPlane(const float3 &n, const float3 &p0, const float3 &l0, const float3 &l, float &t) { 
+    // assuming vectors are all normalized
+    float denom = dot(n, l); 
+    if (denom > 1e-6) {  
+        t = dot(p0 - l0, n) / denom; 
+        return (t >= 0); 
+    } 
+ 
+    return false; 
+}
+
+__device__ inline bool intersectPlane(const float3 &n, const float3 &p0, const float3 &l, float &t) { 
+    // assuming vectors are all normalized
+    float denom = dot(n, l); 
+    if (denom > 1e-6) {  
+        t = dot(p0, n) / denom; 
+        return (t >= 0); 
+    }
+    return false; 
+}
+
+__device__ inline bool intersectDisk(const float3 &n, const float3 &p0, float radius, const float3 &l0, const float3 &l) { 
+    float t = 0; 
+    if (intersectPlane(n, p0, l0, l, t)) { 
+        float3 p = l0 + l * t; 
+        float3 v = p - p0; 
+        float d2 = dot(v, v); 
+        return (sqrt(d2) <= radius); 
+        // or you can use the following optimisation (and precompute radius^2)
+        // return d2 <= radius2; // where radius2 = radius * radius
+     }
+     return false; 
+}
+
+/**
+ * Get the radius of a ray intersection with a disk.
+ * @param n Normalised normal of disk.
+ * @param p0 Centre of disk in camera space
+ * @param l Normalised ray direction in camera space
+ * @return Radius from centre of disk where intersection occurred.
+ */
+__device__ inline float intersectDistance(const float3 &n, const float3 &p0, const float3 &l0, const float3 &l) { 
+    float t = 0; 
+    if (intersectPlane(n, p0, l0, l, t)) { 
+        const float3 p = l0 + l * t; 
+        const float3 v = p - p0; 
+        const float d2 = dot(v, v); 
+        return sqrt(d2);
+        // or you can use the following optimisation (and precompute radius^2)
+        // return d2 <= radius2; // where radius2 = radius * radius
+     }
+     return PINF; 
+}
+
+/**
+ * Get the radius of a ray intersection with a disk.
+ * @param n Normalised normal of disk.
+ * @param p0 Centre of disk in camera space
+ * @param l Normalised ray direction in camera space
+ * @return Radius from centre of disk where intersection occurred.
+ */
+__device__ inline float intersectDistance(const float3 &n, const float3 &p0, const float3 &l) { 
+    float t = 0; 
+    if (intersectPlane(n, p0, l, t)) { 
+        const float3 p = l * t; 
+        const float3 v = p - p0; 
+        const float d2 = dot(v, v); 
+        return sqrt(d2);
+        // or you can use the following optimisation (and precompute radius^2)
+        // return d2 <= radius2; // where radius2 = radius * radius
+     }
+     return PINF; 
+}
+
+}
+}
+
+#endif  // _FTL_CUDA_INTERSECTIONS_HPP_
diff --git a/components/renderers/cpp/include/ftl/cuda/points.hpp b/components/renderers/cpp/include/ftl/cuda/points.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..deffe32777789e2b58a96aef2106975ad37e0cdd
--- /dev/null
+++ b/components/renderers/cpp/include/ftl/cuda/points.hpp
@@ -0,0 +1,16 @@
+#ifndef _FTL_CUDA_POINTS_HPP_
+#define _FTL_CUDA_POINTS_HPP_
+
+#include <ftl/cuda_common.hpp>
+#include <ftl/rgbd/camera.hpp>
+#include <ftl/cuda_matrix_util.hpp>
+
+namespace ftl {
+namespace cuda {
+
+void point_cloud(ftl::cuda::TextureObject<float4> &output, ftl::cuda::TextureObject<float> &depth, const ftl::rgbd::Camera &params, const float4x4 &pose, cudaStream_t stream);
+
+}
+}
+
+#endif  // _FTL_CUDA_POINTS_HPP_
diff --git a/components/renderers/cpp/include/ftl/cuda/weighting.hpp b/components/renderers/cpp/include/ftl/cuda/weighting.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..9498d0508605087306db2658b2a1ae1943cde536
--- /dev/null
+++ b/components/renderers/cpp/include/ftl/cuda/weighting.hpp
@@ -0,0 +1,23 @@
+#ifndef _FTL_CUDA_WEIGHTING_HPP_
+#define _FTL_CUDA_WEIGHTING_HPP_
+
+namespace ftl {
+namespace cuda {
+
+/*
+ * Guennebaud, G.; Gross, M. Algebraic point set surfaces. ACMTransactions on Graphics Vol. 26, No. 3, Article No. 23, 2007.
+ * Used in: FusionMLS: Highly dynamic 3D reconstruction with consumer-grade RGB-D cameras
+ *     r = distance between points
+ *     h = smoothing parameter in meters (default 4cm)
+ */
+__device__ inline float spatialWeighting(float r, float h) {
+	if (r >= h) return 0.0f;
+	float rh = r / h;
+	rh = 1.0f - rh*rh;
+	return rh*rh*rh*rh;
+}
+
+}
+}
+
+#endif  // _FTL_CUDA_WEIGHTING_HPP_ 
diff --git a/components/renderers/cpp/include/ftl/display.hpp b/components/renderers/cpp/include/ftl/display.hpp
deleted file mode 100644
index 05ae0bf11e5ebc3a5b8d54339bc85166effbb4a9..0000000000000000000000000000000000000000
--- a/components/renderers/cpp/include/ftl/display.hpp
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright 2019 Nicolas Pope
- */
-
-#ifndef _FTL_DISPLAY_HPP_
-#define _FTL_DISPLAY_HPP_
-
-#include <ftl/config.h>
-#include <ftl/configurable.hpp>
-#include "../../../rgbd-sources/include/ftl/rgbd/camera.hpp"
-
-#include <nlohmann/json.hpp>
-#include <opencv2/opencv.hpp>
-#include "opencv2/highgui.hpp"
-
-#if defined HAVE_PCL
-#include <pcl/common/common_headers.h>
-#include <pcl/visualization/pcl_visualizer.h>
-#endif  // HAVE_PCL
-
-namespace ftl {
-
-/**
- * Multiple local display options for disparity or point clouds.
- */
-class Display : public ftl::Configurable {
-	private:
-		std::string name_;
-	public:
-	enum style_t {
-		STYLE_NORMAL, STYLE_DISPARITY, STYLE_DEPTH
-	};
-
-	public:
-	explicit Display(nlohmann::json &config, std::string name);
-	~Display();
-	
-	bool render(const cv::Mat &rgb, const cv::Mat &depth, const ftl::rgbd::Camera &p);
-
-#if defined HAVE_PCL
-	bool render(pcl::PointCloud<pcl::PointXYZRGB>::ConstPtr);
-#endif  // HAVE_PCL
-	bool render(const cv::Mat &img, style_t s=STYLE_NORMAL);
-
-	bool active() const;
-
-	bool hasDisplays();
-	
-	void wait(int ms);
-
-	void onKey(const std::function<void(int)> &h) { key_handlers_.push_back(h); }
-
-	private:
-#if defined HAVE_VIZ
-	cv::viz::Viz3d *window_;
-#endif  // HAVE_VIZ
-
-#if defined HAVE_PCL
-	pcl::visualization::PCLVisualizer::Ptr pclviz_;
-#endif  // HAVE_PCL
-
-	bool active_;
-	std::vector<std::function<void(int)>> key_handlers_;
-};
-};
-
-#endif  // _FTL_DISPLAY_HPP_
-
diff --git a/components/renderers/cpp/include/ftl/render/renderer.hpp b/components/renderers/cpp/include/ftl/render/renderer.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..1871b9f9f2a8e1fda0766e1c2e74d2169f47f3fa
--- /dev/null
+++ b/components/renderers/cpp/include/ftl/render/renderer.hpp
@@ -0,0 +1,35 @@
+#ifndef _FTL_RENDER_RENDERER_HPP_
+#define _FTL_RENDER_RENDERER_HPP_
+
+#include <ftl/configurable.hpp>
+#include <ftl/rgbd/virtual.hpp>
+#include <ftl/cuda_common.hpp>
+
+namespace ftl {
+namespace render {
+
+/**
+ * Abstract class for all renderers. A renderer takes some 3D scene and
+ * generates a virtual camera perspective of that scene. The scene might be
+ * based upon a point cloud, or an entirely virtual mesh or procedural scene.
+ * It is intended that multiple scenes can be rendered into a single virtual
+ * view using a compositing renderer, such a renderer accepting any kind of
+ * renderer for compositing and hence relying on this base class.
+ */
+class Renderer : public ftl::Configurable {
+    public:
+    explicit Renderer(nlohmann::json &config) : Configurable(config) {};
+    virtual ~Renderer() {};
+
+    /**
+     * Generate a single virtual camera frame. The frame takes its pose from
+     * the virtual camera object passed, and writes the result into the
+     * virtual camera.
+     */
+    virtual bool render(ftl::rgbd::VirtualSource *, ftl::rgbd::Frame &, cudaStream_t)=0;
+};
+
+}
+}
+
+#endif  // _FTL_RENDER_RENDERER_HPP_
diff --git a/components/renderers/cpp/include/ftl/render/splat_params.hpp b/components/renderers/cpp/include/ftl/render/splat_params.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..4f9c8882b161d7774388e8d9fff7337cb1d6e685
--- /dev/null
+++ b/components/renderers/cpp/include/ftl/render/splat_params.hpp
@@ -0,0 +1,30 @@
+#ifndef _FTL_RENDER_SPLAT_PARAMS_HPP_
+#define _FTL_RENDER_SPLAT_PARAMS_HPP_
+
+#include <ftl/cuda_util.hpp>
+#include <ftl/cuda_matrix_util.hpp>
+#include <ftl/rgbd/camera.hpp>
+
+namespace ftl {
+namespace render {
+
+static const uint kShowBlockBorders = 0x00000001;  // Deprecated: from voxels system
+static const uint kNoSplatting = 0x00000002;
+static const uint kNoUpsampling = 0x00000004;
+static const uint kNoTexturing = 0x00000008;
+
+struct __align__(16) SplatParams {
+	float4x4 m_viewMatrix;
+	float4x4 m_viewMatrixInverse;
+
+	uint m_flags;
+	//float voxelSize;
+	float depthThreshold;
+
+	ftl::rgbd::Camera camera;
+};
+
+}
+}
+
+#endif
diff --git a/applications/reconstruct/src/splat_render.hpp b/components/renderers/cpp/include/ftl/render/splat_render.hpp
similarity index 57%
rename from applications/reconstruct/src/splat_render.hpp
rename to components/renderers/cpp/include/ftl/render/splat_render.hpp
index 7737bb5d903d725a55b81d75812b99b52e7ace66..55522c483c25f58d843543ee8d5ba42aae9c32c8 100644
--- a/applications/reconstruct/src/splat_render.hpp
+++ b/components/renderers/cpp/include/ftl/render/splat_render.hpp
@@ -1,14 +1,9 @@
 #ifndef _FTL_RECONSTRUCTION_SPLAT_HPP_
 #define _FTL_RECONSTRUCTION_SPLAT_HPP_
 
-#include <ftl/configurable.hpp>
-#include <ftl/rgbd/source.hpp>
-#include <ftl/depth_camera.hpp>
-#include <ftl/voxel_scene.hpp>
-//#include <ftl/ray_cast_util.hpp>
-#include <ftl/cuda_common.hpp>
-
-#include "splat_params.hpp"
+#include <ftl/render/renderer.hpp>
+#include <ftl/rgbd/frameset.hpp>
+#include <ftl/render/splat_params.hpp>
 
 namespace ftl {
 namespace render {
@@ -21,23 +16,28 @@ namespace render {
  * on a separate machine or at a later time, the advantage being to save local
  * processing resources and that the first pass result may compress better.
  */
-class Splatter {
+class Splatter : public ftl::render::Renderer {
 	public:
-	explicit Splatter(ftl::voxhash::SceneRep *scene);
+	explicit Splatter(nlohmann::json &config, ftl::rgbd::FrameSet *fs);
 	~Splatter();
 
-	void render(ftl::rgbd::Source *src, cudaStream_t stream=0);
+	bool render(ftl::rgbd::VirtualSource *src, ftl::rgbd::Frame &out, cudaStream_t stream=0) override;
 
-	void setOutputDevice(int);
+	//void setOutputDevice(int);
 
 	private:
 	int device_;
-	ftl::cuda::TextureObject<int> depth1_;
+	/*ftl::cuda::TextureObject<int> depth1_;
+	ftl::cuda::TextureObject<int> depth3_;
 	ftl::cuda::TextureObject<uchar4> colour1_;
+	ftl::cuda::TextureObject<float4> colour_tmp_;
 	ftl::cuda::TextureObject<float> depth2_;
 	ftl::cuda::TextureObject<uchar4> colour2_;
-	SplatParams params_;
-	ftl::voxhash::SceneRep *scene_;
+	ftl::cuda::TextureObject<float4> normal1_;*/
+	//SplatParams params_;
+
+	ftl::rgbd::Frame temp_;
+	ftl::rgbd::FrameSet *scene_;
 };
 
 }
diff --git a/components/renderers/cpp/include/ftl/rgbd_display.hpp b/components/renderers/cpp/include/ftl/rgbd_display.hpp
deleted file mode 100644
index 9c1be76fb5f38a584d3032d50b6ef046f0d0b605..0000000000000000000000000000000000000000
--- a/components/renderers/cpp/include/ftl/rgbd_display.hpp
+++ /dev/null
@@ -1,47 +0,0 @@
-#ifndef _FTL_RGBD_DISPLAY_HPP_
-#define _FTL_RGBD_DISPLAY_HPP_
-
-#include <nlohmann/json.hpp>
-#include <ftl/rgbd/source.hpp>
-
-using MouseAction = std::function<void(int, int, int, int)>;
-
-namespace ftl {
-namespace rgbd {
-
-class Display : public ftl::Configurable {
-	public:
-	explicit Display(nlohmann::json &);
-	Display(nlohmann::json &, Source *);
-	~Display();
-
-	void setSource(Source *src) { source_ = src; }
-	void update();
-
-	bool active() const { return active_; }
-
-	void onKey(const std::function<void(int)> &h) { key_handlers_.push_back(h); }
-
-	void wait(int ms);
-
-	private:
-	Source *source_;
-	std::string name_;
-	std::vector<std::function<void(int)>> key_handlers_;
-	Eigen::Vector3d eye_;
-	Eigen::Vector3d centre_;
-	Eigen::Vector3d up_;
-	Eigen::Vector3d lookPoint_;
-	float lerpSpeed_;
-	bool active_;
-	MouseAction mouseaction_;
-
-	static int viewcount__;
-
-	void init();
-};
-
-}
-}
-
-#endif  // _FTL_RGBD_DISPLAY_HPP_
diff --git a/applications/reconstruct/include/ftl/matrix_conversion.hpp b/components/renderers/cpp/include/ftl/utility/matrix_conversion.hpp
similarity index 99%
rename from applications/reconstruct/include/ftl/matrix_conversion.hpp
rename to components/renderers/cpp/include/ftl/utility/matrix_conversion.hpp
index ac0bff14da8ad6be553943db2c04f6b9ef22844f..2888e928ebbe13c54585837882e81ad9c32cdad9 100644
--- a/applications/reconstruct/include/ftl/matrix_conversion.hpp
+++ b/components/renderers/cpp/include/ftl/utility/matrix_conversion.hpp
@@ -2,7 +2,7 @@
 
 #pragma once
 
-#include <eigen3/Eigen/Eigen>
+#include <Eigen/Eigen>
 // #include "d3dx9math.h"
 #include <ftl/cuda_matrix_util.hpp>
 
diff --git a/components/renderers/cpp/src/dibr.cu b/components/renderers/cpp/src/dibr.cu
new file mode 100644
index 0000000000000000000000000000000000000000..66428ce3086e42b6a3e81c667ae126314d910c98
--- /dev/null
+++ b/components/renderers/cpp/src/dibr.cu
@@ -0,0 +1,781 @@
+#include "splat_render_cuda.hpp"
+#include "depth_camera_cuda.hpp"
+//#include <cuda_runtime.h>
+
+#include <ftl/cuda_matrix_util.hpp>
+
+#include "splat_params.hpp"
+#include "mls_cuda.hpp"
+#include <ftl/depth_camera.hpp>
+
+#define T_PER_BLOCK 8
+#define UPSAMPLE_FACTOR 1.8f
+#define WARP_SIZE 32
+#define DEPTH_THRESHOLD 0.05f
+#define UPSAMPLE_MAX 60
+#define MAX_ITERATIONS 32  // Note: Must be multiple of 32
+#define SPATIAL_SMOOTHING 0.005f
+
+using ftl::cuda::TextureObject;
+using ftl::render::SplatParams;
+
+extern __constant__ ftl::voxhash::DepthCameraCUDA c_cameras[MAX_CAMERAS];
+
+__global__ void clearColourKernel(TextureObject<uchar4> colour) {
+	const unsigned int x = blockIdx.x*blockDim.x + threadIdx.x;
+	const unsigned int y = blockIdx.y*blockDim.y + threadIdx.y;
+
+	if (x < colour.width() && y < colour.height()) {
+		//depth(x,y) = 0x7f800000; //PINF;
+		colour(x,y) = make_uchar4(76,76,82,0);
+	}
+}
+
+__device__ inline bool isStable(const float3 &previous, const float3 &estimate, const SplatParams &params, float d) {
+    const float psize = 2.0f * d / params.camera.fx;
+    //printf("PSIZE %f\n", psize);
+    return fabs(previous.x - estimate.x) <= psize &&
+        fabs(previous.y - estimate.y) <= psize &&
+        fabs(previous.z - estimate.z) <= psize;
+}
+
+// ===== PASS 1 : Gather & Upsample (Depth) ====================================
+
+/*
+ * Pass 1: Directly render raw points from all cameras, but upsample the points
+ * if their spacing is within smoothing threshold but greater than their pixel
+ * size in the original image.
+ */
+ __global__ void dibr_merge_upsample_kernel(TextureObject<int> depth, int cam, SplatParams params) {
+	const ftl::voxhash::DepthCameraCUDA &camera = c_cameras[cam];
+
+	const int x = (blockIdx.x*blockDim.x + threadIdx.x) / WARP_SIZE;
+	const int y = blockIdx.y*blockDim.y + threadIdx.y;
+
+	const float3 worldPos = make_float3(tex2D<float4>(camera.points, x, y));
+	//const float3 normal = make_float3(tex2D<float4>(camera.normal, x, y));
+	if (worldPos.x == MINF) return;
+    const float r = (camera.poseInverse * worldPos).z / camera.params.fx;
+
+	// Get virtual camera ray for splat centre and backface cull if possible
+	//const float3 rayOrigin = params.m_viewMatrixInverse * make_float3(0.0f,0.0f,0.0f);
+	//const float3 rayDir = normalize(params.m_viewMatrixInverse * params.camera.kinectDepthToSkeleton(x,y,1.0f) - rayOrigin);
+	//if (dot(rayDir, normal) > 0.0f) return;
+
+    // Find the virtual screen position of current point
+	const float3 camPos = params.m_viewMatrix * worldPos;
+	if (camPos.z < params.camera.m_sensorDepthWorldMin) return;
+	if (camPos.z > params.camera.m_sensorDepthWorldMax) return;
+
+	// TODO: Don't upsample so much that only minimum depth makes it through
+	// Consider also using some SDF style approach to accumulate and smooth a
+	// depth value between points
+	const int upsample = min(UPSAMPLE_MAX-2, int(0.01 * params.camera.fx / camPos.z))+3;
+	const float interval = 1.0f / float(upsample / 2);
+
+            
+    // TODO:(Nick) Check depth buffer and don't do anything if already hidden?
+
+	// Each thread in warp takes an upsample point and updates corresponding depth buffer.
+	const int lane = threadIdx.x % WARP_SIZE;
+	for (int i=lane; i<upsample*upsample; i+=WARP_SIZE) {
+		const float u = (i % upsample) - (upsample / 2);
+		const float v = (i / upsample) - (upsample / 2);
+
+        // Make an initial estimate of the points location
+		// Use centroid depth as estimate...?
+		const float3 point = params.m_viewMatrix * ftl::cuda::upsampled_point(camera.points, make_float2(float(x)+float(u)*interval, float(y)+float(v)*interval));
+		const float d = point.z;
+
+		const uint2 screenPos = params.camera.cameraToKinectScreen(point);
+		const unsigned int cx = screenPos.x;//+u;
+        const unsigned int cy = screenPos.y;//+v;
+		if (d > params.camera.m_sensorDepthWorldMin && d < params.camera.m_sensorDepthWorldMax && cx < depth.width() && cy < depth.height()) {
+			// Transform estimated point to virtual cam space and output z
+			atomicMin(&depth(cx,cy), d * 1000.0f);
+		}
+	}
+}
+
+/*
+ * Pass 1: Directly render each camera into virtual view but with no upsampling
+ * for sparse points.
+ */
+ __global__ void dibr_merge_kernel(TextureObject<int> depth, int cam, SplatParams params) {
+	const ftl::voxhash::DepthCameraCUDA &camera = c_cameras[cam];
+
+	const int x = blockIdx.x*blockDim.x + threadIdx.x;
+	const int y = blockIdx.y*blockDim.y + threadIdx.y;
+
+	const float3 worldPos = make_float3(tex2D<float4>(camera.points, x, y));
+	if (worldPos.x == MINF) return;
+
+    // Find the virtual screen position of current point
+	const float3 camPos = params.m_viewMatrix * worldPos;
+	if (camPos.z < params.camera.m_sensorDepthWorldMin) return;
+	if (camPos.z > params.camera.m_sensorDepthWorldMax) return;
+
+	const float d = camPos.z;
+
+	const uint2 screenPos = params.camera.cameraToKinectScreen(camPos);
+	const unsigned int cx = screenPos.x;
+	const unsigned int cy = screenPos.y;
+	if (d > params.camera.m_sensorDepthWorldMin && d < params.camera.m_sensorDepthWorldMax && cx < depth.width() && cy < depth.height()) {
+		// Transform estimated point to virtual cam space and output z
+		atomicMin(&depth(cx,cy), d * 1000.0f);
+	}
+}
+
+// ===== PASS 2 : Splat Visible Surface ========================================
+
+/*
+ * Pass 2: Determine depth buffer with enough accuracy for a visibility test in pass 2.
+ * These values are also used as the actual surface estimate during rendering so should
+ * at least be plane or sphere fitted if not MLS smoothed onto the actual surface.
+ */
+__global__ void OLD_dibr_visibility_kernel(TextureObject<int> depth, int cam, SplatParams params) {
+	const ftl::voxhash::DepthCameraCUDA &camera = c_cameras[cam];
+
+	const int x = (blockIdx.x*blockDim.x + threadIdx.x) / WARP_SIZE;
+	const int y = blockIdx.y*blockDim.y + threadIdx.y;
+
+	const float3 worldPos = make_float3(tex2D<float4>(camera.points, x, y));
+	const float3 normal = make_float3(tex2D<float4>(camera.normal, x, y));
+	if (worldPos.x == MINF) return;
+    const float r = (camera.poseInverse * worldPos).z / camera.params.fx;
+
+	// Get virtual camera ray for splat centre and backface cull if possible
+	//const float3 rayOrigin = params.m_viewMatrixInverse * make_float3(0.0f,0.0f,0.0f);
+	//const float3 rayDir = normalize(params.m_viewMatrixInverse * params.camera.kinectDepthToSkeleton(x,y,1.0f) - rayOrigin);
+	//if (dot(rayDir, normal) > 0.0f) return;
+
+    // Find the virtual screen position of current point
+	const float3 camPos = params.m_viewMatrix * worldPos;
+	if (camPos.z < params.camera.m_sensorDepthWorldMin) return;
+	if (camPos.z > params.camera.m_sensorDepthWorldMax) return;
+	const uint2 screenPos = params.camera.cameraToKinectScreen(camPos);
+
+	const int upsample = min(UPSAMPLE_MAX, int((r) * params.camera.fx / camPos.z));
+
+	// Not on screen so stop now...
+	if (screenPos.x - upsample >= depth.width() || screenPos.y - upsample >= depth.height()) return;
+            
+    // TODO:(Nick) Check depth buffer and don't do anything if already hidden?
+
+	// Each thread in warp takes an upsample point and updates corresponding depth buffer.
+	const int lane = threadIdx.x % WARP_SIZE;
+	for (int i=lane; i<upsample*upsample; i+=WARP_SIZE) {
+		const float u = (i % upsample) - (upsample / 2);
+		const float v = (i / upsample) - (upsample / 2);
+
+        // Make an initial estimate of the points location
+		// Use centroid depth as estimate...?
+        float3 nearest = ftl::cuda::screen_centroid<1>(camera.points, make_float2(screenPos.x+u, screenPos.y+v), make_int2(x,y), params, upsample);
+
+		// Use current points z as estimate
+		//float3 nearest = params.camera.kinectDepthToSkeleton(screenPos.x+u,screenPos.y+v,camPos.z);
+		
+		// Or calculate upper and lower bounds for depth and do gradient
+		// descent until the gradient change is too small or max iter is reached
+		// and depth remains within the bounds.
+		// How to find min and max depths?
+
+        //float ld = nearest.z;
+
+		// TODO: (Nick) Estimate depth using points plane, but needs better normals.
+		//float t;
+		//if (ftl::cuda::intersectPlane(normal, worldPos, rayOrigin, rayDir, t)) {
+			// Plane based estimate of surface at this pixel
+			//const float3 nearest = rayOrigin + rayDir * camPos.z;
+			float3 output;
+
+            // Use MLS of camera neighbor points to get more exact estimate
+            // Iterate until pixel is stable on the surface.
+            for (int k=0; k<MAX_ITERATIONS; ++k) {
+
+                // TODO:(Nick) Should perhaps use points from all cameras?
+                // Instead of doing each camera separately...
+                // If the depth already is close then it has already been done and can skip this point
+                if (ftl::cuda::mls_point_surface<1>(camera.points, make_int2(x,y), params.m_viewMatrixInverse * nearest, output, SPATIAL_SMOOTHING) <= 0.0f) {
+                    /*const unsigned int cx = screenPos.x;
+                    const unsigned int cy = screenPos.y;
+                    if (cx < depth.width() && cy < depth.height()) {
+                        atomicMax(&depth(cx,cy), 10000.0f);
+                    }*/
+                    break;
+                }
+            
+				//ftl::cuda::render_depth(depth, params, output);
+
+				output = params.m_viewMatrix * output;
+
+                // This is essentially the SDF function f(x), only the normal should be estimated also from the weights
+                //const float d = nearest.z + (normal.x*output.x + normal.y*output.y + normal.z*output.z);
+
+				const float d = nearest.z + copysignf(0.5f*length(output - nearest), output.z - nearest.z);
+				nearest = params.camera.kinectDepthToSkeleton(screenPos.x+u,screenPos.y+v,d);
+
+                const float2 sp = params.camera.cameraToKinectScreenFloat(output);
+
+                //if (isStable(nearest, output, params, d)) {
+                //if (fabs(sp.x - float(screenPos.x+u)) < 2.0f && fabs(sp.y - float(screenPos.y+v)) < 2.0f) {
+				if (length(output - nearest) <= 2.0f * params.camera.fx / camPos.z) {
+                    const unsigned int cx = screenPos.x+u;
+                    const unsigned int cy = screenPos.y+v;
+
+                    if (d > params.camera.m_sensorDepthWorldMin && d < params.camera.m_sensorDepthWorldMax && cx < depth.width() && cy < depth.height()) {
+                        // Transform estimated point to virtual cam space and output z
+                        atomicMin(&depth(cx,cy), d * 1000.0f);
+                    }
+                    break;
+                }
+
+                /*if (k >= MAX_ITERATIONS-1 && length(output - nearest) <= SPATIAL_SMOOTHING) {
+                    const unsigned int cx = screenPos.x+u;
+                    const unsigned int cy = screenPos.y+v;
+                    if (d > params.camera.m_sensorDepthWorldMin && d < params.camera.m_sensorDepthWorldMax && cx < depth.width() && cy < depth.height()) {
+						//atomicMin(&depth(cx,cy), d * 1000.0f);
+						printf("ERR = %f, %f\n", fabs(sp.x - float(screenPos.x+u)), fabs(sp.y - float(screenPos.y+v)));
+                    }
+                }*/
+
+                //nearest = params.camera.kinectDepthToSkeleton(screenPos.x+u,screenPos.y+v,d);  // ld + (d - ld)*0.8f
+                //ld = d;
+			}
+		//}
+	}
+}
+
+// ------ Alternative for pass 2: principle surfaces ---------------------------
+
+#define NEIGHBOR_RADIUS 1
+#define MAX_NEIGHBORS ((NEIGHBOR_RADIUS*2+1)*(NEIGHBOR_RADIUS*2+1))
+
+/*
+ * Pass 2: Determine depth buffer with enough accuracy for a visibility test in pass 2.
+ * These values are also used as the actual surface estimate during rendering so should
+ * at least be plane or sphere fitted if not MLS smoothed onto the actual surface.
+ */
+ __global__ void dibr_visibility_principal_kernel(TextureObject<int> depth, int cam, SplatParams params) {
+	__shared__ float3 neighborhood_cache[2*T_PER_BLOCK][MAX_NEIGHBORS];
+	__shared__ int minimum[2*T_PER_BLOCK];
+	__shared__ int maximum[2*T_PER_BLOCK];
+
+	const ftl::voxhash::DepthCameraCUDA &camera = c_cameras[cam];
+
+	const int warp = threadIdx.x / WARP_SIZE + threadIdx.y*2;
+	const int x = (blockIdx.x*blockDim.x + threadIdx.x) / WARP_SIZE;
+	const int y = blockIdx.y*blockDim.y + threadIdx.y;
+
+	const float3 worldPos = make_float3(tex2D<float4>(camera.points, x, y));
+	//const float3 normal = make_float3(tex2D<float4>(camera.normal, x, y));
+	if (worldPos.x == MINF) return;
+    const float r = (camera.poseInverse * worldPos).z / camera.params.fx;
+
+	// Get virtual camera ray for splat centre and backface cull if possible
+	//const float3 rayOrigin = params.m_viewMatrixInverse * make_float3(0.0f,0.0f,0.0f);
+	//const float3 rayDir = normalize(params.m_viewMatrixInverse * params.camera.kinectDepthToSkeleton(x,y,1.0f) - rayOrigin);
+	//if (dot(rayDir, normal) > 0.0f) return;
+
+    // Find the virtual screen position of current point
+	const float3 camPos = params.m_viewMatrix * worldPos;
+	if (camPos.z < params.camera.m_sensorDepthWorldMin) return;
+	if (camPos.z > params.camera.m_sensorDepthWorldMax) return;
+	const uint2 screenPos = params.camera.cameraToKinectScreen(camPos);
+
+	const int upsample = min(UPSAMPLE_MAX, int((4.0f*r) * params.camera.fx / camPos.z));
+
+	// Not on screen so stop now...
+	if (screenPos.x - upsample >= depth.width() || screenPos.y - upsample >= depth.height()) return;
+            
+	// TODO:(Nick) Check depth buffer and don't do anything if already hidden?
+	
+	// TODO:(Nick) Preload point neighbors and transform to eye
+	const int lane = threadIdx.x % WARP_SIZE;
+	if (lane == 0) {
+		minimum[warp] = 100000000;
+		maximum[warp] = -100000000;
+	}
+
+	__syncwarp();
+
+	for (int i=lane; i<MAX_NEIGHBORS; i+=WARP_SIZE) {
+		const int u = (i % (2*NEIGHBOR_RADIUS+1)) - NEIGHBOR_RADIUS;
+		const int v = (i / (2*NEIGHBOR_RADIUS+1)) - NEIGHBOR_RADIUS;
+		const float3 point = params.m_viewMatrix * make_float3(tex2D<float4>(camera.points, x+u, y+v));
+		neighborhood_cache[warp][i] = point;
+
+		if (length(point - camPos) <= 0.04f) {
+			atomicMin(&minimum[warp], point.z*1000.0f);
+			atomicMax(&maximum[warp], point.z*1000.0f);
+		}
+	}
+
+	__syncwarp();
+	
+	const float interval = (float(maximum[warp])/1000.0f - float(minimum[warp]) / 1000.0f) / float(MAX_ITERATIONS);
+	//if (y == 200) printf("interval: %f\n", interval);
+
+	// TODO:(Nick) Find min and max depths of neighbors to estimate z bounds
+
+	// Each thread in warp takes an upsample point and updates corresponding depth buffer.
+	for (int i=lane; i<upsample*upsample; i+=WARP_SIZE) {
+		const float u = (i % upsample) - (upsample / 2);
+		const float v = (i / upsample) - (upsample / 2);
+
+        // Make an initial estimate of the points location
+		// Use centroid depth as estimate...?
+        //float3 nearest = ftl::cuda::screen_centroid<1>(camera.points, make_float2(screenPos.x+u, screenPos.y+v), make_int2(x,y), params, upsample);
+
+		// Use current points z as estimate
+		// TODO: Use min point as estimate
+		float3 nearest = params.camera.kinectDepthToSkeleton(screenPos.x+u,screenPos.y+v,float(minimum[warp])/1000.0f);
+		
+		// Or calculate upper and lower bounds for depth and do gradient
+		// descent until the gradient change is too small or max iter is reached
+		// and depth remains within the bounds.
+		// How to find min and max depths?
+
+		// TODO: (Nick) Estimate depth using points plane, but needs better normals.
+		//float t;
+		//if (ftl::cuda::intersectPlane(normal, worldPos, rayOrigin, rayDir, t)) {
+			// Plane based estimate of surface at this pixel
+			//const float3 nearest = rayOrigin + rayDir * camPos.z;
+
+            // Use MLS of camera neighbor points to get more exact estimate
+            // Iterate until pixel is stable on the surface.
+            for (int k=0; k<MAX_ITERATIONS; ++k) {
+
+                // TODO:(Nick) Should perhaps use points from all cameras?
+                // Instead of doing each camera separately...
+                // If the depth already is close then it has already been done and can skip this point
+				const float energy = ftl::cuda::mls_point_energy<MAX_NEIGHBORS>(neighborhood_cache[warp], nearest, SPATIAL_SMOOTHING);
+				
+				if (energy <= 0.0f) break;
+            
+				//ftl::cuda::render_depth(depth, params, output);
+
+                // This is essentially the SDF function f(x), only the normal should be estimated also from the weights
+                //const float d = nearest.z + (normal.x*output.x + normal.y*output.y + normal.z*output.z);
+
+				const float d = nearest.z;
+				nearest = params.camera.kinectDepthToSkeleton(screenPos.x+u,screenPos.y+v,d+interval);
+				
+				if (energy >= 0.1f) {
+					const unsigned int cx = screenPos.x+u;
+                    const unsigned int cy = screenPos.y+v;
+					if (d > params.camera.m_sensorDepthWorldMin && d < params.camera.m_sensorDepthWorldMax && cx < depth.width() && cy < depth.height()) {
+                        // Transform estimated point to virtual cam space and output z
+                        atomicMin(&depth(cx,cy), d * 1000.0f);
+					}
+					break;
+				}
+			}
+		//}
+	}
+}
+
+#define NEIGHBOR_RADIUS_2 3
+#define NEIGHBOR_WINDOW ((NEIGHBOR_RADIUS_2*2+1)*(NEIGHBOR_RADIUS_2*2+1))
+#define MAX_NEIGHBORS_2 32
+
+#define FULL_MASK 0xffffffff
+
+__device__ inline float warpMax(float e) {
+	for (int i = WARP_SIZE/2; i > 0; i /= 2) {
+		const float other = __shfl_xor_sync(FULL_MASK, e, i, WARP_SIZE);
+		e = max(e, other);
+	}
+	return e;
+}
+
+__device__ inline float warpMin(float e) {
+	for (int i = WARP_SIZE/2; i > 0; i /= 2) {
+		const float other = __shfl_xor_sync(FULL_MASK, e, i, WARP_SIZE);
+		e = min(e, other);
+	}
+	return e;
+}
+
+#define ENERGY_THRESHOLD 0.1f
+#define SMOOTHING_MULTIPLIER_A 10.0f	// For surface search
+#define SMOOTHING_MULTIPLIER_B 4.0f		// For z contribution
+#define SMOOTHING_MULTIPLIER_C 4.0f		// For colour contribution
+
+
+/*
+ * Pass 2: Determine depth buffer with enough accuracy for a visibility test in pass 2.
+ * These values are also used as the actual surface estimate during rendering so should
+ * at least be plane or sphere fitted if not MLS smoothed onto the actual surface.
+ *
+ * This version uses a previous point render as neighbour source.
+ */
+ __global__ void dibr_visibility_principal_kernel2(TextureObject<int> point_in, TextureObject<int> depth, SplatParams params) {
+	__shared__ float3 neighborhood_cache[2*T_PER_BLOCK][MAX_NEIGHBORS_2];
+	__shared__ int minimum[2*T_PER_BLOCK];
+	__shared__ int maximum[2*T_PER_BLOCK];
+	__shared__ unsigned int nidx[2*T_PER_BLOCK];
+
+	const int tid = (threadIdx.x + threadIdx.y * blockDim.x);
+	const int warp = tid / WARP_SIZE; //threadIdx.x / WARP_SIZE + threadIdx.y*2;
+	const int x = (blockIdx.x*blockDim.x + threadIdx.x) / WARP_SIZE;
+	const int y = blockIdx.y*blockDim.y + threadIdx.y;
+
+	// Starting point for surface minimum
+	float clusterBase = params.camera.m_sensorDepthWorldMin;
+
+	// Loop to a deeper surface if not on the first one selected...
+	while (clusterBase < params.camera.m_sensorDepthWorldMax) {
+
+	const int lane = tid % WARP_SIZE;
+	if (lane == 0) {
+		minimum[warp] = 100000000;
+		maximum[warp] = -100000000;
+		nidx[warp] = 0;
+	}
+
+	__syncwarp();
+
+	// Search for a valid minimum neighbour
+	// TODO: Should this really be minimum or the median of a depth cluster?
+	// cluster median seems very hard to calculate...
+	for (int i=lane; i<NEIGHBOR_WINDOW; i+=WARP_SIZE) {
+		const int u = (i % (2*NEIGHBOR_RADIUS_2+1)) - NEIGHBOR_RADIUS_2;
+		const int v = (i / (2*NEIGHBOR_RADIUS_2+1)) - NEIGHBOR_RADIUS_2;
+		const float3 point = params.camera.kinectDepthToSkeleton(x+u, y+v, float(point_in.tex2D(x+u, y+v)) / 1000.0f);
+		const float3 camPos = params.camera.kinectDepthToSkeleton(x, y, point.z);
+
+		// If it is close enough...
+		// TODO: smoothing / strength should be determined by a number of factors including:
+		//     1) Depth from original source
+		//     2) Colour contrast in underlying RGB
+		//     3) Estimated noise levels in depth values
+		if (point.z > clusterBase && point.z < params.camera.m_sensorDepthWorldMax && length(point - camPos) <= SMOOTHING_MULTIPLIER_A*(point.z / params.camera.fx)) {
+			atomicMin(&minimum[warp], point.z*1000.0f);
+		}
+	}
+
+	__syncwarp();
+
+	const float minDepth = float(minimum[warp])/1000.0f;
+	
+	// Preload valid neighbour points from within a window. A point is valid
+	// if it is within a specific distance of the minimum.
+	// Also calculate the maximum at the same time.
+	// TODO: Could here do a small search in each camera? This would allow all
+	// points to be considered, even those masked in our depth input.
+	const float3 minPos = params.camera.kinectDepthToSkeleton(x, y, minDepth);
+
+	for (int i=lane; i<NEIGHBOR_WINDOW; i+=WARP_SIZE) {
+		const int u = (i % (2*NEIGHBOR_RADIUS_2+1)) - NEIGHBOR_RADIUS_2;
+		const int v = (i / (2*NEIGHBOR_RADIUS_2+1)) - NEIGHBOR_RADIUS_2;
+		const float3 point = params.camera.kinectDepthToSkeleton(x+u, y+v, float(point_in.tex2D(x+u, y+v)) / 1000.0f);
+
+		// If it is close enough...
+		if (point.z > params.camera.m_sensorDepthWorldMin && point.z < params.camera.m_sensorDepthWorldMax && length(point - minPos) <= SMOOTHING_MULTIPLIER_A*(point.z / params.camera.fx)) {
+			// Append to neighbour list
+			//unsigned int idx = atomicInc(&nidx[warp], MAX_NEIGHBORS_2-1);
+			unsigned int idx = atomicAdd(&nidx[warp], 1);
+			if (idx >= MAX_NEIGHBORS_2) break;
+			neighborhood_cache[warp][idx] = point;
+			atomicMax(&maximum[warp], point.z*1000.0f);
+		}
+	}
+
+	__syncwarp();
+
+	const float maxDepth = float(maximum[warp])/1000.0f;
+	const float interval = (maxDepth - minDepth) / float(MAX_ITERATIONS);
+
+	if (minDepth >= params.camera.m_sensorDepthWorldMax) return;
+	if (maxDepth <= params.camera.m_sensorDepthWorldMin) return;
+	//if (y == 200) printf("interval: %f\n", maxDepth);
+
+	// If all samples say same depth, then agree and return
+	// TODO: Check this is valid, since small energies should be removed...
+	/*if (fabs(minDepth - maxDepth) < 0.0001f) {
+		if (lane == 0) {
+			const unsigned int cx = x;
+			const unsigned int cy = y;
+			if (minDepth < params.camera.m_sensorDepthWorldMax && cx < depth.width() && cy < depth.height()) {
+				// Transform estimated point to virtual cam space and output z
+				atomicMin(&depth(cx,cy), minDepth * 1000.0f);
+			}
+		}
+		return;
+	}*/
+
+
+	float maxenergy = -1.0f;
+	float bestdepth = 0.0f;
+
+	// Search for best or threshold energy
+	for (int k=lane; k<MAX_ITERATIONS; k+=WARP_SIZE) {
+		const float3 nearest = params.camera.kinectDepthToSkeleton(x,y,minDepth+float(k)*interval);
+		const float myenergy = ftl::cuda::mls_point_energy<MAX_NEIGHBORS_2>(neighborhood_cache[warp], nearest, min(nidx[warp], MAX_NEIGHBORS_2), SMOOTHING_MULTIPLIER_B*(nearest.z/params.camera.fx));
+		const float newenergy = warpMax(max(myenergy, maxenergy));
+		bestdepth = (myenergy == newenergy) ? nearest.z : (newenergy > maxenergy) ? 0.0f : bestdepth;
+		maxenergy = newenergy;
+	}
+
+	// If enough energy was found and this thread was the one that found the best
+	// then output the depth that this energy occured at.
+	if (bestdepth > 0.0f && maxenergy >= ENERGY_THRESHOLD) {
+		//printf("E D %f %f\n", maxenergy, bestdepth);
+		const unsigned int cx = x;
+		const unsigned int cy = y;
+		if (bestdepth > params.camera.m_sensorDepthWorldMin && bestdepth < params.camera.m_sensorDepthWorldMax && cx < depth.width() && cy < depth.height()) {
+			// Transform estimated point to virtual cam space and output z
+			atomicMin(&depth(cx,cy), bestdepth * 1000.0f);
+			//depth(cx,cy) = bestdepth * 1000.0f;
+		}
+	}
+
+	// TODO: Could the threshold depend upon the number of points? Fewer points
+	// due to distance is incorrect since really there may not be fewer points
+	// Perhaps the best option is to make it depend on depth ... really close
+	// and really far both has lower thresholds due to point densities. Other
+	// option is smoothing factor and surface distances alter with distance to
+	// vary the number of points used ... smoothing factor could be a multiple
+	// of pixel size at given distance. Density from original source is also
+	// an influencer of smoothing factor and thresholds. Colour contrast also
+	// has a weighting influence, high contrast is high certainty in the
+	// disparity so such points should have a high influence over choice of
+	// surface location.
+	//
+	// Magnitude vs dispersion factor in the energy function ...
+	//   * Mag is certainty of surface location
+	//   * Dispersion is how far to propagate that certainty,
+	if (maxenergy >= ENERGY_THRESHOLD) return;
+
+	// Move to next possible surface...
+	clusterBase = minDepth + SMOOTHING_MULTIPLIER_B*(minDepth / params.camera.fx);
+
+	};
+}
+
+// ===== Pass 2 and 3 : Attribute contributions ================================
+
+__device__ inline float4 make_float4(const uchar4 &c) {
+    return make_float4(c.x,c.y,c.z,c.w);
+}
+
+/*
+ * Pass 2: Accumulate attribute contributions if the points pass a visibility test.
+ */
+__global__ void dibr_attribute_contrib_kernel(
+        TextureObject<int> depth_in,
+        TextureObject<float4> colour_out,
+        TextureObject<float4> normal_out,
+        TextureObject<float> contrib_out, int cam,
+        SplatParams params) {
+        
+	const ftl::voxhash::DepthCameraCUDA &camera = c_cameras[cam];
+
+	const int tid = (threadIdx.x + threadIdx.y * blockDim.x);
+	//const int warp = tid / WARP_SIZE;
+	const int x = (blockIdx.x*blockDim.x + threadIdx.x) / WARP_SIZE;
+	const int y = blockIdx.y*blockDim.y + threadIdx.y;
+
+	const float3 worldPos = make_float3(tex2D<float4>(camera.points, x, y));
+	//const float3 normal = make_float3(tex2D<float4>(camera.normal, x, y));
+	if (worldPos.x == MINF) return;
+    const float r = (camera.poseInverse * worldPos).z / camera.params.fx;
+
+	const float3 camPos = params.m_viewMatrix * worldPos;
+	if (camPos.z < params.camera.m_sensorDepthWorldMin) return;
+	if (camPos.z > params.camera.m_sensorDepthWorldMax) return;
+	const uint2 screenPos = params.camera.cameraToKinectScreen(camPos);
+
+    const int upsample = 8; //min(UPSAMPLE_MAX, int((5.0f*r) * params.camera.fx / camPos.z));
+
+	// Not on screen so stop now...
+	if (screenPos.x >= depth_in.width() || screenPos.y >= depth_in.height()) return;
+            
+    // Is this point near the actual surface and therefore a contributor?
+    const float d = ((float)depth_in.tex2D((int)screenPos.x, (int)screenPos.y)/1000.0f);
+    //if (abs(d - camPos.z) > DEPTH_THRESHOLD) return;
+
+    // TODO:(Nick) Should just one thread load these to shared mem?
+    const float4 colour = make_float4(tex2D<uchar4>(camera.colour, x, y));
+    const float4 normal = tex2D<float4>(camera.normal, x, y);
+
+	// Each thread in warp takes an upsample point and updates corresponding depth buffer.
+	const int lane = tid % WARP_SIZE;
+	for (int i=lane; i<upsample*upsample; i+=WARP_SIZE) {
+		const float u = (i % upsample) - (upsample / 2);
+		const float v = (i / upsample) - (upsample / 2);
+
+        // Use the depth buffer to determine this pixels 3D position in camera space
+        const float d = ((float)depth_in.tex2D(screenPos.x+u, screenPos.y+v)/1000.0f);
+		const float3 nearest = params.camera.kinectDepthToSkeleton((int)(screenPos.x+u),(int)(screenPos.y+v),d);
+
+        // What is contribution of our current point at this pixel?
+        const float weight = ftl::cuda::spatialWeighting(length(nearest - camPos), SMOOTHING_MULTIPLIER_C*(nearest.z/params.camera.fx));
+        if (screenPos.x+u < colour_out.width() && screenPos.y+v < colour_out.height() && weight > 0.0f) {  // TODO: Use confidence threshold here
+            const float4 wcolour = colour * weight;
+			const float4 wnormal = normal * weight;
+			
+			//printf("Z %f\n", d);
+
+            // Add this points contribution to the pixel buffer
+            atomicAdd((float*)&colour_out(screenPos.x+u, screenPos.y+v), wcolour.x);
+            atomicAdd((float*)&colour_out(screenPos.x+u, screenPos.y+v)+1, wcolour.y);
+            atomicAdd((float*)&colour_out(screenPos.x+u, screenPos.y+v)+2, wcolour.z);
+            atomicAdd((float*)&colour_out(screenPos.x+u, screenPos.y+v)+3, wcolour.w);
+            atomicAdd((float*)&normal_out(screenPos.x+u, screenPos.y+v), wnormal.x);
+            atomicAdd((float*)&normal_out(screenPos.x+u, screenPos.y+v)+1, wnormal.y);
+            atomicAdd((float*)&normal_out(screenPos.x+u, screenPos.y+v)+2, wnormal.z);
+            atomicAdd((float*)&normal_out(screenPos.x+u, screenPos.y+v)+3, wnormal.w);
+            atomicAdd(&contrib_out(screenPos.x+u, screenPos.y+v), weight);
+        }
+	}
+}
+
+/*
+ * Pass 2: Accumulate attribute contributions if the points pass a visibility test.
+ */
+/*__global__ void dibr_attribute_contrib_kernel(
+    TextureObject<int> depth_in,
+	TextureObject<uchar4> colour_out,
+	TextureObject<float4> normal_out, int numcams, SplatParams params) {
+
+    const int i = threadIdx.y*blockDim.y + threadIdx.x;
+    const int bx = blockIdx.x*blockDim.x;
+    const int by = blockIdx.y*blockDim.y;
+    const int x = bx + threadIdx.x;
+    const int y = by + threadIdx.y;
+    
+    for (int j=0; j<numcams; ++j) {
+        const ftl::voxhash::DepthCameraCUDA &camera = c_cameras[j];
+	
+        float3 worldPos = make_float3(tex2D<float4>(camera.points, x, y));
+        float r = (camera.poseInverse * worldPos).z;
+        //if (ftl::cuda::mls_point_surface<3>(camera.points, make_int2(x,y), worldPos, 0.02f) < 0.001f) continue;
+        if (worldPos.x == MINF) continue;
+        
+        const float3 camPos = params.m_viewMatrix * worldPos;
+
+        // Estimate upsample factor using ratio of source depth and output depth
+
+		const int upsample = min(15, (int)(UPSAMPLE_FACTOR * (r / camPos.z))+1);
+		const float upfactor = 2.0f / (float)(upsample);
+
+        for (int v=0; v<upsample; ++v) {
+            for (int u=0; u<upsample; ++u) {
+                float3 point;
+                const ftl::cuda::fragment nearest = ftl::cuda::upsampled_point(camera.points, camera.normal, camera.colour,
+                    make_float2((float)x-1.0f+u*upfactor,(float)y-1.0f+v*upfactor));
+                //if (ftl::cuda::mls_point_surface<3>(camera.points, make_int2(x,y), nearest, point, 0.02f) < 0.001f) continue;
+                ftl::cuda::render_fragment(depth_in, normal_out, colour_out, params, nearest);
+            }
+        }
+    }
+}*/
+
+
+
+__global__ void dibr_normalise_kernel(
+        TextureObject<float4> colour_in,
+        TextureObject<uchar4> colour_out,
+        TextureObject<float4> normals,
+        TextureObject<float> contribs) {
+	const unsigned int x = blockIdx.x*blockDim.x + threadIdx.x;
+	const unsigned int y = blockIdx.y*blockDim.y + threadIdx.y;
+
+	if (x < colour_in.width() && y < colour_in.height()) {
+        const float4 colour = colour_in.tex2D((int)x,(int)y);
+        const float4 normal = normals.tex2D((int)x,(int)y);
+        const float contrib = contribs.tex2D((int)x,(int)y);
+
+        if (contrib > 0.0f) {
+            colour_out(x,y) = make_uchar4(colour.x / contrib, colour.y / contrib, colour.z / contrib, 0);
+            normals(x,y) = normal / contrib;
+        }
+	}
+}
+
+void ftl::cuda::dibr(const TextureObject<int> &depth_out,
+        const TextureObject<uchar4> &colour_out,
+        const TextureObject<float4> &normal_out,
+        const TextureObject<float> &confidence_out,
+		const TextureObject<float4> &tmp_colour,
+		const TextureObject<int> &tmp_depth,
+        int numcams,
+        const SplatParams &params,
+        cudaStream_t stream) {
+
+	const dim3 sgridSize((depth_out.width() + 2 - 1)/2, (depth_out.height() + T_PER_BLOCK - 1)/T_PER_BLOCK);
+	const dim3 sblockSize(2*WARP_SIZE, T_PER_BLOCK);
+    const dim3 gridSize((depth_out.width() + T_PER_BLOCK - 1)/T_PER_BLOCK, (depth_out.height() + T_PER_BLOCK - 1)/T_PER_BLOCK);
+    const dim3 blockSize(T_PER_BLOCK, T_PER_BLOCK);
+
+    clearColourKernel<<<gridSize, blockSize, 0, stream>>>(colour_out);
+    ftl::cuda::clear_to_zero(confidence_out, stream);
+    ftl::cuda::clear_colour(tmp_colour, stream);
+    ftl::cuda::clear_colour(normal_out, stream);
+	
+#ifdef _DEBUG
+	cudaSafeCall(cudaDeviceSynchronize());
+#endif
+
+	//int i=3;
+
+	bool noSplatting = params.m_flags & ftl::render::kNoSplatting;
+
+	// Pass 1, gather and upsample depth maps
+	if (params.m_flags & ftl::render::kNoUpsampling) {
+		for (int i=0; i<numcams; ++i)
+			dibr_merge_kernel<<<gridSize, blockSize, 0, stream>>>((noSplatting) ? depth_out : tmp_depth, i, params);
+	} else {
+		for (int i=0; i<numcams; ++i)
+			dibr_merge_upsample_kernel<<<sgridSize, sblockSize, 0, stream>>>((noSplatting) ? depth_out : tmp_depth, i, params);
+	}
+
+	if (noSplatting) {
+		// Pass 3, accumulate all point contributions to pixels
+		for (int i=0; i<numcams; ++i)
+        	dibr_attribute_contrib_kernel<<<sgridSize, sblockSize, 0, stream>>>(depth_out, tmp_colour, normal_out, confidence_out, i, params);
+	} else {
+		// Pass 2
+		dibr_visibility_principal_kernel2<<<sgridSize, sblockSize, 0, stream>>>(tmp_depth, depth_out, params);
+
+		// Pass 3, accumulate all point contributions to pixels
+		for (int i=0; i<numcams; ++i)
+        	dibr_attribute_contrib_kernel<<<sgridSize, sblockSize, 0, stream>>>(depth_out, tmp_colour, normal_out, confidence_out, i, params);
+	}
+	// Pass 2
+	//dibr_visibility_principal_kernel2<<<sgridSize, sblockSize, 0, stream>>>(tmp_depth, depth_out, params);
+
+    // Pass 2, merge a depth map from each camera.
+	//for (int i=0; i<numcams; ++i)
+    //    dibr_visibility_principal_kernel<<<sgridSize, sblockSize, 0, stream>>>(depth_out, i, params);
+
+    // Pass 4, normalise contributions
+    dibr_normalise_kernel<<<gridSize, blockSize, 0, stream>>>(tmp_colour, colour_out, normal_out, confidence_out);
+
+	cudaSafeCall( cudaGetLastError() );
+	
+#ifdef _DEBUG
+	cudaSafeCall(cudaDeviceSynchronize());
+#endif
+}
+
+void ftl::cuda::dibr_raw(const TextureObject<int> &depth_out,
+    	int numcams, const SplatParams &params, cudaStream_t stream) {
+
+    const dim3 gridSize((depth_out.width() + T_PER_BLOCK - 1)/T_PER_BLOCK, (depth_out.height() + T_PER_BLOCK - 1)/T_PER_BLOCK);
+    const dim3 blockSize(T_PER_BLOCK, T_PER_BLOCK);
+	
+#ifdef _DEBUG
+	cudaSafeCall(cudaDeviceSynchronize());
+#endif
+
+	//dibr_depthmap_direct_kernel<<<gridSize, blockSize, 0, stream>>>(depth_out, numcams, params);
+	cudaSafeCall( cudaGetLastError() );
+	
+#ifdef _DEBUG
+	cudaSafeCall(cudaDeviceSynchronize());
+#endif
+}
+
diff --git a/components/renderers/cpp/src/display.cpp b/components/renderers/cpp/src/display.cpp
deleted file mode 100644
index 0f838df751d0c0a315f4d0acca9798d2d7741066..0000000000000000000000000000000000000000
--- a/components/renderers/cpp/src/display.cpp
+++ /dev/null
@@ -1,287 +0,0 @@
-/*
- * Copyright 2019 Nicolas Pope
- */
-
-#include <loguru.hpp>
-
-#include <ftl/display.hpp>
-#include <ftl/utility/opencv_to_pcl.hpp>
-
-using ftl::Display;
-using cv::Mat;
-using cv::Vec3f;
-
-Display::Display(nlohmann::json &config, std::string name) : ftl::Configurable(config) {
-	name_ = name;
-#if defined HAVE_VIZ
-	window_ = new cv::viz::Viz3d("FTL: " + name);
-	window_->setBackgroundColor(cv::viz::Color::white());
-#endif  // HAVE_VIZ
-
-	//cv::namedWindow("Image", cv::WINDOW_KEEPRATIO);
-
-#if defined HAVE_PCL
-	if (value("points", false)) {
-		pclviz_ = pcl::visualization::PCLVisualizer::Ptr(new pcl::visualization::PCLVisualizer ("FTL Cloud: " + name));
-		pclviz_->setBackgroundColor (255, 255, 255);
-		pclviz_->addCoordinateSystem (1.0);
-		pclviz_->setShowFPS(true);
-		pclviz_->initCameraParameters ();
-
-		pclviz_->registerPointPickingCallback(
-			[](const pcl::visualization::PointPickingEvent& event, void* viewer_void) {
-				if (event.getPointIndex () == -1) return;
-				float x, y, z;
-				event.getPoint(x, y, z);
-				LOG(INFO) << "( " << x << ", " << y << ", " << z << ")";
-			}, (void*) &pclviz_);
-		
-		pclviz_->registerKeyboardCallback (
-			[](const pcl::visualization::KeyboardEvent &event, void* viewer_void) {
-				auto viewer = *static_cast<pcl::visualization::PCLVisualizer::Ptr*>(viewer_void);
-				pcl::visualization::Camera cam;
-				viewer->getCameraParameters(cam);
-
-				Eigen::Vector3f pos(cam.pos[0], cam.pos[1], cam.pos[2]);
-				Eigen::Vector3f focal(cam.focal[0], cam.focal[1], cam.focal[2]);
-				Eigen::Vector3f dir = focal - pos; //.normalize();
-				dir.normalize();
-
-				const float speed = 40.0f;
-
-				if (event.getKeySym() == "Up") {
-					pos += speed*dir;
-					focal += speed*dir;
-				} else if (event.getKeySym() == "Down") {
-					pos -= speed*dir;
-					focal -= speed*dir;
-				} else if (event.getKeySym() == "Left") {
-					Eigen::Matrix3f m = Eigen::AngleAxisf(-0.5f*M_PI, Eigen::Vector3f::UnitY()).toRotationMatrix();
-					dir = m*dir;
-					pos += speed*dir;
-					focal += speed*dir;
-				} else if (event.getKeySym() == "Right") {
-					Eigen::Matrix3f m = Eigen::AngleAxisf(0.5f*M_PI, Eigen::Vector3f::UnitY()).toRotationMatrix();
-					dir = m*dir;
-					pos += speed*dir;
-					focal += speed*dir;
-				}
-
-
-				cam.pos[0] = pos[0];
-				cam.pos[1] = pos[1];
-				cam.pos[2] = pos[2];
-				cam.focal[0] = focal[0];
-				cam.focal[1] = focal[1];
-				cam.focal[2] = focal[2];
-				viewer->setCameraParameters(cam);
-
-			}, (void*)&pclviz_);
-	}
-#endif  // HAVE_PCL
-
-	active_ = true;
-}
-
-Display::~Display() {
-	#if defined HAVE_VIZ
-	delete window_;
-	#endif  // HAVE_VIZ
-}
-
-#ifdef HAVE_PCL
-/**
- * Convert an OpenCV RGB and Depth Mats to a PCL XYZRGB point cloud.
- */
-static pcl::PointCloud<pcl::PointXYZRGB>::Ptr rgbdToPointXYZ(const cv::Mat &rgb, const cv::Mat &depth, const ftl::rgbd::Camera &p) {
-	const double CX = p.cx;
-	const double CY = p.cy;
-	const double FX = p.fx;
-	const double FY = p.fy;
-
-	pcl::PointCloud<pcl::PointXYZRGB>::Ptr point_cloud_ptr(new pcl::PointCloud<pcl::PointXYZRGB>);
-	point_cloud_ptr->width = rgb.cols * rgb.rows;
-	point_cloud_ptr->height = 1;
-
-	for(int i=0;i<rgb.rows;i++) {
-		const float *sptr = depth.ptr<float>(i);
-		for(int j=0;j<rgb.cols;j++) {
-			float d = sptr[j] * 1000.0f;
-
-			pcl::PointXYZRGB point;
-			point.x = (((double)j + CX) / FX) * d;
-			point.y = (((double)i + CY) / FY) * d;
-			point.z = d;
-
-			if (point.x == INFINITY || point.y == INFINITY || point.z > 20000.0f || point.z < 0.04f) {
-				point.x = 0.0f; point.y = 0.0f; point.z = 0.0f;
-			}
-
-			cv::Point3_<uchar> prgb = rgb.at<cv::Point3_<uchar>>(i, j);
-			uint32_t rgb = (static_cast<uint32_t>(prgb.z) << 16 | static_cast<uint32_t>(prgb.y) << 8 | static_cast<uint32_t>(prgb.x));
-			point.rgb = *reinterpret_cast<float*>(&rgb);
-
-			point_cloud_ptr -> points.push_back(point);
-		}
-	}
-
-	return point_cloud_ptr;
-}
-#endif  // HAVE_PCL
-
-bool Display::render(const cv::Mat &rgb, const cv::Mat &depth, const ftl::rgbd::Camera &p) {
-	Mat idepth;
-
-	if (value("points", false) && rgb.rows != 0) {
-#if defined HAVE_PCL
-		auto pc = rgbdToPointXYZ(rgb, depth, p);
-
-		pcl::visualization::PointCloudColorHandlerRGBField<pcl::PointXYZRGB> rgb(pc);
-		if (!pclviz_->updatePointCloud<pcl::PointXYZRGB> (pc, rgb, "reconstruction")) {
-			pclviz_->addPointCloud<pcl::PointXYZRGB> (pc, rgb, "reconstruction");
-			pclviz_->setCameraPosition(-878.0, -71.0, -2315.0, -0.1, -0.99, 0.068, 0.0, -1.0, 0.0);
-			pclviz_->setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 2, "reconstruction");
-		}
-#elif defined HAVE_VIZ
-		//cv::Mat Q_32F;
-		//calibrate_.getQ().convertTo(Q_32F, CV_32F);
-		/*cv::Mat_<cv::Vec3f> XYZ(depth.rows, depth.cols);   // Output point cloud
-		reprojectImageTo3D(depth+20.0f, XYZ, q, true);
-
-		// Remove all invalid pixels from point cloud
-		XYZ.setTo(Vec3f(NAN, NAN, NAN), depth == 0.0f);
-
-		cv::viz::WCloud cloud_widget = cv::viz::WCloud(XYZ, rgb);
-		cloud_widget.setRenderingProperty(cv::viz::POINT_SIZE, 2);
-
-		window_->showWidget("coosys", cv::viz::WCoordinateSystem());
-		window_->showWidget("Depth", cloud_widget);
-
-		//window_->spinOnce(40, true);*/
-
-#else  // HAVE_VIZ
-
-		LOG(ERROR) << "Need OpenCV Viz module to display points";
-
-#endif  // HAVE_VIZ
-	}
-
-	if (value("left", false)) {
-		if (value("crosshair", false)) {
-			cv::line(rgb, cv::Point(0, rgb.rows/2), cv::Point(rgb.cols-1, rgb.rows/2), cv::Scalar(0,0,255), 1);
-            cv::line(rgb, cv::Point(rgb.cols/2, 0), cv::Point(rgb.cols/2, rgb.rows-1), cv::Scalar(0,0,255), 1);
-		}
-		cv::namedWindow("Left: " + name_, cv::WINDOW_KEEPRATIO);
-		cv::imshow("Left: " + name_, rgb);
-	}
-	if (value("right", false)) {
-		/*if (config_["crosshair"]) {
-			cv::line(rgbr, cv::Point(0, rgbr.rows/2), cv::Point(rgbr.cols-1, rgbr.rows/2), cv::Scalar(0,0,255), 1);
-            cv::line(rgbr, cv::Point(rgbr.cols/2, 0), cv::Point(rgbr.cols/2, rgbr.rows-1), cv::Scalar(0,0,255), 1);
-		}
-		cv::namedWindow("Right: " + name_, cv::WINDOW_KEEPRATIO);
-		cv::imshow("Right: " + name_, rgbr);*/
-	}
-
-	if (value("disparity", false)) {
-		/*Mat depth32F = (focal * (float)l.cols * base_line) / depth;
-		normalize(depth32F, depth32F, 0, 255, NORM_MINMAX, CV_8U);
-		cv::imshow("Depth", depth32F);
-		if(cv::waitKey(10) == 27){
-	        //exit if ESC is pressed
-	       	active_ = false;
-	    }*/
-    } else if (value("depth", false)) {
-		if (value("flip_vert", false)) {
-			cv::flip(depth, idepth, 0);
-		} else {
-			idepth = depth;
-		}
-
-    	idepth.convertTo(idepth, CV_8U, 255.0f / 10.0f);  // TODO(nick)
-
-    	applyColorMap(idepth, idepth, cv::COLORMAP_JET);
-		cv::imshow("Depth: " + name_, idepth);
-		//if(cv::waitKey(40) == 27) {
-	        // exit if ESC is pressed
-	    //    active_ = false;
-	    //}
-    }
-
-	return true;
-}
-
-#if defined HAVE_PCL
-bool Display::render(pcl::PointCloud<pcl::PointXYZRGB>::ConstPtr pc) {	
-	pcl::visualization::PointCloudColorHandlerRGBField<pcl::PointXYZRGB> rgb(pc);
-	if (pclviz_ && !pclviz_->updatePointCloud<pcl::PointXYZRGB> (pc, rgb, "reconstruction")) {
-		pclviz_->addPointCloud<pcl::PointXYZRGB> (pc, rgb, "reconstruction");
-		pclviz_->setCameraPosition(-878.0, -71.0, -2315.0, -0.1, -0.99, 0.068, 0.0, -1.0, 0.0);
-		pclviz_->setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 2, "reconstruction");
-	}
-	return true;
-}
-#endif  // HAVE_PCL
-bool Display::render(const cv::Mat &img, style_t s) {
-	if (s == STYLE_NORMAL) {
-		cv::imshow("Image", img);
-	} else if (s == STYLE_DISPARITY) {
-		Mat idepth;
-
-		if (value("flip_vert", false)) {
-			cv::flip(img, idepth, 0);
-		} else {
-			idepth = img;
-		}
-
-    	idepth.convertTo(idepth, CV_8U, 255.0f / 256.0f);
-
-    	applyColorMap(idepth, idepth, cv::COLORMAP_JET);
-		cv::imshow("Disparity", idepth);
-	}
-
-	return true;
-}
-
-bool Display::hasDisplays() {
-	return value("depth", false) || value("left", false) || value("right", false) || value("points", false);
-}
-
-void Display::wait(int ms) {
-	if (value("points", false)) {
-		#if defined HAVE_PCL
-		if (pclviz_) pclviz_->spinOnce(20);
-		#elif defined HAVE_VIZ
-		window_->spinOnce(1, true);
-		#endif  // HAVE_VIZ
-	}
-	
-	if (value("depth", false) || value("left", false) || value("right", false)) {
-		while (true) {
-			int key = cv::waitKey(ms);
-
-			if(key == 27) {
-				// exit if ESC is pressed
-				active_ = false;
-			} else if (key == -1) {
-				return;
-			} else {
-				ms = 1;
-				for (auto &h : key_handlers_) {
-					h(key);
-				}
-			}
-		}
-	}
-}
-
-bool Display::active() const {
-	#if defined HAVE_PCL
-	return active_ && (!pclviz_ || !pclviz_->wasStopped());
-	#elif defined HAVE_VIZ
-	return active_ && !window_->wasStopped();
-	#else
-	return active_;
-	#endif
-}
-
diff --git a/components/renderers/cpp/src/mls_cuda.hpp b/components/renderers/cpp/src/mls_cuda.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..7f2cf1d88d92e319ceef96274626477d6e2b839c
--- /dev/null
+++ b/components/renderers/cpp/src/mls_cuda.hpp
@@ -0,0 +1,399 @@
+#ifndef _FTL_MLS_CUDA_HPP_
+#define _FTL_MLS_CUDA_HPP_
+
+#include <ftl/cuda_util.hpp>
+#include <ftl/cuda_common.hpp>
+#include <ftl/cuda_matrix_util.hpp>
+#include "splat_params.hpp"
+
+__device__ inline float3 make_float3(const uchar4 &c) {
+	return make_float3((float)c.x,(float)c.y,(float)c.z);
+}
+
+__device__ inline uchar4 make_uchar4(const float3 &c) {
+	return make_uchar4(c.x,c.y,c.z,255);
+}
+
+namespace ftl {
+namespace cuda {
+
+/*
+ * Guennebaud, G.; Gross, M. Algebraic point set surfaces. ACMTransactions on Graphics Vol. 26, No. 3, Article No. 23, 2007.
+ * Used in: FusionMLS: Highly dynamic 3D reconstruction with consumer-grade RGB-D cameras
+ *     r = distance between points
+ *     h = smoothing parameter in meters (default 4cm)
+ */
+__device__ inline float spatialWeighting(float r, float h) {
+	if (r >= h) return 0.0f;
+	float rh = r / h;
+	rh = 1.0f - rh*rh;
+	return rh*rh*rh*rh;
+}
+
+__device__ float colourWeighting(float c);
+
+struct fragment {
+	float3 point;
+	float3 normal;
+	uchar4 colour;
+};
+
+__device__ inline float3 upsampled_point(cudaTextureObject_t pointset, const float2 &uv) {
+	float3 R = make_float3(0.0f, 0.0f, 0.0f);
+	const float3 P1 = make_float3(tex2D<float4>(pointset, int(uv.x), int(uv.y)));
+	const float D1 = 1.0f - length(uv - make_float2(int(uv.x), int(uv.y)));
+	R += D1 * P1;
+
+	const float3 P2 = make_float3(tex2D<float4>(pointset, int(uv.x), int(uv.y+1.0f)));
+	const float D2 = 1.0f - length(uv - make_float2(int(uv.x), int(uv.y+1.0f)));
+	R += D2 * P2;
+
+	const float3 P3 = make_float3(tex2D<float4>(pointset, int(uv.x+1.0f), int(uv.y)));
+	const float D3 = 1.0f - length(uv - make_float2(int(uv.x+1.0f), int(uv.y)));
+	R += D3 * P3;
+
+	const float3 P4 = make_float3(tex2D<float4>(pointset, int(uv.x+1.0f), int(uv.y+1.0f)));
+	const float D4 = 1.0f - length(uv - make_float2(int(uv.x+1.0f), int(uv.y+1.0f)));
+	R += D4 * P4;
+
+	// R is the centroid of surrounding points.
+	R /= (D1+D2+D3+D4);
+
+	// FIXME: Should not use centroid but instead sample the surface at this point
+	// Use plane estimate at point to get "centroid" and then do the spatial weighted sample?
+	return R;
+}
+
+__device__ inline fragment upsampled_point(cudaTextureObject_t pointset,
+		cudaTextureObject_t normals, cudaTextureObject_t colours, const float2 &uv) {
+	float3 R = make_float3(0.0f, 0.0f, 0.0f);
+	float3 N = make_float3(0.0f, 0.0f, 0.0f);
+	float3 C = make_float3(0.0f, 0.0f, 0.0f);
+
+	// TODO:(Nick) Don't upsample points if distance is too great
+
+	const float3 P1 = make_float3(tex2D<float4>(pointset, int(uv.x), int(uv.y)));
+	const float D1 = 1.0f - length(uv - make_float2(int(uv.x), int(uv.y)));
+	R += D1 * P1;
+	N += D1 * make_float3(tex2D<float4>(normals, int(uv.x), int(uv.y)));
+	C += D1 * make_float3(tex2D<uchar4>(colours, int(uv.x), int(uv.y)));
+
+	const float3 P2 = make_float3(tex2D<float4>(pointset, int(uv.x), int(uv.y+1.0f)));
+	const float D2 = 1.0f - length(uv - make_float2(int(uv.x), int(uv.y+1.0f)));
+	R += D2 * P2;
+	N += D2 * make_float3(tex2D<float4>(normals, int(uv.x), int(uv.y+1.0f)));
+	C += D2 * make_float3(tex2D<uchar4>(colours, int(uv.x), int(uv.y+1.0f)));
+
+	const float3 P3 = make_float3(tex2D<float4>(pointset, int(uv.x+1.0f), int(uv.y)));
+	const float D3 = 1.0f - length(uv - make_float2(int(uv.x+1.0f), int(uv.y)));
+	R += D3 * P3;
+	N += D3 * make_float3(tex2D<float4>(normals, int(uv.x+1.0f), int(uv.y)));
+	C += D3 * make_float3(tex2D<uchar4>(colours, int(uv.x+1.0f), int(uv.y)));
+
+	const float3 P4 = make_float3(tex2D<float4>(pointset, int(uv.x+1.0f), int(uv.y+1.0f)));
+	const float D4 = 1.0f - length(uv - make_float2(int(uv.x+1.0f), int(uv.y+1.0f)));
+	R += D4 * P4;
+	N += D4 * make_float3(tex2D<float4>(normals, int(uv.x+1.0f), int(uv.y+1.0f)));
+	C += D4 * make_float3(tex2D<uchar4>(colours, int(uv.x+1.0f), int(uv.y+1.0f)));
+
+	return {R / (D1+D2+D3+D4), N / (D1+D2+D3+D4), make_uchar4(C / (D1+D2+D3+D4))};
+}
+
+__device__ inline void render_depth(ftl::cuda::TextureObject<int> &depth, ftl::render::SplatParams &params, const float3 &worldPos) {
+	const float3 camPos = params.m_viewMatrix * worldPos;
+	const float d = camPos.z;
+	if (d < params.camera.m_sensorDepthWorldMin) return;
+
+	const float2 screenPosf = params.camera.cameraToKinectScreenFloat(camPos);
+	const uint2 screenPos = make_uint2(make_int2(screenPosf));
+
+	const unsigned int cx = screenPos.x;
+	const unsigned int cy = screenPos.y;
+
+	if (cx < depth.width() && cy < depth.height()) {
+		atomicMin(&depth(cx,cy), d * 1000.0f);
+	}
+}
+
+__device__ inline void render_fragment(
+		ftl::cuda::TextureObject<int> &depth_in,
+		ftl::cuda::TextureObject<float4> &normal_out,
+		ftl::cuda::TextureObject<uchar4> &colour_out,
+		ftl::render::SplatParams &params, const fragment &frag) {
+	const float3 camPos = params.m_viewMatrix * frag.point;
+	const float d = camPos.z;
+	if (d < params.camera.m_sensorDepthWorldMin) return;
+
+	const float2 screenPosf = params.camera.cameraToKinectScreenFloat(camPos);
+	const uint2 screenPos = make_uint2(make_int2(screenPosf));
+
+	const unsigned int cx = screenPos.x;
+	const unsigned int cy = screenPos.y;
+
+	if (cx < depth_in.width() && cy < depth_in.height()) {
+		if (depth_in(cx,cy) == (int)(d * 1000.0f)) {
+			colour_out(cx,cy) = frag.colour;
+			normal_out(cx,cy) = make_float4(frag.normal, 0.0f);
+		}
+	}
+}
+
+/**
+ * Estimate the point set surface location near to a given point.
+ */
+template <int R>
+__device__ float3 screen_centroid(
+		cudaTextureObject_t pointset,
+		const float2 &suv,
+		const int2 &uv,
+		const ftl::render::SplatParams &params,
+		float smoothing) {
+
+	float3 pos = make_float3(0.0f, 0.0f, 0.0f);
+	float weights = 0.0f;
+
+	//#pragma unroll
+	for (int v=-R; v<=R; ++v) {
+		for (int u=-R; u<=R; ++u) {
+			//if (uv.x+u >= 0 && uv.x+u < pointset.width() && uv.y+v >= 0 && uv.y+v < pointset.height()) {
+				const float3 samplePoint = params.m_viewMatrix * make_float3(tex2D<float4>(pointset, uv.x+u, uv.y+v));
+				const float weight = ftl::cuda::spatialWeighting(length(suv - params.camera.cameraToKinectScreenFloat(samplePoint)), smoothing);
+				pos += weight*samplePoint;
+				weights += weight;
+			//}
+		}
+	}
+
+	if (weights > 0.0f) pos = pos / weights;
+	return pos;
+}
+
+/**
+ * Estimate a point set surface point from an existing point in the set.
+ */
+template <int R>
+__device__ float mls_point_surface(
+		cudaTextureObject_t pointset,
+		const int2 &uv,
+		float3 &estPoint,
+		float smoothing) {
+
+	float3 pos = make_float3(0.0f, 0.0f, 0.0f);
+	float weights = 0.0f;
+	const float3 nearPoint = make_float3(tex2D<float4>(pointset, uv.x, uv.y));
+
+	//#pragma unroll
+	for (int v=-R; v<=R; ++v) {
+		for (int u=-R; u<=R; ++u) {
+			//if (uv.x+u >= 0 && uv.x+u < pointset.width() && uv.y+v >= 0 && uv.y+v < pointset.height()) {
+				const float3 samplePoint = make_float3(tex2D<float4>(pointset, uv.x+u, uv.y+v));
+				const float weight = ftl::cuda::spatialWeighting(length(nearPoint - samplePoint), smoothing);
+				pos += weight*samplePoint;
+				weights += weight;
+			//}
+		}
+	}
+
+	if (weights > 0.0f) estPoint = pos / weights;
+	return weights;
+};
+
+/**
+ * Estimate the point set surface location near to a given point.
+ */
+template <int R>
+__device__ float mls_point_surface(
+		cudaTextureObject_t pointset,
+		const int2 &uv,
+		const float3 &nearPoint,
+		float3 &estPoint,
+		float smoothing) {
+
+	float3 pos = make_float3(0.0f, 0.0f, 0.0f);
+	float weights = 0.0f;
+
+	//#pragma unroll
+	for (int v=-R; v<=R; ++v) {
+		for (int u=-R; u<=R; ++u) {
+			//if (uv.x+u >= 0 && uv.x+u < pointset.width() && uv.y+v >= 0 && uv.y+v < pointset.height()) {
+				const float3 samplePoint = make_float3(tex2D<float4>(pointset, uv.x+u, uv.y+v));
+				const float weight = ftl::cuda::spatialWeighting(length(nearPoint - samplePoint), smoothing);
+				pos += weight*samplePoint;
+				weights += weight;
+			//}
+		}
+	}
+
+	if (weights > 0.0f) estPoint = pos / weights;
+	return weights;
+}
+
+/**
+ * Calculate the point sample energy.
+ */
+template <int R>
+__device__ float mls_point_energy(
+		cudaTextureObject_t pointset,
+		const int2 &uv,
+		const float3 &nearPoint,
+		float smoothing) {
+
+	float weights = 0.0f;
+
+	//#pragma unroll
+	for (int v=-R; v<=R; ++v) {
+		for (int u=-R; u<=R; ++u) {
+			//if (uv.x+u >= 0 && uv.x+u < pointset.width() && uv.y+v >= 0 && uv.y+v < pointset.height()) {
+				const float3 samplePoint = make_float3(tex2D<float4>(pointset, uv.x+u, uv.y+v));
+				const float weight = ftl::cuda::spatialWeighting(length(nearPoint - samplePoint), smoothing);
+				weights += weight;
+			//}
+		}
+	}
+
+	return weights;
+}
+
+/**
+ * Calculate the point sample energy.
+ */
+template <int M>
+__device__ float mls_point_energy(
+		const float3 (&pointset)[M],
+		const float3 &nearPoint,
+		float smoothing) {
+
+	float weights = 0.0f;
+
+	//#pragma unroll
+	for (int i=0; i<M; ++i) {
+		const float3 samplePoint = pointset[i];
+		const float weight = ftl::cuda::spatialWeighting(length(nearPoint - samplePoint), smoothing);
+		weights += weight;
+	}
+
+	return weights;
+}
+
+/**
+ * Calculate the point sample energy.
+ */
+template <int M>
+__device__ float mls_point_energy(
+		const float3 (&pointset)[M],
+		const float3 &nearPoint,
+		unsigned int N,
+		float smoothing) {
+
+	float weights = 0.0f;
+
+	//#pragma unroll
+	for (int i=0; i<N; ++i) {
+		const float3 samplePoint = pointset[i];
+		const float weight = ftl::cuda::spatialWeighting(length(nearPoint - samplePoint), smoothing);
+		weights += weight;
+	}
+
+	return weights;
+}
+
+/**
+ * Estimate a point set surface location near an existing and return also
+ * an estimate of the normal and colour of that point.
+ */
+template <int R>
+__device__ float mls_point_surface(
+		cudaTextureObject_t pointset,
+		cudaTextureObject_t normalset,
+		cudaTextureObject_t colourset,
+		const int2 &uv,
+		float3 &estPoint,
+		float3 &estNormal,
+		uchar4 &estColour,
+		float smoothing) {
+
+	float3 pos = make_float3(0.0f, 0.0f, 0.0f);
+	float3 normal = make_float3(0.0f, 0.0f, 0.0f);
+	float3 colour = make_float3(0.0f, 0.0f, 0.0f);
+	float weights = 0.0f;
+	const float3 nearPoint = make_float3(tex2D<float4>(pointset, uv.x, uv.y));
+
+	//#pragma unroll
+	for (int v=-R; v<=R; ++v) {
+		for (int u=-R; u<=R; ++u) {
+			//if (uv.x+u >= 0 && uv.x+u < pointset.width() && uv.y+v >= 0 && uv.y+v < pointset.height()) {
+				const float3 samplePoint = make_float3(tex2D<float4>(pointset, uv.x+u, uv.y+v));
+				const float weight = spatialWeighting(length(nearPoint - samplePoint), smoothing);
+
+				if (weight > 0.0f) {
+					pos += weight*samplePoint;
+					weights += weight;
+
+					normal += weight * make_float3(tex2D<float4>(normalset, uv.x+u, uv.y+v));
+					const uchar4 c = tex2D<uchar4>(colourset, uv.x+u, uv.y+v);
+					colour += weight * make_float3(c.x, c.y, c.z);
+				}
+			//}
+		}
+	}
+
+	if (weights > 0.0f) {
+		estPoint = pos / weights;
+		estNormal = normal / weights;
+		estColour = make_uchar4(colour.x / weights, colour.y / weights, colour.z / weights, 255);
+	}
+	return weights;
+}
+
+/**
+ * Estimate a point set surface location near a given point and return also
+ * an estimate of the normal and colour of that point.
+ */
+template <int R>
+__device__ float mls_point_surface(
+		cudaTextureObject_t pointset,
+		cudaTextureObject_t normalset,
+		cudaTextureObject_t colourset,
+		const int2 &uv,
+		const float3 &nearPoint,
+		float3 &estPoint,
+		float3 &estNormal,
+		uchar4 &estColour,
+		float smoothing) {
+
+	float3 pos = make_float3(0.0f, 0.0f, 0.0f);
+	float3 normal = make_float3(0.0f, 0.0f, 0.0f);
+	float3 colour = make_float3(0.0f, 0.0f, 0.0f);
+	float weights = 0.0f;
+
+	//#pragma unroll
+	for (int v=-R; v<=R; ++v) {
+		for (int u=-R; u<=R; ++u) {
+			//if (uv.x+u >= 0 && uv.x+u < pointset.width() && uv.y+v >= 0 && uv.y+v < pointset.height()) {
+				const float3 samplePoint = make_float3(tex2D<float4>(pointset, uv.x+u, uv.y+v));
+				const float weight = spatialWeighting(length(nearPoint - samplePoint), smoothing);
+
+				if (weight > 0.0f) {
+					pos += weight*samplePoint;
+					weights += weight;
+
+					normal += weight * make_float3(tex2D<float4>(normalset, uv.x+u, uv.y+v));
+					const uchar4 c = tex2D<uchar4>(colourset, uv.x+u, uv.y+v);
+					colour += weight * make_float3(c.x, c.y, c.z);
+				}
+			//}
+		}
+	}
+
+	if (weights > 0.0f) {
+		estPoint = pos / weights;
+		estNormal = normal / weights;
+		estColour = make_uchar4(colour.x / weights, colour.y / weights, colour.z / weights, 255);
+	}
+	return weights;
+}
+
+}
+}
+
+#endif  // _FTL_MLS_CUDA_HPP_
diff --git a/components/renderers/cpp/src/points.cu b/components/renderers/cpp/src/points.cu
new file mode 100644
index 0000000000000000000000000000000000000000..39764e4c8aba523caf2758262d9f41f8782ac9dc
--- /dev/null
+++ b/components/renderers/cpp/src/points.cu
@@ -0,0 +1,28 @@
+#include <ftl/cuda/points.hpp>
+
+#define T_PER_BLOCK 8
+
+__global__ void point_cloud_kernel(ftl::cuda::TextureObject<float4> output, ftl::cuda::TextureObject<float> depth, ftl::rgbd::Camera params, float4x4 pose)
+{
+	const unsigned int x = blockIdx.x*blockDim.x + threadIdx.x;
+	const unsigned int y = blockIdx.y*blockDim.y + threadIdx.y;
+
+	if (x < params.width && y < params.height) {
+		float d = depth.tex2D((int)x, (int)y);
+
+		output(x,y) = (d >= params.minDepth && d <= params.maxDepth) ?
+			make_float4(pose * params.screenToCam(x, y, d), 0.0f) :
+			make_float4(MINF, MINF, MINF, MINF);
+	}
+}
+
+void ftl::cuda::point_cloud(ftl::cuda::TextureObject<float4> &output, ftl::cuda::TextureObject<float> &depth, const ftl::rgbd::Camera &params, const float4x4 &pose, cudaStream_t stream) {
+	const dim3 gridSize((params.width + T_PER_BLOCK - 1)/T_PER_BLOCK, (params.height + T_PER_BLOCK - 1)/T_PER_BLOCK);
+	const dim3 blockSize(T_PER_BLOCK, T_PER_BLOCK);
+
+	point_cloud_kernel<<<gridSize, blockSize, 0, stream>>>(output, depth, params, pose);
+
+#ifdef _DEBUG
+	cudaSafeCall(cudaDeviceSynchronize());
+#endif
+}
diff --git a/components/renderers/cpp/src/rgbd_display.cpp b/components/renderers/cpp/src/rgbd_display.cpp
deleted file mode 100644
index a0d79f8aeeb42b0a0a1fd281c5f3e9065b43780c..0000000000000000000000000000000000000000
--- a/components/renderers/cpp/src/rgbd_display.cpp
+++ /dev/null
@@ -1,129 +0,0 @@
-#include <ftl/rgbd_display.hpp>
-#include <opencv2/opencv.hpp>
-
-using ftl::rgbd::Source;
-using ftl::rgbd::Display;
-using std::string;
-using cv::Mat;
-
-int Display::viewcount__ = 0;
-
-template<class T>
-Eigen::Matrix<T,4,4> lookAt
-(
-	Eigen::Matrix<T,3,1> const & eye,
-	Eigen::Matrix<T,3,1> const & center,
-	Eigen::Matrix<T,3,1> const & up
-)
-{
-	typedef Eigen::Matrix<T,4,4> Matrix4;
-	typedef Eigen::Matrix<T,3,1> Vector3;
-
-	Vector3 f = (center - eye).normalized();
-	Vector3 u = up.normalized();
-	Vector3 s = f.cross(u).normalized();
-	u = s.cross(f);
-
-	Matrix4 res;
-	res <<	s.x(),s.y(),s.z(),-s.dot(eye),
-			u.x(),u.y(),u.z(),-u.dot(eye),
-			-f.x(),-f.y(),-f.z(),f.dot(eye),
-			0,0,0,1;
-
-	return res;
-}
-
-static void setMouseAction(const std::string& winName, const MouseAction &action)
-{
-  cv::setMouseCallback(winName,
-                       [] (int event, int x, int y, int flags, void* userdata) {
-    (*(MouseAction*)userdata)(event, x, y, flags);
-  }, (void*)&action);
-}
-
-Display::Display(nlohmann::json &config) : ftl::Configurable(config) {
-	name_ = value("name", string("View [")+std::to_string(viewcount__)+string("]"));
-	viewcount__++;
-
-	init();
-}
-
-Display::Display(nlohmann::json &config, Source *source)
-		: ftl::Configurable(config) {
-	name_ = value("name", string("View [")+std::to_string(viewcount__)+string("]"));
-	viewcount__++;
-	init();
-}
-
-Display::~Display() {
-
-}
-
-void Display::init() {
-	active_ = true;
-	source_ = nullptr;
-	cv::namedWindow(name_, cv::WINDOW_KEEPRATIO);
-
-	eye_ = Eigen::Vector3d(0.0, 0.0, 0.0);
-	centre_ = Eigen::Vector3d(0.0, 0.0, -4.0);
-	up_ = Eigen::Vector3d(0,1.0,0);
-	lookPoint_ = Eigen::Vector3d(0.0,0.0,-4.0);
-	lerpSpeed_ = 0.4f;
-
-	// Keyboard camera controls
-	onKey([this](int key) {
-		//LOG(INFO) << "Key = " << key;
-		if (key == 81 || key == 83) {
-			Eigen::Quaternion<double> q;  q = Eigen::AngleAxis<double>((key == 81) ? 0.01 : -0.01, up_);
-			eye_ = (q * (eye_ - centre_)) + centre_;
-		} else if (key == 84 || key == 82) {
-			double scalar = (key == 84) ? 0.99 : 1.01;
-			eye_ = ((eye_ - centre_) * scalar) + centre_;
-		}
-	});
-
-	// TODO(Nick) Calculate "camera" properties of viewport.
-	mouseaction_ = [this]( int event, int ux, int uy, int) {
-		//LOG(INFO) << "Mouse " << ux << "," << uy;
-		if (event == 1 && source_) {   // click
-			Eigen::Vector4d camPos = source_->point(ux,uy);
-			camPos *= -1.0f;
-			Eigen::Vector4d worldPos =  source_->getPose() * camPos;
-			lookPoint_ = Eigen::Vector3d(worldPos[0],worldPos[1],worldPos[2]);
-			LOG(INFO) << "Depth at click = " << -camPos[2];
-		}
-	};
-	::setMouseAction(name_, mouseaction_);
-}
-
-void Display::wait(int ms) {
-	while (true) {
-		int key = cv::waitKey(ms);
-
-		if(key == 27) {
-			// exit if ESC is pressed
-			active_ = false;
-		} else if (key == -1) {
-			return;
-		} else {
-			ms = 1;
-			for (auto &h : key_handlers_) {
-				h(key);
-			}
-		}
-	}
-}
-
-void Display::update() {
-	if (!source_) return;
-
-	centre_ += (lookPoint_ - centre_) * (lerpSpeed_ * 0.1f);
-	Eigen::Matrix4d viewPose = lookAt<double>(eye_,centre_,up_).inverse();
-	source_->setPose(viewPose);
-
-	Mat rgb, depth;
-	source_->grab();
-	source_->getFrames(rgb, depth);
-	if (rgb.rows > 0) cv::imshow(name_, rgb);
-	wait(1);
-}
diff --git a/components/renderers/cpp/src/splat_render.cpp b/components/renderers/cpp/src/splat_render.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..daf9f5f64c019d24ae9afb9f2e4540c59b722922
--- /dev/null
+++ b/components/renderers/cpp/src/splat_render.cpp
@@ -0,0 +1,172 @@
+#include <ftl/render/splat_render.hpp>
+#include <ftl/utility/matrix_conversion.hpp>
+#include "splatter_cuda.hpp"
+#include <ftl/cuda/points.hpp>
+
+#include <opencv2/core/cuda_stream_accessor.hpp>
+
+using ftl::render::Splatter;
+using ftl::rgbd::Channel;
+using ftl::rgbd::Channels;
+using ftl::rgbd::Format;
+using cv::cuda::GpuMat;
+
+Splatter::Splatter(nlohmann::json &config, ftl::rgbd::FrameSet *fs) : ftl::render::Renderer(config), scene_(fs) {
+
+}
+
+Splatter::~Splatter() {
+
+}
+
+bool Splatter::render(ftl::rgbd::VirtualSource *src, ftl::rgbd::Frame &out, cudaStream_t stream) {
+	SHARED_LOCK(scene_->mtx, lk);
+	if (!src->isReady()) return false;
+
+	const auto &camera = src->parameters();
+
+	//cudaSafeCall(cudaSetDevice(scene_->getCUDADevice()));
+
+	// Create all the required channels
+	out.create<GpuMat>(Channel::Depth, Format<float>(camera.width, camera.height));
+	out.create<GpuMat>(Channel::Colour, Format<uchar4>(camera.width, camera.height));
+
+	// FIXME: Use source resolutions, not virtual resolution
+	temp_.create<GpuMat>(Channel::Colour, Format<float4>(camera.width, camera.height));
+	temp_.create<GpuMat>(Channel::Colour2, Format<uchar4>(camera.width, camera.height));
+	temp_.create<GpuMat>(Channel::Contribution, Format<float>(camera.width, camera.height));
+	temp_.create<GpuMat>(Channel::Depth, Format<int>(camera.width, camera.height));
+	temp_.create<GpuMat>(Channel::Depth2, Format<int>(camera.width, camera.height));
+	temp_.create<GpuMat>(Channel::Normals, Format<float4>(camera.width, camera.height));
+
+	cv::cuda::Stream cvstream = cv::cuda::StreamAccessor::wrapStream(stream);
+
+	// Create buffers if they don't exist
+	/*if ((unsigned int)depth1_.width() != camera.width || (unsigned int)depth1_.height() != camera.height) {
+		depth1_ = ftl::cuda::TextureObject<int>(camera.width, camera.height);
+	}
+	if ((unsigned int)depth3_.width() != camera.width || (unsigned int)depth3_.height() != camera.height) {
+		depth3_ = ftl::cuda::TextureObject<int>(camera.width, camera.height);
+	}
+	if ((unsigned int)colour1_.width() != camera.width || (unsigned int)colour1_.height() != camera.height) {
+		colour1_ = ftl::cuda::TextureObject<uchar4>(camera.width, camera.height);
+	}
+	if ((unsigned int)colour_tmp_.width() != camera.width || (unsigned int)colour_tmp_.height() != camera.height) {
+		colour_tmp_ = ftl::cuda::TextureObject<float4>(camera.width, camera.height);
+	}
+	if ((unsigned int)normal1_.width() != camera.width || (unsigned int)normal1_.height() != camera.height) {
+		normal1_ = ftl::cuda::TextureObject<float4>(camera.width, camera.height);
+	}
+	if ((unsigned int)depth2_.width() != camera.width || (unsigned int)depth2_.height() != camera.height) {
+		depth2_ = ftl::cuda::TextureObject<float>(camera.width, camera.height);
+	}
+	if ((unsigned int)colour2_.width() != camera.width || (unsigned int)colour2_.height() != camera.height) {
+		colour2_ = ftl::cuda::TextureObject<uchar4>(camera.width, camera.height);
+	}*/
+
+	// Parameters object to pass to CUDA describing the camera
+	SplatParams params;
+	params.m_flags = 0;
+	if (src->value("splatting", true) == false) params.m_flags |= ftl::render::kNoSplatting;
+	if (src->value("upsampling", true) == false) params.m_flags |= ftl::render::kNoUpsampling;
+	if (src->value("texturing", true) == false) params.m_flags |= ftl::render::kNoTexturing;
+	params.m_viewMatrix = MatrixConversion::toCUDA(src->getPose().cast<float>().inverse());
+	params.m_viewMatrixInverse = MatrixConversion::toCUDA(src->getPose().cast<float>());
+	params.camera = camera;
+
+	// Clear all channels to 0 or max depth
+	temp_.get<GpuMat>(Channel::Depth).setTo(cv::Scalar(0x7FFFFFFF), cvstream);
+	temp_.get<GpuMat>(Channel::Depth2).setTo(cv::Scalar(0x7FFFFFFF), cvstream);
+	temp_.get<GpuMat>(Channel::Colour).setTo(cv::Scalar(0.0f,0.0f,0.0f,0.0f), cvstream);
+	temp_.get<GpuMat>(Channel::Contribution).setTo(cv::Scalar(0.0f), cvstream);
+	out.get<GpuMat>(Channel::Depth).setTo(cv::Scalar(1000.0f), cvstream);
+	out.get<GpuMat>(Channel::Colour).setTo(cv::Scalar(76,76,76), cvstream);
+
+	//LOG(INFO) << "Render ready: " << camera.width << "," << camera.height;
+
+	temp_.createTexture<int>(Channel::Depth);
+
+	// Render each camera into virtual view
+	for (size_t i=0; i<scene_->frames.size(); ++i) {
+		auto &f = scene_->frames[i];
+		auto *s = scene_->sources[i];
+
+		if (f.empty(Channel::Depth + Channel::Colour)) {
+			LOG(ERROR) << "Missing required channel";
+			continue;
+		}
+
+		// Needs to create points channel first?
+		if (!f.hasChannel(Channel::Points)) {
+			//LOG(INFO) << "Creating points... " << s->parameters().width;
+			
+			auto &t = f.createTexture<float4>(Channel::Points, Format<float4>(f.get<GpuMat>(Channel::Colour).size()));
+			auto pose = MatrixConversion::toCUDA(s->getPose().cast<float>()); //.inverse());
+			ftl::cuda::point_cloud(t, f.createTexture<float>(Channel::Depth), s->parameters(), pose, stream);
+
+			//LOG(INFO) << "POINTS Added";
+		}
+
+		ftl::cuda::dibr_merge(
+			f.createTexture<float4>(Channel::Points),
+			temp_.getTexture<int>(Channel::Depth),
+			params, stream
+		);
+
+		//LOG(INFO) << "DIBR DONE";
+	}
+
+	// TODO: Add the depth splatting step..
+
+	temp_.createTexture<float4>(Channel::Colour);
+	temp_.createTexture<float>(Channel::Contribution);
+
+	// Accumulate attribute contributions for each pixel
+	for (auto &f : scene_->frames) {
+		// Convert colour from BGR to BGRA if needed
+		if (f.get<GpuMat>(Channel::Colour).type() == CV_8UC3) {
+			// Convert to 4 channel colour
+			auto &col = f.get<GpuMat>(Channel::Colour);
+			GpuMat tmp(col.size(), CV_8UC4);
+			cv::cuda::swap(col, tmp);
+			cv::cuda::cvtColor(tmp,col, cv::COLOR_BGR2BGRA);
+		}
+	
+		ftl::cuda::dibr_attribute(
+			f.createTexture<uchar4>(Channel::Colour),
+			f.createTexture<float4>(Channel::Points),
+			temp_.getTexture<int>(Channel::Depth),
+			temp_.getTexture<float4>(Channel::Colour),
+			temp_.getTexture<float>(Channel::Contribution),
+			params, stream
+		);
+	}
+
+	// Normalise attribute contributions
+	ftl::cuda::dibr_normalise(
+		temp_.createTexture<float4>(Channel::Colour),
+		out.createTexture<uchar4>(Channel::Colour),
+		temp_.createTexture<float>(Channel::Contribution),
+		stream
+	);
+
+	Channel chan = src->getChannel();
+	if (chan == Channel::Depth) {
+		temp_.get<GpuMat>(Channel::Depth).convertTo(out.get<GpuMat>(Channel::Depth), CV_32F, 1.0f / 1000.0f, cvstream);
+	} else if (chan == Channel::Energy) {
+		cv::cuda::swap(temp_.get<GpuMat>(Channel::Energy), out.create<GpuMat>(Channel::Energy));
+	} else if (chan == Channel::Right) {
+		Eigen::Affine3f transform(Eigen::Translation3f(camera.baseline,0.0f,0.0f));
+		Eigen::Matrix4f matrix =  src->getPose().cast<float>() * transform.matrix();
+		params.m_viewMatrix = MatrixConversion::toCUDA(matrix.inverse());
+		params.m_viewMatrixInverse = MatrixConversion::toCUDA(matrix);
+
+		// TODO: Repeat rendering process...
+	}
+
+	return true;
+}
+
+//void Splatter::setOutputDevice(int device) {
+//	device_ = device;
+//}
diff --git a/components/renderers/cpp/src/splatter.cu b/components/renderers/cpp/src/splatter.cu
new file mode 100644
index 0000000000000000000000000000000000000000..c1b46fc1d5768dc15fe54bcf6e37f4655a076b56
--- /dev/null
+++ b/components/renderers/cpp/src/splatter.cu
@@ -0,0 +1,190 @@
+#include <ftl/render/splat_params.hpp>
+#include "splatter_cuda.hpp"
+#include <ftl/rgbd/camera.hpp>
+#include <ftl/cuda_common.hpp>
+
+#include <ftl/cuda/weighting.hpp>
+
+#define T_PER_BLOCK 8
+#define UPSAMPLE_FACTOR 1.8f
+#define WARP_SIZE 32
+#define DEPTH_THRESHOLD 0.05f
+#define UPSAMPLE_MAX 60
+#define MAX_ITERATIONS 32  // Note: Must be multiple of 32
+#define SPATIAL_SMOOTHING 0.005f
+
+using ftl::cuda::TextureObject;
+using ftl::render::SplatParams;
+
+/*
+ * Pass 1: Directly render each camera into virtual view but with no upsampling
+ * for sparse points.
+ */
+ __global__ void dibr_merge_kernel(TextureObject<float4> points, TextureObject<int> depth, SplatParams params) {
+	const int x = blockIdx.x*blockDim.x + threadIdx.x;
+	const int y = blockIdx.y*blockDim.y + threadIdx.y;
+
+	const float3 worldPos = make_float3(points.tex2D(x, y));
+	if (worldPos.x == MINF) return;
+
+    // Find the virtual screen position of current point
+	const float3 camPos = params.m_viewMatrix * worldPos;
+	if (camPos.z < params.camera.minDepth) return;
+	if (camPos.z > params.camera.maxDepth) return;
+
+	const float d = camPos.z;
+
+	const uint2 screenPos = params.camera.camToScreen<uint2>(camPos);
+	const unsigned int cx = screenPos.x;
+	const unsigned int cy = screenPos.y;
+	if (d > params.camera.minDepth && d < params.camera.maxDepth && cx < depth.width() && cy < depth.height()) {
+		// Transform estimated point to virtual cam space and output z
+		atomicMin(&depth(cx,cy), d * 1000.0f);
+	}
+}
+
+void ftl::cuda::dibr_merge(TextureObject<float4> &points, TextureObject<int> &depth, SplatParams params, cudaStream_t stream) {
+    const dim3 gridSize((depth.width() + T_PER_BLOCK - 1)/T_PER_BLOCK, (depth.height() + T_PER_BLOCK - 1)/T_PER_BLOCK);
+    const dim3 blockSize(T_PER_BLOCK, T_PER_BLOCK);
+
+    dibr_merge_kernel<<<gridSize, blockSize, 0, stream>>>(points, depth, params);
+    cudaSafeCall( cudaGetLastError() );
+}
+
+//==============================================================================
+
+__device__ inline float4 make_float4(const uchar4 &c) {
+    return make_float4(c.x,c.y,c.z,c.w);
+}
+
+
+#define ENERGY_THRESHOLD 0.1f
+#define SMOOTHING_MULTIPLIER_A 10.0f	// For surface search
+#define SMOOTHING_MULTIPLIER_B 4.0f		// For z contribution
+#define SMOOTHING_MULTIPLIER_C 4.0f		// For colour contribution
+
+/*
+ * Pass 2: Accumulate attribute contributions if the points pass a visibility test.
+ */
+__global__ void dibr_attribute_contrib_kernel(
+        TextureObject<uchar4> colour_in,    // Original colour image
+        TextureObject<float4> points,       // Original 3D points
+        TextureObject<int> depth_in,        // Virtual depth map
+        TextureObject<float4> colour_out,   // Accumulated output
+        //TextureObject<float4> normal_out,
+        TextureObject<float> contrib_out,
+        SplatParams params) {
+        
+	//const ftl::voxhash::DepthCameraCUDA &camera = c_cameras[cam];
+
+	const int tid = (threadIdx.x + threadIdx.y * blockDim.x);
+	//const int warp = tid / WARP_SIZE;
+	const int x = (blockIdx.x*blockDim.x + threadIdx.x) / WARP_SIZE;
+	const int y = blockIdx.y*blockDim.y + threadIdx.y;
+
+	const float3 worldPos = make_float3(points.tex2D(x, y));
+	//const float3 normal = make_float3(tex2D<float4>(camera.normal, x, y));
+	if (worldPos.x == MINF) return;
+    //const float r = (camera.poseInverse * worldPos).z / camera.params.fx;
+
+	const float3 camPos = params.m_viewMatrix * worldPos;
+	if (camPos.z < params.camera.minDepth) return;
+	if (camPos.z > params.camera.maxDepth) return;
+	const uint2 screenPos = params.camera.camToScreen<uint2>(camPos);
+
+    const int upsample = 8; //min(UPSAMPLE_MAX, int((5.0f*r) * params.camera.fx / camPos.z));
+
+	// Not on screen so stop now...
+	if (screenPos.x >= depth_in.width() || screenPos.y >= depth_in.height()) return;
+            
+    // Is this point near the actual surface and therefore a contributor?
+    const float d = ((float)depth_in.tex2D((int)screenPos.x, (int)screenPos.y)/1000.0f);
+    //if (abs(d - camPos.z) > DEPTH_THRESHOLD) return;
+
+    // TODO:(Nick) Should just one thread load these to shared mem?
+    const float4 colour = make_float4(colour_in.tex2D(x, y));
+    //const float4 normal = tex2D<float4>(camera.normal, x, y);
+
+	// Each thread in warp takes an upsample point and updates corresponding depth buffer.
+	const int lane = tid % WARP_SIZE;
+	for (int i=lane; i<upsample*upsample; i+=WARP_SIZE) {
+		const float u = (i % upsample) - (upsample / 2);
+		const float v = (i / upsample) - (upsample / 2);
+
+        // Use the depth buffer to determine this pixels 3D position in camera space
+        const float d = ((float)depth_in.tex2D(screenPos.x+u, screenPos.y+v)/1000.0f);
+		const float3 nearest = params.camera.screenToCam((int)(screenPos.x+u),(int)(screenPos.y+v),d);
+
+        // What is contribution of our current point at this pixel?
+        const float weight = ftl::cuda::spatialWeighting(length(nearest - camPos), SMOOTHING_MULTIPLIER_C*(nearest.z/params.camera.fx));
+        if (screenPos.x+u < colour_out.width() && screenPos.y+v < colour_out.height() && weight > 0.0f) {  // TODO: Use confidence threshold here
+            const float4 wcolour = colour * weight;
+			//const float4 wnormal = normal * weight;
+			
+			//printf("Z %f\n", d);
+
+            // Add this points contribution to the pixel buffer
+            atomicAdd((float*)&colour_out(screenPos.x+u, screenPos.y+v), wcolour.x);
+            atomicAdd((float*)&colour_out(screenPos.x+u, screenPos.y+v)+1, wcolour.y);
+            atomicAdd((float*)&colour_out(screenPos.x+u, screenPos.y+v)+2, wcolour.z);
+            atomicAdd((float*)&colour_out(screenPos.x+u, screenPos.y+v)+3, wcolour.w);
+            //atomicAdd((float*)&normal_out(screenPos.x+u, screenPos.y+v), wnormal.x);
+            //atomicAdd((float*)&normal_out(screenPos.x+u, screenPos.y+v)+1, wnormal.y);
+            //atomicAdd((float*)&normal_out(screenPos.x+u, screenPos.y+v)+2, wnormal.z);
+            //atomicAdd((float*)&normal_out(screenPos.x+u, screenPos.y+v)+3, wnormal.w);
+            atomicAdd(&contrib_out(screenPos.x+u, screenPos.y+v), weight);
+        }
+	}
+}
+
+void ftl::cuda::dibr_attribute(
+        TextureObject<uchar4> &colour_in,    // Original colour image
+        TextureObject<float4> &points,       // Original 3D points
+        TextureObject<int> &depth_in,        // Virtual depth map
+        TextureObject<float4> &colour_out,   // Accumulated output
+        //TextureObject<float4> normal_out,
+        TextureObject<float> &contrib_out,
+        SplatParams &params, cudaStream_t stream) {
+    const dim3 gridSize((depth_in.width() + 2 - 1)/2, (depth_in.height() + T_PER_BLOCK - 1)/T_PER_BLOCK);
+	const dim3 blockSize(2*WARP_SIZE, T_PER_BLOCK);
+
+    dibr_attribute_contrib_kernel<<<gridSize, blockSize, 0, stream>>>(
+        colour_in,
+        points,
+        depth_in,
+        colour_out,
+        contrib_out,
+        params
+    );
+    cudaSafeCall( cudaGetLastError() );
+}
+
+//==============================================================================
+
+__global__ void dibr_normalise_kernel(
+        TextureObject<float4> colour_in,
+        TextureObject<uchar4> colour_out,
+        //TextureObject<float4> normals,
+        TextureObject<float> contribs) {
+    const unsigned int x = blockIdx.x*blockDim.x + threadIdx.x;
+    const unsigned int y = blockIdx.y*blockDim.y + threadIdx.y;
+
+    if (x < colour_in.width() && y < colour_in.height()) {
+        const float4 colour = colour_in.tex2D((int)x,(int)y);
+        //const float4 normal = normals.tex2D((int)x,(int)y);
+        const float contrib = contribs.tex2D((int)x,(int)y);
+
+        if (contrib > 0.0f) {
+            colour_out(x,y) = make_uchar4(colour.x / contrib, colour.y / contrib, colour.z / contrib, 0);
+            //normals(x,y) = normal / contrib;
+        }
+    }
+}
+
+void ftl::cuda::dibr_normalise(TextureObject<float4> &colour_in, TextureObject<uchar4> &colour_out, TextureObject<float> &contribs, cudaStream_t stream) {
+    const dim3 gridSize((colour_in.width() + T_PER_BLOCK - 1)/T_PER_BLOCK, (colour_in.height() + T_PER_BLOCK - 1)/T_PER_BLOCK);
+    const dim3 blockSize(T_PER_BLOCK, T_PER_BLOCK);
+
+    dibr_normalise_kernel<<<gridSize, blockSize, 0, stream>>>(colour_in, colour_out, contribs);
+    cudaSafeCall( cudaGetLastError() );
+}
diff --git a/components/renderers/cpp/src/splatter_cuda.hpp b/components/renderers/cpp/src/splatter_cuda.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..8f6557b7f3e9d9ce99bceed615946c19b1afbec2
--- /dev/null
+++ b/components/renderers/cpp/src/splatter_cuda.hpp
@@ -0,0 +1,28 @@
+#ifndef _FTL_RECONSTRUCTION_SPLAT_CUDA_HPP_
+#define _FTL_RECONSTRUCTION_SPLAT_CUDA_HPP_
+
+#include <ftl/cuda_common.hpp>
+#include <ftl/render/splat_params.hpp>
+
+namespace ftl {
+namespace cuda {
+    void dibr_merge(ftl::cuda::TextureObject<float4> &points, ftl::cuda::TextureObject<int> &depth, ftl::render::SplatParams params, cudaStream_t stream);
+
+    void dibr_attribute(
+        ftl::cuda::TextureObject<uchar4> &colour_in,    // Original colour image
+        ftl::cuda::TextureObject<float4> &points,       // Original 3D points
+        ftl::cuda::TextureObject<int> &depth_in,        // Virtual depth map
+        ftl::cuda::TextureObject<float4> &colour_out,   // Accumulated output
+        //TextureObject<float4> normal_out,
+        ftl::cuda::TextureObject<float> &contrib_out,
+        ftl::render::SplatParams &params, cudaStream_t stream);
+
+    void dibr_normalise(
+        ftl::cuda::TextureObject<float4> &colour_in,
+        ftl::cuda::TextureObject<uchar4> &colour_out,
+        ftl::cuda::TextureObject<float> &contribs,
+        cudaStream_t stream);
+}
+}
+
+#endif  // _FTL_RECONSTRUCTION_SPLAT_CUDA_HPP_
diff --git a/components/rgbd-sources/CMakeLists.txt b/components/rgbd-sources/CMakeLists.txt
index 5983d1d940b8b420aa5fb8b6653da65f7f904b0a..2b056d009a73dd7ee82adaedbf03bb98194d636f 100644
--- a/components/rgbd-sources/CMakeLists.txt
+++ b/components/rgbd-sources/CMakeLists.txt
@@ -3,6 +3,8 @@ set(RGBDSRC
 	src/local.cpp
 	src/disparity.cpp
 	src/source.cpp
+	src/frame.cpp
+	src/frameset.cpp
 	src/stereovideo.cpp
 	src/middlebury_source.cpp
 	src/net.cpp
@@ -14,6 +16,9 @@ set(RGBDSRC
 #	src/algorithms/opencv_sgbm.cpp
 #	src/algorithms/opencv_bm.cpp
 	src/cb_segmentation.cpp
+	src/abr.cpp
+	src/offilter.cpp
+	src/virtual.cpp
 )
 
 if (HAVE_REALSENSE)
@@ -34,6 +39,7 @@ endif (LIBSGM_FOUND)
 if (CUDA_FOUND)
 	list(APPEND RGBDSRC
 		src/algorithms/disp2depth.cu
+		src/algorithms/offilter.cu
 #		"src/algorithms/opencv_cuda_bm.cpp"
 #		"src/algorithms/opencv_cuda_bp.cpp"
 #		"src/algorithms/rtcensus.cu"
@@ -61,7 +67,7 @@ set_property(TARGET ftlrgbd PROPERTY CUDA_SEPARABLE_COMPILATION OFF)
 endif()
 
 #target_include_directories(cv-node PUBLIC ${PROJECT_SOURCE_DIR}/include)
-target_link_libraries(ftlrgbd ftlcommon ${OpenCV_LIBS} ${LIBSGM_LIBRARIES} ${CUDA_LIBRARIES} Eigen3::Eigen ${REALSENSE_LIBRARY} ftlnet ${LibArchive_LIBRARIES})
+target_link_libraries(ftlrgbd ftlcommon ${OpenCV_LIBS} ${LIBSGM_LIBRARIES} ${CUDA_LIBRARIES} Eigen3::Eigen ${REALSENSE_LIBRARY} ftlnet ${LibArchive_LIBRARIES} ftlcodecs)
 
 add_subdirectory(test)
 
diff --git a/components/rgbd-sources/README.md b/components/rgbd-sources/README.md
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..90d1d3554c9c4b13044aa3fadc8d3637baa35205 100644
--- a/components/rgbd-sources/README.md
+++ b/components/rgbd-sources/README.md
@@ -0,0 +1,51 @@
+# RGB-Depth Sources
+
+This component provides a variety of sources for colour and depth images. These
+include the following, but do not include virtual cameras:
+* [Intel Realsense depth camera](src/realsense_source.hpp)
+* [Stereo video](src/stereovideo.hpp) from two video capture cards using a disparity algorithm
+* [Snapshots](src/snapshot_source.hpp) that were previously captured and saved to disk
+* [Middlebury](src/middlebury_source.hpp) test datasets as available online
+* [Streamed network sources](include/ftl/rgbd/streamer.hpp) from other nodes
+
+An RGB-D source is represented as a two image channel object that is generated
+through a pipeline of processes usually consisting of the following (but not
+all sources have all steps):
+1. Frame capture from hardware
+2. Internal buffer swapping if double-buffering is used
+3. Retrieval, an IO blocking process of downloading images from devices
+4. Computation of, for example, disparity and depth maps from colour images
+
+## Groups
+A collection of sources may form a group that must be synchronised accurately
+for reconstruction to take place. A [group class](include/ftl/rgbd/group.hpp)
+coordinates the above 4 steps across all sources such that millisecond accurate
+frames with timestamps can be buffered and collected together to be passed on to
+the next stage. A [high precision timer](../common/cpp/include/ftl/timer.hpp)
+is used to manage the pipeline.
+
+## Streaming
+One possible use for a group of sources is to stream them over a network
+where they may be re-grouped. A [streamer](include/ftl/rgbd/streamer.hpp) object
+will receive sets of frames from a group object and then divide each image into
+a number of chunks, each of which is compressed on a CPU core and sent to every
+client who asks for them. Each client may ask for a different bitrate and
+resolution so the streamer will also take care of this. The streamer class uses
+the [ftl net library](../net/) for network communication.
+
+## Calibration
+Some sources require a camera calibration step. Lens corrections an stereo
+camera configurations are applied by the [calibrate class](src/calibrate.hpp).
+Only stereo video sources currently need this step and the correction matrices
+are calculated using a separate
+[calibration app](../../application/calibration-multi/). There is also some
+basic [colour correction](src/colour.hpp) that can be applied.
+
+## Disparity Algorithms
+A few algorithms are included with the RGB-D sources for converting two
+colour images into one colour and one depth image based upon the pixel shift
+observed between the two images. [LibSGM](https://github.com/fixstars/libSGM)
+is our algorithm of choice currently. Further pre and post filtering and
+smoothing steps are applied, in particular an optical flow based temporal
+smoothing across a number of frames to reduce flickering effects. This uses
+[NVIDIA's Optical Flow SDK](https://developer.nvidia.com/opticalflow-sdk).
\ No newline at end of file
diff --git a/components/rgbd-sources/include/ftl/offilter.hpp b/components/rgbd-sources/include/ftl/offilter.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..6aece39aab241dfc7605dfb208d7d30ba6135509
--- /dev/null
+++ b/components/rgbd-sources/include/ftl/offilter.hpp
@@ -0,0 +1,31 @@
+#pragma once
+
+#include <ftl/config.h>
+#include <ftl/rgbd/frame.hpp>
+
+#ifdef HAVE_OPTFLOW
+#include <ftl/cuda_util.hpp>
+#include <opencv2/core.hpp>
+#include <opencv2/core/cuda.hpp>
+#include <opencv2/cudaoptflow.hpp>
+
+namespace ftl {
+namespace rgbd {
+
+class OFDisparityFilter {
+public:
+	OFDisparityFilter() : n_max_(0), threshold_(0.0) {}
+	OFDisparityFilter(cv::Size size, int n_frames, float threshold);
+	void filter(ftl::rgbd::Frame &frame, cv::cuda::Stream &stream);
+
+private:
+	int n_max_;
+	float threshold_;
+
+	cv::cuda::GpuMat disp_old_;
+};
+
+}
+}
+
+#endif  // HAVE_OPTFLOW
diff --git a/components/rgbd-sources/include/ftl/rgbd/camera.hpp b/components/rgbd-sources/include/ftl/rgbd/camera.hpp
index 8acad9c41d6c4950398cde7d9f44ab8ae0bc08b6..e245be1ea44f3e7e8503f585bf2c387fef821a38 100644
--- a/components/rgbd-sources/include/ftl/rgbd/camera.hpp
+++ b/components/rgbd-sources/include/ftl/rgbd/camera.hpp
@@ -2,23 +2,69 @@
 #ifndef _FTL_RGBD_CAMERA_PARAMS_HPP_
 #define _FTL_RGBD_CAMERA_PARAMS_HPP_
 
+#include <vector_types.h>
+#include <cuda_runtime.h>
+#include <ftl/cuda_util.hpp>
+
 namespace ftl{
 namespace rgbd {
 
-struct Camera {
-	double fx;
-	double fy;
-	double cx;
-	double cy;
-	unsigned int width;
-	unsigned int height;
-	double minDepth;
-	double maxDepth;
-	double baseline;
-	double doffs;
+/**
+ * All properties associated with cameras. This structure is designed to
+ * operate on CPU and GPU.
+ */
+struct __align__(16) Camera {
+	double fx;				// Focal length X
+	double fy;				// Focal length Y (usually same as fx)
+	double cx;				// Principle point Y
+	double cy;				// Principle point Y
+	unsigned int width;		// Pixel width
+	unsigned int height;	// Pixel height
+	double minDepth;		// Near clip in meters
+	double maxDepth;		// Far clip in meters
+	double baseline;		// For stereo pair
+	double doffs;			// Disparity offset
+
+	/**
+	 * Convert camera coordinates into screen coordinates.
+	 */
+	template <typename T> __device__ T camToScreen(const float3 &pos) const;
+
+	/**
+	 * Convert screen plus depth into camera coordinates.
+	 */
+	__device__ float3 screenToCam(uint ux, uint uy, float depth) const; 
 };
 
 };
 };
 
+// ---- IMPLEMENTATIONS --------------------------------------------------------
+
+template <> __device__
+inline float2 ftl::rgbd::Camera::camToScreen<float2>(const float3 &pos) const {
+	return make_float2(
+			pos.x*fx/pos.z - cx,			
+			pos.y*fy/pos.z - cy);
+}
+
+template <> __device__
+inline int2 ftl::rgbd::Camera::camToScreen<int2>(const float3 &pos) const {
+	float2 pImage = camToScreen<float2>(pos);
+	return make_int2(pImage + make_float2(0.5f, 0.5f));
+}
+
+template <> __device__
+inline uint2 ftl::rgbd::Camera::camToScreen<uint2>(const float3 &pos) const {
+	int2 p = camToScreen<int2>(pos);
+	return make_uint2(p.x, p.y);
+}
+
+__device__
+inline float3 ftl::rgbd::Camera::screenToCam(uint ux, uint uy, float depth) const {
+	const float x = ((float)ux+cx) / fx;
+	const float y = ((float)uy+cy) / fy;
+	return make_float3(depth*x, depth*y, depth);
+}
+
 #endif
diff --git a/components/rgbd-sources/include/ftl/rgbd/channels.hpp b/components/rgbd-sources/include/ftl/rgbd/channels.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..9bf731a5319fa47c501a91e09f1e2acc48c5a4a8
--- /dev/null
+++ b/components/rgbd-sources/include/ftl/rgbd/channels.hpp
@@ -0,0 +1,124 @@
+#ifndef _FTL_RGBD_CHANNELS_HPP_
+#define _FTL_RGBD_CHANNELS_HPP_
+
+#include <bitset>
+#include <msgpack.hpp>
+
+namespace ftl {
+namespace rgbd {
+
+enum struct Channel : int {
+    None = -1,
+    Colour = 0,         // 8UC3 or 8UC4
+    Left = 0,
+    Depth = 1,          // 32S or 32F
+    Right = 2,          // 8UC3 or 8UC4
+    Colour2 = 2,
+    Disparity = 3,
+    Depth2 = 3,
+    Deviation = 4,
+    Normals = 5,        // 32FC4
+    Points = 6,         // 32FC4
+    Confidence = 7,     // 32F
+    Contribution = 7,   // 32F
+    EnergyVector,       // 32FC4
+    Flow,               // 32F
+    Energy,             // 32F
+    LeftGray,
+    RightGray,
+    Overlay1
+};
+
+class Channels {
+    public:
+
+	class iterator {
+		public:
+		iterator(const Channels &c, unsigned int ix) : channels_(c), ix_(ix) { }
+		iterator operator++();
+		iterator operator++(int junk);
+		inline ftl::rgbd::Channel operator*() { return static_cast<Channel>(static_cast<int>(ix_)); }
+		//ftl::rgbd::Channel operator->() { return ptr_; }
+		inline bool operator==(const iterator& rhs) { return ix_ == rhs.ix_; }
+		inline bool operator!=(const iterator& rhs) { return ix_ != rhs.ix_; }
+		private:
+		const Channels &channels_;
+		unsigned int ix_;
+	};
+
+    inline Channels() { mask = 0; }
+    inline explicit Channels(unsigned int m) { mask = m; }
+    inline explicit Channels(Channel c) { mask = (c == Channel::None) ? 0 : 0x1 << static_cast<unsigned int>(c); }
+    inline Channels &operator=(Channel c) { mask = (c == Channel::None) ? 0 : 0x1 << static_cast<unsigned int>(c); return *this; }
+    inline Channels operator|(Channel c) const { return (c == Channel::None) ? Channels(mask) : Channels(mask | (0x1 << static_cast<unsigned int>(c))); }
+    inline Channels operator+(Channel c) const { return (c == Channel::None) ? Channels(mask) : Channels(mask | (0x1 << static_cast<unsigned int>(c))); }
+    inline Channels &operator|=(Channel c) { mask |= (c == Channel::None) ? 0 : (0x1 << static_cast<unsigned int>(c)); return *this; }
+    inline Channels &operator+=(Channel c) { mask |= (c == Channel::None) ? 0 : (0x1 << static_cast<unsigned int>(c)); return *this; }
+    inline Channels &operator-=(Channel c) { mask &= ~((c == Channel::None) ? 0 : (0x1 << static_cast<unsigned int>(c))); return *this; }
+    inline Channels &operator+=(unsigned int c) { mask |= (0x1 << c); return *this; }
+    inline Channels &operator-=(unsigned int c) { mask &= ~(0x1 << c); return *this; }
+
+    inline bool has(Channel c) const {
+        return (c == Channel::None) ? true : mask & (0x1 << static_cast<unsigned int>(c));
+    }
+
+    inline bool has(unsigned int c) const {
+        return mask & (0x1 << c);
+    }
+
+	inline iterator begin() { return iterator(*this, 0); }
+	inline iterator end() { return iterator(*this, 32); }
+
+    inline operator unsigned int() { return mask; }
+    inline operator bool() { return mask > 0; }
+    inline operator Channel() {
+        if (mask == 0) return Channel::None;
+        int ix = 0;
+        int tmask = mask;
+        while (!(tmask & 0x1) && ++ix < 32) tmask >>= 1;
+        return static_cast<Channel>(ix);
+    }
+    
+    inline size_t count() { return std::bitset<32>(mask).count(); }
+    inline void clear() { mask = 0; }
+
+    static const size_t kMax = 32;
+
+	static Channels All();
+
+    private:
+    unsigned int mask;
+};
+
+inline Channels::iterator Channels::iterator::operator++() { Channels::iterator i = *this; while (++ix_ < 32 && !channels_.has(ix_)); return i; }
+inline Channels::iterator Channels::iterator::operator++(int junk) { while (++ix_ < 32 && !channels_.has(ix_)); return *this; }
+
+inline Channels Channels::All() {
+	return Channels(0xFFFFFFFFu);
+}
+
+static const Channels kNoChannels;
+static const Channels kAllChannels(0xFFFFFFFFu);
+
+inline bool isFloatChannel(ftl::rgbd::Channel chan) {
+	switch (chan) {
+	case Channel::Depth		:
+	case Channel::Energy	: return true;
+	default					: return false;
+	}
+}
+
+}
+}
+
+MSGPACK_ADD_ENUM(ftl::rgbd::Channel);
+
+inline ftl::rgbd::Channels operator|(ftl::rgbd::Channel a, ftl::rgbd::Channel b) {
+    return ftl::rgbd::Channels(a) | b;
+}
+
+inline ftl::rgbd::Channels operator+(ftl::rgbd::Channel a, ftl::rgbd::Channel b) {
+    return ftl::rgbd::Channels(a) | b;
+}
+
+#endif  // _FTL_RGBD_CHANNELS_HPP_
diff --git a/components/rgbd-sources/include/ftl/rgbd/detail/abr.hpp b/components/rgbd-sources/include/ftl/rgbd/detail/abr.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..b3d809784abdf9a3f1bdb362c2dcc3a88b1a9e3e
--- /dev/null
+++ b/components/rgbd-sources/include/ftl/rgbd/detail/abr.hpp
@@ -0,0 +1,121 @@
+#ifndef _FTL_RGBD_ABR_HPP_
+#define _FTL_RGBD_ABR_HPP_
+
+#include <ftl/rgbd/detail/netframe.hpp>
+#include <cstdint>
+
+namespace ftl {
+namespace rgbd {
+namespace detail {
+
+static const float kAspectRatio = 1.777778f;
+
+enum codec_t {
+	kCodecJPG = 0,
+	kCodecPNG
+};
+
+struct BitrateSetting {
+	int colour_res;
+	int depth_res;
+	int colour_qual;
+	int depth_qual;
+	codec_t colour_codec;
+	codec_t depth_codec;
+	int block_count_x;
+
+	/*int width;
+	int height;
+	int jpg_quality;
+	int png_compression;
+	codec_t colour_codec;
+	codec_t depth_codec;
+	int chunking;*/
+};
+
+static const BitrateSetting bitrate_settings[] = {
+	1080, 1080, 95, 1, kCodecJPG, kCodecPNG, 4,
+	1080, 720, 95, 1, kCodecJPG, kCodecPNG, 4,
+	720, 720, 95, 1, kCodecJPG, kCodecPNG, 4,
+	720, 576, 95, 5, kCodecJPG, kCodecPNG, 4,
+	576, 576, 95, 5, kCodecJPG, kCodecPNG, 4,
+	576, 480, 95, 5, kCodecJPG, kCodecPNG, 2,
+	480, 480, 95, 5, kCodecJPG, kCodecPNG, 2,
+	480, 360, 95, 9, kCodecJPG, kCodecPNG, 2,
+	360, 360, 95, 9, kCodecJPG, kCodecPNG, 2,
+	360, 360, 50, 9, kCodecJPG, kCodecPNG, 2
+};
+
+/*static const BitrateSetting bitrate_settings[] = {
+	1920, 1080, 95, 1, kCodecJPG, kCodecPNG, 4,	// ?
+	1280, 720, 95, 1, kCodecJPG, kCodecPNG, 4,	// ~96Mbps
+	1024, 576, 95, 5, kCodecJPG, kCodecPNG, 3,	// ~62Mbps
+	854, 480, 95, 5, kCodecJPG, kCodecPNG, 3,	// ~48Mbps
+	640, 360, 95, 9, kCodecJPG, kCodecPNG, 2,	// ~31Mbps
+	640, 360, 75, 9, kCodecJPG, kCodecPNG, 2,	// ~25Mbps
+	640, 360, 65, 9, kCodecJPG, kCodecPNG, 2,	// ~24Mbps
+	640, 360, 50, 9, kCodecJPG, kCodecPNG, 2,	// ~23Mbps
+	320, 160, 95, 9, kCodecJPG, kCodecPNG, 2,	// ~10Mbps
+	320, 160, 75, 9, kCodecJPG, kCodecPNG, 2	// ~8Mbps
+};*/
+
+typedef unsigned int bitrate_t;
+
+static const bitrate_t kBitrateBest = 0;
+static const bitrate_t kBitrateWorst = 9;
+
+/**
+ * Adaptive Bitrate Controller to monitor and decide on a client streams
+ * bitrate. The basics of our approach are that if transmission latency exceeds
+ * some proportion of the frame time then mark it as a slow frame. Similarly if
+ * transmission latency falls below a proportion of frame time then mark it as
+ * a fast frame. If the net frame status is slow (thresholded) then reduce
+ * bitrate, if the net status is fast then increase bitrate.
+ */
+class ABRController {
+	public:
+	ABRController();
+	~ABRController();
+
+	/**
+	 * From a received frame, select a bitrate based upon actual and required
+	 * bitrate as well as past frames.
+	 */
+	bitrate_t selectBitrate(const ftl::rgbd::detail::NetFrame &);
+
+	/**
+	 * Called to tell the controller the new bitrate is now in use by the stream
+	 */
+	void notifyChanged();
+
+	void setMaximumBitrate(bitrate_t);
+	void setMinimumBitrate(bitrate_t);
+
+	static const ftl::rgbd::detail::BitrateSetting &getBitrateInfo(bitrate_t b);
+	static int getColourWidth(bitrate_t b);
+	static int getDepthWidth(bitrate_t b);
+	static int getColourHeight(bitrate_t b);
+	static int getDepthHeight(bitrate_t b);
+	static int getBlockCountX(bitrate_t b);
+	static int getBlockCountY(bitrate_t b);
+	static int getBlockCount(bitrate_t b);
+	static int getColourQuality(bitrate_t b);
+	static int getDepthQuality(bitrate_t b);
+
+	private:
+	unsigned int down_log_;		// Bit log of delayed frames
+	unsigned int up_log_;		// Bit log of fast frames
+	int64_t last_br_change_;	// Time of last adaptive change
+	float down_threshold_;		// Proportion of min bitrate before reduction
+	float up_threshold_;		// Proportion of min bitrate before increase
+	bitrate_t bitrate_;
+	bool enabled_;
+	bitrate_t max_;
+	bitrate_t min_;
+};
+
+}
+}
+}
+
+#endif  // _FTL_RGBD_ABR_HPP_
diff --git a/components/rgbd-sources/include/ftl/rgbd/detail/netframe.hpp b/components/rgbd-sources/include/ftl/rgbd/detail/netframe.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..f01154e12c876292f724f072fbaa69812e1b29bb
--- /dev/null
+++ b/components/rgbd-sources/include/ftl/rgbd/detail/netframe.hpp
@@ -0,0 +1,50 @@
+#ifndef _FTL_RGBD_NETFRAME_HPP_
+#define _FTL_RGBD_NETFRAME_HPP_
+
+#include <cstdint>
+#include <vector>
+#include <ftl/rgbd/source.hpp>
+
+namespace ftl {
+namespace rgbd {
+namespace detail {
+
+/**
+ * Buffers for a single frame as it is being received over the network.
+ * Also maintains statistics about the frame transmission for later analysis.
+ */
+struct NetFrame {
+	cv::Mat channel1;
+	cv::Mat channel2;
+	volatile int64_t timestamp;
+	std::atomic<int> chunk_count;
+	int chunk_total;
+	std::atomic<int> tx_size;
+	int64_t tx_latency;
+	MUTEX mtx;
+};
+
+/**
+ * Manage multiple frames with their timestamp as an identifier. Once a frame
+ * is completed it should be freed immediately from the queue for reuse. It
+ * is not the job of this queue to buffer frames for longer periods, see Group
+ * for this functionality. This queue is only to manage chunk ordering problems.
+ */
+class NetFrameQueue {
+	public:
+	explicit NetFrameQueue(int size=2);
+	~NetFrameQueue();
+
+	NetFrame &getFrame(int64_t ts, const cv::Size &, int c1type, int c2type);
+	void freeFrame(NetFrame &);
+
+	private:
+	std::vector<NetFrame> frames_;
+	MUTEX mtx_;
+};
+
+}
+}
+}
+
+#endif  // _FTL_RGBD_NETFRAME_HPP_
diff --git a/components/rgbd-sources/include/ftl/rgbd/detail/source.hpp b/components/rgbd-sources/include/ftl/rgbd/detail/source.hpp
index 7557fecb3da30af7a818823904a9b71bba3bc057..e98ff38aacd4cf0731ef96b67ecc85732d4c0c7f 100644
--- a/components/rgbd-sources/include/ftl/rgbd/detail/source.hpp
+++ b/components/rgbd-sources/include/ftl/rgbd/detail/source.hpp
@@ -2,33 +2,16 @@
 #define _FTL_RGBD_DETAIL_SOURCE_HPP_
 
 #include <Eigen/Eigen>
+#include <ftl/cuda_util.hpp>
 #include <opencv2/opencv.hpp>
 #include <ftl/rgbd/camera.hpp>
+#include <ftl/rgbd/frame.hpp>
 
 namespace ftl{
 namespace rgbd {
 
 class Source;
 
-typedef unsigned int channel_t;
-
-static const channel_t kChanNone = 0;
-static const channel_t kChanLeft = 0x0001;
-static const channel_t kChanDepth = 0x0002;
-static const channel_t kChanRight = 0x0004;
-static const channel_t kChanDisparity = 0x0008;
-static const channel_t kChanDeviation = 0x0010;
-static const channel_t kChanNormals = 0x0020;
-static const channel_t kChanConfidence = 0x0040;
-static const channel_t kChanFlow = 0x0080;
-
-static const channel_t kChanOverlay1 = 0x1000;
-
-inline bool isFloatChannel(ftl::rgbd::channel_t chan) {
-	return (chan == ftl::rgbd::kChanDepth);
-}
-
-
 typedef unsigned int capability_t;
 
 static const capability_t kCapMovable	= 0x0001;	// A movable virtual cam
@@ -49,14 +32,31 @@ class Source {
 	virtual ~Source() {}
 
 	/**
+	 * Perform hardware data capture.
+	 */
+	virtual bool capture(int64_t ts)=0;
+
+	/**
+	 * Perform IO operation to get the data.
+	 */
+	virtual bool retrieve()=0;
+
+	/**
+	 * Do any processing from previously captured frames...
 	 * @param n Number of frames to request in batch. Default -1 means automatic (10)
 	 * @param b Bit rate setting. -1 = automatic, 0 = best quality, 9 = lowest quality
 	 */
-	virtual bool grab(int n, int b)=0;
+	virtual bool compute(int n, int b)=0;
+
+	/**
+	 * Between frames, or before next frame, do any buffer swapping operations.
+	 */
+	virtual void swap() {}
+
 	virtual bool isReady() { return false; };
 	virtual void setPose(const Eigen::Matrix4d &pose) { };
 
-	virtual Camera parameters(channel_t) { return params_; };
+	virtual Camera parameters(ftl::rgbd::Channel) { return params_; };
 
 	protected:
 	capability_t capabilities_;
diff --git a/components/rgbd-sources/include/ftl/rgbd/format.hpp b/components/rgbd-sources/include/ftl/rgbd/format.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..2e86aaf96a5f64d13065385ddee360fd80ca9f8c
--- /dev/null
+++ b/components/rgbd-sources/include/ftl/rgbd/format.hpp
@@ -0,0 +1,52 @@
+#ifndef _FTL_RGBD_FORMAT_HPP_
+#define _FTL_RGBD_FORMAT_HPP_
+
+#include <opencv2/core.hpp>
+#include <opencv2/core/cuda.hpp>
+#include <ftl/cuda_util.hpp>
+#include <ftl/codecs/bitrates.hpp>
+#include <ftl/traits.hpp>
+
+namespace ftl {
+namespace rgbd {
+
+struct FormatBase {
+	FormatBase(size_t w, size_t h, int t) : width(w), height(h), cvType(t) {}
+
+	size_t width;		// Width in pixels
+	size_t height;		// Height in pixels
+	int cvType;			// OpenCV Mat type
+
+	inline bool empty() const { return width == 0 || height == 0; }
+	inline cv::Size size() const { return cv::Size(width, height); }
+};
+
+template <typename T>
+struct Format : public ftl::rgbd::FormatBase {
+	Format() : FormatBase(0,0,0) {}
+
+	Format(size_t w, size_t h) : FormatBase(
+			w, h, ftl::traits::OpenCVType<T>::value) {}
+
+	explicit Format(ftl::codecs::definition_t d) : FormatBase(
+			ftl::codecs::getWidth(d),
+			ftl::codecs::getHeight(d),
+			ftl::traits::OpenCVType<T>::value) {}
+
+	explicit Format(const cv::Size &s) : FormatBase(
+			s.width,
+			s.height,
+			ftl::traits::OpenCVType<T>::value) {}
+
+	explicit Format(const cv::InputArray &a) : FormatBase(
+			a.cols,
+			a.rows,
+			ftl::traits::OpenCVType<T>::value) {
+		CHECK(cvType == a.type());
+	}
+};
+
+}
+}
+
+#endif  // _FTL_RGBD_FORMAT_HPP_
diff --git a/components/rgbd-sources/include/ftl/rgbd/frame.hpp b/components/rgbd-sources/include/ftl/rgbd/frame.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..252ff271de36336938d51d616cdd5a9e87d52187
--- /dev/null
+++ b/components/rgbd-sources/include/ftl/rgbd/frame.hpp
@@ -0,0 +1,257 @@
+#pragma once
+#ifndef _FTL_RGBD_FRAME_HPP_
+#define _FTL_RGBD_FRAME_HPP_
+
+#include <ftl/configuration.hpp>
+#include <ftl/exception.hpp>
+#include <opencv2/core.hpp>
+#include <opencv2/core/cuda.hpp>
+#include <opencv2/core/cuda_stream_accessor.hpp>
+
+#include <ftl/rgbd/channels.hpp>
+#include <ftl/rgbd/format.hpp>
+#include <ftl/codecs/bitrates.hpp>
+
+#include <ftl/cuda_common.hpp>
+
+#include <type_traits>
+#include <array>
+
+namespace ftl {
+namespace rgbd {
+
+// TODO:	interpolation for scaling depends on channel type;
+//			NN for depth/disparity/optflow, linear/cubic/etc. for RGB
+
+class Frame;
+class Source;
+
+/**
+ * Manage a set of image channels corresponding to a single camera frame.
+ */
+class Frame {
+public:
+	Frame() : src_(nullptr) {}
+	explicit Frame(ftl::rgbd::Source *src) : src_(src) {}
+
+	inline ftl::rgbd::Source *source() const { return src_; }
+
+	// Prevent frame copy, instead use a move.
+	//Frame(const Frame &)=delete;
+	//Frame &operator=(const Frame &)=delete;
+
+	void download(ftl::rgbd::Channel c, cv::cuda::Stream stream);
+	void upload(ftl::rgbd::Channel c, cv::cuda::Stream stream);
+	void download(ftl::rgbd::Channels c, cv::cuda::Stream stream);
+	void upload(ftl::rgbd::Channels c, cv::cuda::Stream stream);
+
+	inline void download(ftl::rgbd::Channel c, cudaStream_t stream=0) { download(c, cv::cuda::StreamAccessor::wrapStream(stream)); };
+	inline void upload(ftl::rgbd::Channel c, cudaStream_t stream=0) { upload(c, cv::cuda::StreamAccessor::wrapStream(stream)); };
+	inline void download(ftl::rgbd::Channels c, cudaStream_t stream=0) { download(c, cv::cuda::StreamAccessor::wrapStream(stream)); };
+	inline void upload(ftl::rgbd::Channels c, cudaStream_t stream=0) { upload(c, cv::cuda::StreamAccessor::wrapStream(stream)); };
+
+	/**
+	 * Perform a buffer swap of the selected channels. This is intended to be
+	 * a copy from `this` to the passed frame object but by buffer swap
+	 * instead of memory copy, meaning `this` may become invalid afterwards.
+	 */
+	void swapTo(ftl::rgbd::Channels, Frame &);
+
+	/**
+	 * Create a channel with a given format. This will discard any existing
+	 * data associated with the channel and ensure all data structures and
+	 * memory allocations match the new format.
+	 */
+	template <typename T> T &create(ftl::rgbd::Channel c, const ftl::rgbd::FormatBase &f);
+
+	/**
+	 * Create a channel but without any format.
+	 */
+	template <typename T> T &create(ftl::rgbd::Channel c);
+
+	/**
+	 * Create a CUDA texture object for a channel. This version takes a format
+	 * argument to also create (or recreate) the associated GpuMat.
+	 */
+	template <typename T>
+	ftl::cuda::TextureObject<T> &createTexture(ftl::rgbd::Channel c, const ftl::rgbd::Format<T> &f);
+
+	/**
+	 * Create a CUDA texture object for a channel. With this version the GpuMat
+	 * must already exist and be of the correct type.
+	 */
+	template <typename T>
+	ftl::cuda::TextureObject<T> &createTexture(ftl::rgbd::Channel c);
+
+	/**
+	 * Reset all channels without releasing memory.
+	 */
+	void reset();
+
+	bool empty(ftl::rgbd::Channels c);
+
+	inline bool empty(ftl::rgbd::Channel c) {
+		auto &m = _get(c);
+		return !hasChannel(c) || (m.host.empty() && m.gpu.empty());
+	}
+
+	/**
+	 * Is there valid data in channel (either host or gpu).
+	 */
+	inline bool hasChannel(ftl::rgbd::Channel channel) const {
+		return channels_.has(channel);
+	}
+
+	inline ftl::rgbd::Channels getChannels() const { return channels_; }
+
+	/**
+	 * Is the channel data currently located on GPU. This also returns false if
+	 * the channel does not exist.
+	 */
+	inline bool isGPU(ftl::rgbd::Channel channel) const {
+		return channels_.has(channel) && gpu_.has(channel);
+	}
+
+	/**
+	 * Is the channel data currently located on CPU memory. This also returns
+	 * false if the channel does not exist.
+	 */
+	inline bool isCPU(ftl::rgbd::Channel channel) const {
+		return channels_.has(channel) && !gpu_.has(channel);
+	}
+
+	/**
+	 * Method to get reference to the channel content.
+	 * @param	Channel type
+	 * @return	Const reference to channel data
+	 * 
+	 * Result is valid only if hasChannel() is true. Host/Gpu transfer is
+	 * performed, if necessary, but with a warning since an explicit upload or
+	 * download should be used.
+	 */
+	template <typename T> const T& get(ftl::rgbd::Channel channel) const;
+
+	/**
+	 * Method to get reference to the channel content.
+	 * @param	Channel type
+	 * @return	Reference to channel data
+	 * 
+	 * Result is valid only if hasChannel() is true. Host/Gpu transfer is
+	 * performed, if necessary, but with a warning since an explicit upload or
+	 * download should be used.
+	 */
+	template <typename T> T& get(ftl::rgbd::Channel channel);
+
+	template <typename T> const ftl::cuda::TextureObject<T> &getTexture(ftl::rgbd::Channel) const;
+	template <typename T> ftl::cuda::TextureObject<T> &getTexture(ftl::rgbd::Channel);
+
+private:
+	struct ChannelData {
+		cv::Mat host;
+		cv::cuda::GpuMat gpu;
+		ftl::cuda::TextureObjectBase tex;
+	};
+
+	std::array<ChannelData, Channels::kMax> data_;
+
+	ftl::rgbd::Channels channels_;	// Does it have a channel
+	ftl::rgbd::Channels gpu_;		// Is the channel on a GPU
+
+	ftl::rgbd::Source *src_;
+
+	inline ChannelData &_get(ftl::rgbd::Channel c) { return data_[static_cast<unsigned int>(c)]; }
+	inline const ChannelData &_get(ftl::rgbd::Channel c) const { return data_[static_cast<unsigned int>(c)]; }
+};
+
+// Specialisations
+
+template<> const cv::Mat& Frame::get(ftl::rgbd::Channel channel) const;
+template<> const cv::cuda::GpuMat& Frame::get(ftl::rgbd::Channel channel) const;
+template<> cv::Mat& Frame::get(ftl::rgbd::Channel channel);
+template<> cv::cuda::GpuMat& Frame::get(ftl::rgbd::Channel channel);
+
+template <> cv::Mat &Frame::create(ftl::rgbd::Channel c, const ftl::rgbd::FormatBase &);
+template <> cv::cuda::GpuMat &Frame::create(ftl::rgbd::Channel c, const ftl::rgbd::FormatBase &);
+template <> cv::Mat &Frame::create(ftl::rgbd::Channel c);
+template <> cv::cuda::GpuMat &Frame::create(ftl::rgbd::Channel c);
+
+template <typename T>
+ftl::cuda::TextureObject<T> &Frame::getTexture(ftl::rgbd::Channel c) {
+	if (!channels_.has(c)) throw ftl::exception("Texture channel does not exist");
+	if (!gpu_.has(c)) throw ftl::exception("Texture channel is not on GPU");
+
+	auto &m = _get(c);
+
+	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()) {
+		throw ftl::exception("Texture has not been created properly for this channel");
+	}
+
+	return ftl::cuda::TextureObject<T>::cast(m.tex);
+}
+
+template <typename T>
+ftl::cuda::TextureObject<T> &Frame::createTexture(ftl::rgbd::Channel c, const ftl::rgbd::Format<T> &f) {
+	if (!channels_.has(c)) channels_ += c;
+	if (!gpu_.has(c)) gpu_ += c;
+
+	auto &m = _get(c);
+
+	if (f.empty()) {
+		throw ftl::exception("createTexture needs a non-empty format");
+	} else {
+		m.gpu.create(f.size(), f.cvType);
+	}
+
+	if (m.gpu.type() != ftl::traits::OpenCVType<T>::value) {
+		throw ftl::exception("Texture type does not match underlying data type");
+	}
+
+	// TODO: Check tex cvType
+
+	if (m.tex.devicePtr() == nullptr) {
+		LOG(INFO) << "Creating texture object";
+		m.tex = ftl::cuda::TextureObject<T>(m.gpu);
+	} else if (m.tex.cvType() != ftl::traits::OpenCVType<T>::value || m.tex.width() != m.gpu.cols || m.tex.height() != m.gpu.rows) {
+		LOG(INFO) << "Recreating texture object";
+		m.tex.free();
+		m.tex = ftl::cuda::TextureObject<T>(m.gpu);
+	}
+
+	return ftl::cuda::TextureObject<T>::cast(m.tex);
+}
+
+template <typename T>
+ftl::cuda::TextureObject<T> &Frame::createTexture(ftl::rgbd::Channel c) {
+	if (!channels_.has(c)) throw ftl::exception("createTexture needs a format if the channel does not exist");
+
+	auto &m = _get(c);
+
+	if (isCPU(c) && !m.host.empty()) {
+		m.gpu.create(m.host.size(), m.host.type());
+		// TODO: Should this upload to GPU or not?
+		//gpu_ += c;
+	} else if (isCPU(c) || (isGPU(c) && m.gpu.empty())) {
+		throw ftl::exception("createTexture needs a format if no memory is allocated");
+	}
+
+	if (m.gpu.type() != ftl::traits::OpenCVType<T>::value) {
+		throw ftl::exception("Texture type does not match underlying data type");
+	}
+
+	// TODO: Check tex cvType
+
+	if (m.tex.devicePtr() == nullptr) {
+		LOG(INFO) << "Creating texture object";
+		m.tex = ftl::cuda::TextureObject<T>(m.gpu);
+	} else if (m.tex.cvType() != ftl::traits::OpenCVType<T>::value || m.tex.width() != m.gpu.cols || m.tex.height() != m.gpu.rows || m.tex.devicePtr() != m.gpu.data) {
+		m.tex.free();
+		m.tex = ftl::cuda::TextureObject<T>(m.gpu);
+	}
+
+	return ftl::cuda::TextureObject<T>::cast(m.tex);
+}
+
+}
+}
+
+#endif // _FTL_RGBD_FRAME_HPP_
\ No newline at end of file
diff --git a/components/rgbd-sources/include/ftl/rgbd/frameset.hpp b/components/rgbd-sources/include/ftl/rgbd/frameset.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..2fa39e2eacf19339860e98fa98df44f687ac64c7
--- /dev/null
+++ b/components/rgbd-sources/include/ftl/rgbd/frameset.hpp
@@ -0,0 +1,37 @@
+#ifndef _FTL_RGBD_FRAMESET_HPP_
+#define _FTL_RGBD_FRAMESET_HPP_
+
+#include <ftl/threads.hpp>
+#include <ftl/rgbd/frame.hpp>
+
+#include <opencv2/opencv.hpp>
+#include <vector>
+
+namespace ftl {
+namespace rgbd {
+
+class Source;
+
+/**
+ * Represents a set of synchronised frames, each with two channels. This is
+ * used to collect all frames from multiple computers that have the same
+ * timestamp.
+ */
+struct FrameSet {
+	int64_t timestamp;				// Millisecond timestamp of all frames
+	std::vector<Source*> sources;	// All source objects involved.
+	std::vector<ftl::rgbd::Frame> frames;
+	std::atomic<int> count;				// Number of valid frames
+	std::atomic<unsigned int> mask;		// Mask of all sources that contributed
+	bool stale;						// True if buffers have been invalidated
+	SHARED_MUTEX mtx;
+
+	void upload(ftl::rgbd::Channels, cudaStream_t stream=0);
+	void download(ftl::rgbd::Channels, cudaStream_t stream=0);
+	void swapTo(ftl::rgbd::FrameSet &);
+};
+
+}
+}
+
+#endif  // _FTL_RGBD_FRAMESET_HPP_
diff --git a/components/rgbd-sources/include/ftl/rgbd/group.hpp b/components/rgbd-sources/include/ftl/rgbd/group.hpp
index 1fc92a772e4edb7ce21590429aa08c7f602ed2bd..0ded29e80b7d2fa01ad656c2fbb3b8865b726a70 100644
--- a/components/rgbd-sources/include/ftl/rgbd/group.hpp
+++ b/components/rgbd-sources/include/ftl/rgbd/group.hpp
@@ -1,7 +1,11 @@
 #ifndef _FTL_RGBD_GROUP_HPP_
 #define _FTL_RGBD_GROUP_HPP_
 
+#include <ftl/cuda_util.hpp>
 #include <ftl/threads.hpp>
+#include <ftl/timer.hpp>
+#include <ftl/rgbd/frame.hpp>
+#include <ftl/rgbd/frameset.hpp>
 
 #include <opencv2/opencv.hpp>
 #include <vector>
@@ -11,37 +15,99 @@ namespace rgbd {
 
 class Source;
 
-struct FrameSet {
-	int64_t timestamp;
-	std::vector<Source*> sources;
-	std::vector<cv::Mat> channel1;
-	std::vector<cv::Mat> channel2;
-	int count;
-	unsigned int mask;
-};
-
+// Allows a latency of 20 frames maximum
 static const size_t kFrameBufferSize = 20;
 
+/**
+ * Manage a group of RGB-D sources to obtain synchronised sets of frames from
+ * those sources. The Group class provides a synchronised callback mechanism
+ * that uses the high precision timer to ensure that it is called once per
+ * frame. The callback is not called if the frameset is not completed or
+ * is unavailable for some other reason. By default if the required frame is
+ * not available but there is an older frame available that has not been used
+ * then it will be used. This can be disabled. It is also possible to allow
+ * incomplete frames to be used, but this is disabled by default.
+ */
 class Group {
 	public:
 	Group();
 	~Group();
 
+	/**
+	 * Give this group a name for logging purposes.
+	 */
+	void setName(const std::string &name);
+
+	/**
+	 * Add a new source to the group. Framesets generated prior to the source
+	 * being added will still be valid and will not contain a frame from this
+	 * source. Sets generated after addition will require a frame from this
+	 * source.
+	 */
 	void addSource(ftl::rgbd::Source *);
 
-	void sync(int N=-1, int B=-1);
-	void sync(std::function<bool(const FrameSet &)>);
+	/**
+	 * Add another group to this one. All sources in the other group are made
+	 * available to this group in a synchronised way. There is additional
+	 * overhead in supporting this as additional data copies are required
+	 * internally for all the source frames.
+	 */
+	void addGroup(ftl::rgbd::Group *);
+
+	/**
+	 * Provide a function to be called once per frame with a valid frameset
+	 * at the specified latency. The function may not be called under certain
+	 * conditions (missing frameset). No guarantee is made about the timing
+	 * accuracy of the call, it should be close to the frame point. This
+	 * function may or may not block. It is intended that the buffers within
+	 * the frameset are swapped during the function call, meaning that the
+	 * frameset data is no longer valid upon returning.
+	 */
+	void sync(std::function<bool(FrameSet &)>);
+
+	/** @deprecated */
+	//bool getFrames(FrameSet &, bool complete=false);
 
-	bool getFrames(FrameSet &, bool complete=false);
+	/** To be deprecated in favour of ftl::timer::setInterval.
+	 */
+	//void setFPS(int fps);
+
+	/**
+	 * Set the minimum number of frames latency. The latency is from the most
+	 * recent frame obtained, meaning that the timestamp of the input frames is
+	 * the reference point, this may already be several frames old. Latency
+	 * does not correspond to actual current time.
+	 */
+	void setLatency(int frames) { latency_ = frames; }
+
+	void stop() {}
 
 	private:
 	std::vector<FrameSet> framesets_;
 	std::vector<Source*> sources_;
 	size_t head_;
-	std::function<bool(const FrameSet &)> callback_;
+	std::function<bool(FrameSet &)> callback_;
 	MUTEX mutex_;
+	int mspf_;
+	int latency_;
+	int64_t last_ts_;
+	std::atomic<int> jobs_;
+	volatile bool skip_;
+	ftl::timer::TimerHandle cap_id_;
+	ftl::timer::TimerHandle swap_id_;
+	ftl::timer::TimerHandle main_id_;
+	std::string name_;
 
+	/* Insert a new frameset into the buffer, along with all intermediate
+	 * framesets between the last in buffer and the new one.
+	 */
 	void _addFrameset(int64_t timestamp);
+
+	void _retrieveJob(ftl::rgbd::Source *);
+	void _computeJob(ftl::rgbd::Source *);
+
+	/* Find a frameset with given latency in frames. */
+	ftl::rgbd::FrameSet *_getFrameset(int f);
 };
 
 }
diff --git a/components/rgbd-sources/include/ftl/rgbd/snapshot.hpp b/components/rgbd-sources/include/ftl/rgbd/snapshot.hpp
index edec6150217a6655021b94d49ef36d6018d87374..f9bb39756362c344877df441a445a07f3e5eab37 100644
--- a/components/rgbd-sources/include/ftl/rgbd/snapshot.hpp
+++ b/components/rgbd-sources/include/ftl/rgbd/snapshot.hpp
@@ -27,16 +27,26 @@ public:
 	explicit SnapshotWriter(const std::string &filename);
 	~SnapshotWriter();
 	
-	bool addCameraParams(const std::string &name, const Eigen::Matrix4d &pose, const ftl::rgbd::Camera &params);
-	bool addCameraRGBD(const std::string &name, const cv::Mat &rgb, const cv::Mat &depth);
-	bool addMat(const std::string &name, const cv::Mat &mat, const std::string &format="tiff");
-	bool addEigenMatrix4d(const std::string &name, const Eigen::Matrix4d &m, const std::string &format="pfm");
+	void addSource(const std::string &id, const ftl::rgbd::Camera &params, const Eigen::Matrix4d &extrinsic);
+	void addSource(const std::string &id, const std::vector<double> &params, const cv::Mat &extrinsic);
+	bool addRGBD(size_t source, const cv::Mat &rgb, const cv::Mat &depth, uint64_t time=0);
+
+	bool addMat(const std::string &name, const cv::Mat &mat, const std::string &format, const std::vector<int> &params);
 	bool addFile(const std::string &name, const std::vector<uchar> &buf);
 	bool addFile(const std::string &name, const uchar *buf, const size_t len);
+	
+	void writeIndex();
 
 private:
-	struct archive *archive_;
-	struct archive_entry *entry_;
+	std::vector<std::string> sources_;
+	std::vector<std::vector<double>> params_;
+	std::vector<cv::Mat> extrinsic_;
+	std::vector<size_t> frame_idx_;
+	std::vector<std::vector<std::string>> fname_rgb_;
+	std::vector<std::vector<std::string>> fname_depth_;
+
+	struct archive *archive_ = nullptr;
+	struct archive_entry *entry_ = nullptr;
 };
 
 class SnapshotStreamWriter {
@@ -59,35 +69,47 @@ private:
 	void run();
 };
 
-struct SnapshotEntry {
-	long t;
-	cv::Mat rgb;
-	cv::Mat depth;
-	Eigen::Matrix4d pose;
-	ftl::rgbd::Camera params;
-	uint status;
-	SnapshotEntry() : status(1+2+4+8) {};
+class Snapshot {
+public:
+	size_t getSourcesCount();
+	size_t getFramesCount();
+	
+	std::string getSourceURI(size_t camera);
+	ftl::rgbd::Camera getParameters(size_t camera);
+	void getPose(size_t camera, cv::Mat &out);
+	void getPose(size_t camera, Eigen::Matrix4d &out);
+
+	void getLeftRGB(size_t camera, size_t frame, cv::Mat &data);
+	void getLeftDepth(size_t camera, size_t frame, cv::Mat &data);
+
+	size_t n_frames;
+	size_t n_cameras;
+
+	std::vector<std::string> sources;
+	std::vector<ftl::rgbd::Camera> parameters;
+	std::vector<cv::Mat> extrinsic;
+	std::vector<std::vector<cv::Mat>> rgb_left;
+	std::vector<std::vector<cv::Mat>> depth_left;
 };
 
 class SnapshotReader {
 public:
 	explicit SnapshotReader(const std::string &filename);
 	~SnapshotReader();
-	
-	bool getCameraRGBD(const std::string &id, cv::Mat &rgb, cv::Mat &depth, Eigen::Matrix4d &pose, ftl::rgbd::Camera &params);
-	std::vector<std::string> getIds();
+
+	Snapshot readArchive();
 
 private:
-	SnapshotEntry& getEntry(const std::string &id);
 	bool readEntry(std::vector<uchar> &data);
-	bool readArchive();
 
-	std::map<std::string, SnapshotEntry> data_;
+	bool getDepth(const std::string &name, cv::Mat &data);
+	bool getRGB(const std::string &name, cv::Mat &data);
+
+	std::map<std::string, std::vector<uchar>> files_;
 	struct archive *archive_;
 	struct archive_entry *entry_;
 };
 
-
 };
 };
 
diff --git a/components/rgbd-sources/include/ftl/rgbd/source.hpp b/components/rgbd-sources/include/ftl/rgbd/source.hpp
index 4efb97175e886ba821f752f7f154eddde0aa1f8f..0ee163add0009023ec24e6df6bd18a1da927af1e 100644
--- a/components/rgbd-sources/include/ftl/rgbd/source.hpp
+++ b/components/rgbd-sources/include/ftl/rgbd/source.hpp
@@ -1,10 +1,11 @@
 #ifndef _FTL_RGBD_SOURCE_HPP_
 #define _FTL_RGBD_SOURCE_HPP_
 
+#include <ftl/cuda_util.hpp>
 #include <ftl/configuration.hpp>
 #include <ftl/rgbd/camera.hpp>
 #include <ftl/threads.hpp>
-//#include <ftl/net/universe.hpp>
+#include <ftl/net/universe.hpp>
 #include <ftl/uri.hpp>
 #include <ftl/rgbd/detail/source.hpp>
 #include <opencv2/opencv.hpp>
@@ -12,6 +13,7 @@
 #include <string>
 
 #include <ftl/cuda_common.hpp>
+#include <ftl/rgbd/frame.hpp>
 
 namespace ftl {
 
@@ -24,6 +26,7 @@ namespace rgbd {
 static inline bool isValidDepth(float d) { return (d > 0.01f) && (d < 39.99f); }
 
 class SnapshotReader;
+class VirtualSource;
 
 /**
  * RGBD Generic data source configurable entity. This class hides the
@@ -38,6 +41,7 @@ class Source : public ftl::Configurable {
 	public:
 	template <typename T, typename... ARGS>
 	friend T *ftl::config::create(ftl::config::json_t &, ARGS ...);
+	friend class VirtualSource;
 
 	//template <typename T, typename... ARGS>
 	//friend T *ftl::config::create(ftl::Configurable *, const std::string &, ARGS ...);
@@ -49,11 +53,11 @@ class Source : public ftl::Configurable {
 	Source(const Source&)=delete;
 	Source &operator=(const Source&) =delete;
 
-	private:
+	protected:
 	explicit Source(ftl::config::json_t &cfg);
 	Source(ftl::config::json_t &cfg, ftl::rgbd::SnapshotReader *);
 	Source(ftl::config::json_t &cfg, ftl::net::Universe *net);
-	~Source();
+	virtual ~Source();
 
 	public:
 	/**
@@ -64,26 +68,48 @@ class Source : public ftl::Configurable {
 	/**
 	 * Change the second channel source.
 	 */
-	bool setChannel(channel_t c);
+	bool setChannel(ftl::rgbd::Channel c);
 
-	channel_t getChannel() const { return channel_; }
+	ftl::rgbd::Channel getChannel() const { return channel_; }
 
 	/**
-	 * Perform the hardware or virtual frame grab operation. 
+	 * Perform the hardware or virtual frame grab operation. This should be
+	 * fast and non-blocking. 
 	 */
-	bool grab(int N=-1, int B=-1);
+	bool capture(int64_t ts);
+
+	/**
+	 * Download captured frame. This could be a blocking IO operation.
+	 */
+	bool retrieve();
+
+	/**
+	 * Between frames, do any required buffer swaps.
+	 */
+	void swap() { if (impl_) impl_->swap(); }
 
 	/**
 	 * Do any post-grab processing. This function
 	 * may take considerable time to return, especially for sources requiring
-	 * software stereo correspondance. If `process` is not called manually
-	 * after a `grab` and before a `get`, then it will be called automatically
-	 * on first `get`.
+	 * software stereo correspondance.
+	 */
+	bool compute(int N=-1, int B=-1);
+
+	/**
+	 * Wrapper grab that performs capture, swap and computation steps in one.
+	 * It is more optimal to perform capture and compute in parallel.
 	 */
-	//void process();
+	bool grab(int N=-1, int B=-1) {
+		bool c = capture(0);
+		c = c && retrieve();
+		swap();
+		return c && compute(N,B);
+	}
 
 	/**
-	 * Get a copy of both colour and depth frames.
+	 * Get a copy of both colour and depth frames. Note that this does a buffer
+	 * swap rather than a copy, so the parameters should be persistent buffers for
+	 * best performance.
 	 */
 	void getFrames(cv::Mat &c, cv::Mat &d);
 
@@ -97,17 +123,6 @@ class Source : public ftl::Configurable {
 	 */
 	void getDepth(cv::Mat &d);
 
-	/**
-	 * Write frames into source buffers from an external renderer. Virtual
-	 * sources do not have an internal generator of frames but instead have
-	 * their data provided from an external rendering class. This function only
-	 * works when there is no internal generator.
-	 */
-	void writeFrames(const cv::Mat &rgb, const cv::Mat &depth);
-	void writeFrames(const ftl::cuda::TextureObject<uchar4> &rgb, const ftl::cuda::TextureObject<uint> &depth, cudaStream_t stream);
-	void writeFrames(const ftl::cuda::TextureObject<uchar4> &rgb, const ftl::cuda::TextureObject<float> &depth, cudaStream_t stream);
-	void writeFrames(const ftl::cuda::TextureObject<uchar4> &rgb, const ftl::cuda::TextureObject<uchar4> &rgb2, cudaStream_t stream);
-
 	int64_t timestamp() const { return timestamp_; }
 
 	/**
@@ -118,6 +133,8 @@ class Source : public ftl::Configurable {
 	void uploadColour(cv::cuda::GpuMat&);
 	void uploadDepth(cv::cuda::GpuMat&);
 
+	bool isVirtual() const { return impl_ == nullptr; }
+
 	/**
 	 * Get the source's camera intrinsics.
 	 */
@@ -126,7 +143,7 @@ class Source : public ftl::Configurable {
 		else return params_;
 	}
 
-	const Camera parameters(channel_t) const;
+	const Camera parameters(ftl::rgbd::Channel) const;
 
 	cv::Mat cameraMatrix() const;
 
@@ -183,11 +200,12 @@ class Source : public ftl::Configurable {
 
 	SHARED_MUTEX &mutex() { return mutex_; }
 
-	std::function<void(int64_t, const cv::Mat &, const cv::Mat &)> &callback() { return callback_; }
-	void setCallback(std::function<void(int64_t, const cv::Mat &, const cv::Mat &)> cb) { callback_ = cb; }
+	std::function<void(int64_t, cv::Mat &, cv::Mat &)> &callback() { return callback_; }
+	void setCallback(std::function<void(int64_t, cv::Mat &, cv::Mat &)> cb);
+	void removeCallback() { callback_ = nullptr; }
 
 
-	private:
+	protected:
 	detail::Source *impl_;
 	cv::Mat rgb_;
 	cv::Mat depth_;
@@ -198,10 +216,10 @@ class Source : public ftl::Configurable {
 	SHARED_MUTEX mutex_;
 	bool paused_;
 	bool bullet_;
-	channel_t channel_;
+	ftl::rgbd::Channel channel_;
 	cudaStream_t stream_;
 	int64_t timestamp_;
-	std::function<void(int64_t, const cv::Mat &, const cv::Mat &)> callback_;
+	std::function<void(int64_t, cv::Mat &, cv::Mat &)> callback_;
 
 	detail::Source *_createImplementation();
 	detail::Source *_createFileImpl(const ftl::URI &uri);
diff --git a/components/rgbd-sources/include/ftl/rgbd/streamer.hpp b/components/rgbd-sources/include/ftl/rgbd/streamer.hpp
index 0143bc0b0a030e509629e5bb6185dd553d1c9683..7c6e6f479afe022cdacefbabf9098e276e0c9f79 100644
--- a/components/rgbd-sources/include/ftl/rgbd/streamer.hpp
+++ b/components/rgbd-sources/include/ftl/rgbd/streamer.hpp
@@ -5,7 +5,9 @@
 #include <ftl/configuration.hpp>
 #include <ftl/configurable.hpp>
 #include <ftl/rgbd/source.hpp>
+#include <ftl/rgbd/group.hpp>
 #include <ftl/net/universe.hpp>
+#include <ftl/codecs/encoder.hpp>
 #include <ftl/threads.hpp>
 #include <string>
 #include <vector>
@@ -15,8 +17,8 @@
 namespace ftl {
 namespace rgbd {
 
-static const int kChunkDim = 4;
-static constexpr int kChunkCount = kChunkDim * kChunkDim;
+//static const int kChunkDim = 4;
+//static constexpr int kChunkCount = kChunkDim * kChunkDim;
 
 namespace detail {
 
@@ -25,23 +27,38 @@ struct StreamClient {
 	ftl::UUID peerid;
 	std::atomic<int> txcount;	// Frames sent since last request
 	int txmax;					// Frames to send in request
+	ftl::codecs::preset_t preset;
 };
 
 static const unsigned int kGrabbed = 0x1;
 static const unsigned int kRGB = 0x2;
-static const unsigned int kDepth = 0x4; 
+static const unsigned int kDepth = 0x4;
+
+static const unsigned int kFrameDropLimit = 5;
+static const unsigned int kMaxBitrateLevels = 10;
 
 struct StreamSource {
 	ftl::rgbd::Source *src;
-	std::atomic<unsigned int> jobs;				// Busy or ready to swap?
+	std::atomic<int> jobs;				// Busy or ready to swap?
 	std::atomic<unsigned int> clientCount;
+
+	int hq_count;	// Number of high quality requests
+	int lq_count;	// Number of low quality requests
+	ftl::codecs::preset_t hq_bitrate=ftl::codecs::kPresetBest;	// Max bitrate
+	ftl::codecs::preset_t lq_bitrate=ftl::codecs::kPresetWorst;	// Min bitrate
+
 	cv::Mat rgb;									// Tx buffer
 	cv::Mat depth;									// Tx buffer
 	cv::Mat prev_rgb;
 	cv::Mat prev_depth;
-	std::list<detail::StreamClient> clients[10];	// One list per bitrate
+	std::list<detail::StreamClient> clients;
 	SHARED_MUTEX mutex;
 	unsigned long long frame;
+
+	ftl::codecs::Encoder *hq_encoder_c1 = nullptr;
+	ftl::codecs::Encoder *hq_encoder_c2 = nullptr;
+	ftl::codecs::Encoder *lq_encoder_c1 = nullptr;
+	ftl::codecs::Encoder *lq_encoder_c2 = nullptr;
 };
 
 }
@@ -51,6 +68,11 @@ struct StreamSource {
  */
 static const int kMaxFrames = 100;
 
+enum encoder_t {
+	kEncodeVideo,
+	kEncodeImages
+};
+
 /**
  * Allows network streaming of a number of RGB-Depth sources. Remote machines
  * can discover available streams from an instance of Streamer. It also allows
@@ -95,6 +117,8 @@ class Streamer : public ftl::Configurable {
 
 	void wait();
 
+	void setLatency(int l) { group_.setLatency(l); }
+
 	/**
 	 * Alternative to calling run(), it will operate a single frame capture,
 	 * compress and stream cycle.
@@ -104,27 +128,40 @@ class Streamer : public ftl::Configurable {
 	Source *get(const std::string &uri);
 
 	private:
+	ftl::rgbd::Group group_;
 	std::map<std::string, detail::StreamSource*> sources_;
 	//ctpl::thread_pool pool_;
 	SHARED_MUTEX mutex_;
 	bool active_;
 	ftl::net::Universe *net_;
 	bool late_;
-	std::mutex job_mtx_;
-	std::condition_variable job_cv_;
-	std::atomic<int> jobs_;
 	int compress_level_;
 	int64_t clock_adjust_;
 	ftl::UUID time_peer_;
 	int64_t last_frame_;
 	int64_t frame_no_;
 
-	void _schedule();
-	void _swap(detail::StreamSource *);
+	encoder_t encode_mode_;
+
+	int64_t mspf_;
+	float actual_fps_;
+	//int64_t last_dropped_;
+	//int drop_count_;
+
+	ftl::timer::TimerHandle timer_job_;
+
+	ftl::codecs::device_t hq_devices_;
+
+	void _process(ftl::rgbd::FrameSet &);
+	void _cleanUp();
 	void _addClient(const std::string &source, int N, int rate, const ftl::UUID &peer, const std::string &dest);
-	void _encodeAndTransmit(detail::StreamSource *src, int chunk);
-	void _encodeChannel1(const cv::Mat &in, std::vector<unsigned char> &out, unsigned int b);
-	bool _encodeChannel2(const cv::Mat &in, std::vector<unsigned char> &out, ftl::rgbd::channel_t c, unsigned int b);
+	void _transmitPacket(detail::StreamSource *src, const ftl::codecs::Packet &pkt, int chan, bool hasChan2, bool hqonly);
+
+	//void _encodeHQAndTransmit(detail::StreamSource *src, const cv::Mat &, const cv::Mat &, int chunk);
+	//void _encodeLQAndTransmit(detail::StreamSource *src, const cv::Mat &, const cv::Mat &, int chunk);
+	//void _encodeAndTransmit(detail::StreamSource *src, ftl::codecs::Encoder *enc1, ftl::codecs::Encoder *enc2, const cv::Mat &, const cv::Mat &);
+	//void _encodeImageChannel1(const cv::Mat &in, std::vector<unsigned char> &out, unsigned int b);
+	//bool _encodeImageChannel2(const cv::Mat &in, std::vector<unsigned char> &out, ftl::rgbd::channel_t c, unsigned int b);
 };
 
 }
diff --git a/components/rgbd-sources/include/ftl/rgbd/virtual.hpp b/components/rgbd-sources/include/ftl/rgbd/virtual.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..ed3530258d64b14427802f8e56375635ab26a24a
--- /dev/null
+++ b/components/rgbd-sources/include/ftl/rgbd/virtual.hpp
@@ -0,0 +1,30 @@
+#ifndef _FTL_RGBD_VIRTUAL_HPP_
+#define _FTL_RGBD_VIRTUAL_HPP_
+
+#include <ftl/rgbd/source.hpp>
+
+namespace ftl {
+namespace rgbd {
+
+class VirtualSource : public ftl::rgbd::Source {
+    public:
+    explicit VirtualSource(ftl::config::json_t &cfg);
+	~VirtualSource();
+
+	void onRender(const std::function<void(ftl::rgbd::Frame &)> &);
+
+	void setTimestamp(int64_t ts) { timestamp_ = ts; }
+
+    /**
+	 * Write frames into source buffers from an external renderer. Virtual
+	 * sources do not have an internal generator of frames but instead have
+	 * their data provided from an external rendering class. This function only
+	 * works when there is no internal generator.
+	 */
+    //void write(int64_t ts, ftl::rgbd::Frame &frame, cudaStream_t stream=0);
+};
+
+}
+}
+
+#endif  // _FTL_RGBD_VIRTUAL_HPP_
diff --git a/components/rgbd-sources/include/qsort.h b/components/rgbd-sources/include/qsort.h
new file mode 100644
index 0000000000000000000000000000000000000000..62a76b836c1b13861fc8a5a12ff0fc0eb7936f8a
--- /dev/null
+++ b/components/rgbd-sources/include/qsort.h
@@ -0,0 +1,186 @@
+/*
+ * Copyright (c) 2013, 2017 Alexey Tourbin
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/*
+ * This is a traditional Quicksort implementation which mostly follows
+ * [Sedgewick 1978].  Sorting is performed entirely on array indices,
+ * while actual access to the array elements is abstracted out with the
+ * user-defined `LESS` and `SWAP` primitives.
+ *
+ * Synopsis:
+ *	QSORT(N, LESS, SWAP);
+ * where
+ *	N - the number of elements in A[];
+ *	LESS(i, j) - compares A[i] to A[j];
+ *	SWAP(i, j) - exchanges A[i] with A[j].
+ */
+
+#ifndef QSORT_H
+#define QSORT_H
+
+/* Sort 3 elements. */
+#define Q_SORT3(q_a1, q_a2, q_a3, Q_LESS, Q_SWAP) \
+do {					\
+    if (Q_LESS(q_a2, q_a1)) {		\
+	if (Q_LESS(q_a3, q_a2))		\
+	    Q_SWAP(q_a1, q_a3);		\
+	else {				\
+	    Q_SWAP(q_a1, q_a2);		\
+	    if (Q_LESS(q_a3, q_a2))	\
+		Q_SWAP(q_a2, q_a3);	\
+	}				\
+    }					\
+    else if (Q_LESS(q_a3, q_a2)) {	\
+	Q_SWAP(q_a2, q_a3);		\
+	if (Q_LESS(q_a2, q_a1))		\
+	    Q_SWAP(q_a1, q_a2);		\
+    }					\
+} while (0)
+
+/* Partition [q_l,q_r] around a pivot.  After partitioning,
+ * [q_l,q_j] are the elements that are less than or equal to the pivot,
+ * while [q_i,q_r] are the elements greater than or equal to the pivot. */
+#define Q_PARTITION(q_l, q_r, q_i, q_j, Q_UINT, Q_LESS, Q_SWAP)		\
+do {									\
+    /* The middle element, not to be confused with the median. */	\
+    Q_UINT q_m = q_l + ((q_r - q_l) >> 1);				\
+    /* Reorder the second, the middle, and the last items.		\
+     * As [Edelkamp Weiss 2016] explain, using the second element	\
+     * instead of the first one helps avoid bad behaviour for		\
+     * decreasingly sorted arrays.  This method is used in recent	\
+     * versions of gcc's std::sort, see gcc bug 58437#c13, although	\
+     * the details are somewhat different (cf. #c14). */		\
+    Q_SORT3(q_l + 1, q_m, q_r, Q_LESS, Q_SWAP);				\
+    /* Place the median at the beginning. */				\
+    Q_SWAP(q_l, q_m);							\
+    /* Partition [q_l+2, q_r-1] around the median which is in q_l.	\
+     * q_i and q_j are initially off by one, they get decremented	\
+     * in the do-while loops. */					\
+    q_i = q_l + 1; q_j = q_r;						\
+    while (1) {								\
+	do q_i++; while (Q_LESS(q_i, q_l));				\
+	do q_j--; while (Q_LESS(q_l, q_j));				\
+	if (q_i >= q_j) break; /* Sedgewick says "until j < i" */	\
+	Q_SWAP(q_i, q_j);						\
+    }									\
+    /* Compensate for the i==j case. */					\
+    q_i = q_j + 1;							\
+    /* Put the median to its final place. */				\
+    Q_SWAP(q_l, q_j);							\
+    /* The median is not part of the left subfile. */			\
+    q_j--;								\
+} while (0)
+
+/* Insertion sort is applied to small subfiles - this is contrary to
+ * Sedgewick's suggestion to run a separate insertion sort pass after
+ * the partitioning is done.  The reason I don't like a separate pass
+ * is that it triggers extra comparisons, because it can't see that the
+ * medians are already in their final positions and need not be rechecked.
+ * Since I do not assume that comparisons are cheap, I also do not try
+ * to eliminate the (q_j > q_l) boundary check. */
+#define Q_INSERTION_SORT(q_l, q_r, Q_UINT, Q_LESS, Q_SWAP)		\
+do {									\
+    Q_UINT q_i, q_j;							\
+    /* For each item starting with the second... */			\
+    for (q_i = q_l + 1; q_i <= q_r; q_i++)				\
+    /* move it down the array so that the first part is sorted. */	\
+    for (q_j = q_i; q_j > q_l && (Q_LESS(q_j, q_j - 1)); q_j--)		\
+	Q_SWAP(q_j, q_j - 1);						\
+} while (0)
+
+/* When the size of [q_l,q_r], i.e. q_r-q_l+1, is greater than or equal to
+ * Q_THRESH, the algorithm performs recursive partitioning.  When the size
+ * drops below Q_THRESH, the algorithm switches to insertion sort.
+ * The minimum valid value is probably 5 (with 5 items, the second and
+ * the middle items, the middle itself being rounded down, are distinct). */
+#define Q_THRESH 16
+
+/* The main loop. */
+#define Q_LOOP(Q_UINT, Q_N, Q_LESS, Q_SWAP)				\
+do {									\
+    Q_UINT q_l = 0;							\
+    Q_UINT q_r = (Q_N) - 1;						\
+    Q_UINT q_sp = 0; /* the number of frames pushed to the stack */	\
+    struct { Q_UINT q_l, q_r; }						\
+	/* On 32-bit platforms, to sort a "char[3GB+]" array,		\
+	 * it may take full 32 stack frames.  On 64-bit CPUs,		\
+	 * though, the address space is limited to 48 bits.		\
+	 * The usage is further reduced if Q_N has a 32-bit type. */	\
+	q_st[sizeof(Q_UINT) > 4 && sizeof(Q_N) > 4 ? 48 : 32];		\
+    while (1) {								\
+	if (q_r - q_l + 1 >= Q_THRESH) {				\
+	    Q_UINT q_i, q_j;						\
+	    Q_PARTITION(q_l, q_r, q_i, q_j, Q_UINT, Q_LESS, Q_SWAP);	\
+	    /* Now have two subfiles: [q_l,q_j] and [q_i,q_r].		\
+	     * Dealing with them depends on which one is bigger. */	\
+	    if (q_j - q_l >= q_r - q_i)					\
+		Q_SUBFILES(q_l, q_j, q_i, q_r);				\
+	    else							\
+		Q_SUBFILES(q_i, q_r, q_l, q_j);				\
+	}								\
+	else {								\
+	    Q_INSERTION_SORT(q_l, q_r, Q_UINT, Q_LESS, Q_SWAP);		\
+	    /* Pop subfiles from the stack, until it gets empty. */	\
+	    if (q_sp == 0) break;					\
+	    q_sp--;							\
+	    q_l = q_st[q_sp].q_l;					\
+	    q_r = q_st[q_sp].q_r;					\
+	}								\
+    }									\
+} while (0)
+
+/* The missing part: dealing with subfiles.
+ * Assumes that the first subfile is not smaller than the second. */
+#define Q_SUBFILES(q_l1, q_r1, q_l2, q_r2)				\
+do {									\
+    /* If the second subfile is only a single element, it needs		\
+     * no further processing.  The first subfile will be processed	\
+     * on the next iteration (both subfiles cannot be only a single	\
+     * element, due to Q_THRESH). */					\
+    if (q_l2 == q_r2) {							\
+	q_l = q_l1;							\
+	q_r = q_r1;							\
+    }									\
+    else {								\
+	/* Otherwise, both subfiles need processing.			\
+	 * Push the larger subfile onto the stack. */			\
+	q_st[q_sp].q_l = q_l1;						\
+	q_st[q_sp].q_r = q_r1;						\
+	q_sp++;								\
+	/* Process the smaller subfile on the next iteration. */	\
+	q_l = q_l2;							\
+	q_r = q_r2;							\
+    }									\
+} while (0)
+
+/* And now, ladies and gentlemen, may I proudly present to you... */
+#define QSORT(Q_N, Q_LESS, Q_SWAP)					\
+do {									\
+    if ((Q_N) > 1)							\
+	/* We could check sizeof(Q_N) and use "unsigned", but at least	\
+	 * on x86_64, this has the performance penalty of up to 5%. */	\
+	Q_LOOP(unsigned long, Q_N, Q_LESS, Q_SWAP);			\
+} while (0)
+
+#endif
+
+/* ex:set ts=8 sts=4 sw=4 noet: */
\ No newline at end of file
diff --git a/components/rgbd-sources/src/abr.cpp b/components/rgbd-sources/src/abr.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..c338d4725ed2ab493fd61143d24b9b7241453622
--- /dev/null
+++ b/components/rgbd-sources/src/abr.cpp
@@ -0,0 +1,120 @@
+#include <ftl/rgbd/detail/abr.hpp>
+#include <ftl/timer.hpp>
+
+#include <bitset>
+
+using ftl::rgbd::detail::BitrateSetting;
+using ftl::rgbd::detail::ABRController;
+using ftl::rgbd::detail::bitrate_t;
+using ftl::rgbd::detail::kBitrateWorst;
+using ftl::rgbd::detail::kBitrateBest;
+using ftl::rgbd::detail::bitrate_settings;
+using ftl::rgbd::detail::NetFrame;
+
+ABRController::ABRController() {
+    bitrate_ = 0;
+    enabled_ = true;
+    max_ = kBitrateBest;
+    min_ = kBitrateWorst;
+}
+
+ABRController::~ABRController() {
+
+}
+
+void ABRController::setMaximumBitrate(bitrate_t b) {
+    max_ = (b == -1) ? kBitrateBest : b;
+    if (bitrate_ < max_) bitrate_ = max_;
+}
+
+void ABRController::setMinimumBitrate(bitrate_t b) {
+    min_ = (b == -1) ? kBitrateWorst : b;
+    if (bitrate_ > min_) bitrate_ = min_;
+}
+
+void ABRController::notifyChanged() {
+    enabled_ = true;
+}
+
+bitrate_t ABRController::selectBitrate(const NetFrame &frame) {
+    if (!enabled_) return bitrate_;
+
+    float actual_mbps = (float(frame.tx_size) * 8.0f * (1000.0f / float(frame.tx_latency))) / 1048576.0f;
+    float min_mbps = (float(frame.tx_size) * 8.0f * (1000.0f / float(ftl::timer::getInterval()))) / 1048576.0f;
+    //LOG(INFO) << "Bitrate = " << actual_mbps << "Mbps, min required = " << min_mbps << "Mbps";
+    float ratio = actual_mbps / min_mbps;
+    //LOG(INFO) << "Rate Ratio = " << frame.tx_latency;
+
+    return bitrate_;
+
+    down_log_ = down_log_ << 1;
+    up_log_ = up_log_ << 1;
+
+    if (ratio < 1.2f) {
+        down_log_ += 1;
+    } else if (ratio > 1.5f) {
+        up_log_ += 1;
+    }
+
+    std::bitset<32> bd(down_log_);
+    std::bitset<32> bu(up_log_);
+
+    if (bitrate_ < min_ && int(bd.count()) - int(bu.count()) > 5) {
+        enabled_ = false;
+        down_log_ = 0;
+        up_log_ = 0;
+        bitrate_++;
+        LOG(INFO) << "Bitrate down to: " << bitrate_;
+    } else if (bitrate_ > max_ && int(bu.count()) - int(bd.count()) > 15) {
+        enabled_ = false;
+        up_log_ = 0;
+        down_log_ = 0;
+        bitrate_--;
+        LOG(INFO) << "Bitrate up to: " << bitrate_;
+    }
+
+    return bitrate_;
+}
+
+const BitrateSetting &ABRController::getBitrateInfo(bitrate_t b) {
+    if (b > kBitrateWorst) return bitrate_settings[kBitrateWorst];
+    if (b < kBitrateBest) return bitrate_settings[kBitrateBest];
+    return bitrate_settings[b];
+};
+
+int ABRController::getColourWidth(bitrate_t b) {
+    return int(std::ceil(bitrate_settings[b].colour_res * kAspectRatio)) & 0x7FFFFFFFC;
+}
+
+int ABRController::getDepthWidth(bitrate_t b) {
+    return std::ceil(bitrate_settings[b].depth_res * kAspectRatio);
+}
+
+int ABRController::getColourHeight(bitrate_t b) {
+    return bitrate_settings[b].colour_res;
+}
+
+int ABRController::getDepthHeight(bitrate_t b) {
+    return bitrate_settings[b].depth_res;
+}
+
+int ABRController::getBlockCountX(bitrate_t b) {
+    return bitrate_settings[b].block_count_x;
+}
+
+int ABRController::getBlockCountY(bitrate_t b) {
+    return bitrate_settings[b].block_count_x;
+}
+
+int ABRController::getBlockCount(bitrate_t b) {
+    const int c = bitrate_settings[b].block_count_x;
+    return c*c;
+}
+
+int ABRController::getColourQuality(bitrate_t b) {
+    return bitrate_settings[b].colour_qual;
+}
+
+int ABRController::getDepthQuality(bitrate_t b) {
+    return bitrate_settings[b].depth_qual;
+}
diff --git a/components/rgbd-sources/src/algorithms/fixstars_sgm.cpp b/components/rgbd-sources/src/algorithms/fixstars_sgm.cpp
index 73d853e9c2fb2fce7728a59c7956136b972c0ec6..5f8921bda0e562bcc26b454d750a35294ed75537 100644
--- a/components/rgbd-sources/src/algorithms/fixstars_sgm.cpp
+++ b/components/rgbd-sources/src/algorithms/fixstars_sgm.cpp
@@ -7,63 +7,135 @@
 using ftl::algorithms::FixstarsSGM;
 using cv::Mat;
 using cv::cuda::GpuMat;
+using ftl::rgbd::Channel;
+using ftl::rgbd::Format;
 
 //static ftl::Disparity::Register fixstarssgm("libsgm", FixstarsSGM::create);
 
 FixstarsSGM::FixstarsSGM(nlohmann::json &config) : Disparity(config) {
 	ssgm_ = nullptr;
+	const int width = size_.width;
+	const int height = size_.height;
+
+	CHECK((width >= 480) && (height >= 360));
+
 	uniqueness_ = value("uniqueness", 0.95f);
+	P1_ = value("P1", 10);
+	P2_ = value("P2", 120);
+
+	CHECK((uniqueness_ >= 0.0) && (uniqueness_ <= 1.0));
+	CHECK(P1_ >= 0);
+	CHECK(P2_ > P1_);
+
 	use_filter_ = value("use_filter", false);
-	filter_ = cv::cuda::createDisparityBilateralFilter(max_disp_ << 4, value("filter_radius", 25), value("filter_iter", 1));
+	if (use_filter_) {
+		int radius = value("filter_radius", 25);
+		int iter = value("filter_iter", 1);
+		CHECK(radius > 0) << "filter_radius must be greater than 0";
+		CHECK(iter > 0) << "filter_iter must be greater than 0";
+
+		filter_ = cv::cuda::createDisparityBilateralFilter(max_disp_ << 4, radius, iter);
+	}
+	
+#ifdef HAVE_OPTFLOW
+	use_off_ = value("use_off", false);
+
+	if (use_off_)
+	{
+		int off_size = value("off_size", 9);
+		double off_threshold = value("off_threshold", 0.9);
+		off_ = ftl::rgbd::OFDisparityFilter(size_, off_size, off_threshold);
+	}
+#endif
+
+	init(size_);
 }
 
-void FixstarsSGM::compute(const cv::cuda::GpuMat &l, const cv::cuda::GpuMat &r, cv::cuda::GpuMat &disp, cv::cuda::Stream &stream) {
-	cv::cuda::cvtColor(l, lbw_, cv::COLOR_BGR2GRAY, 0, stream);
-	cv::cuda::cvtColor(r, rbw_, cv::COLOR_BGR2GRAY, 0, stream);
+void FixstarsSGM::init(const cv::Size size) {
+	if (ssgm_) { delete ssgm_; }
+	lbw_ = GpuMat(size, CV_8UC1);
+	rbw_ = GpuMat(size, CV_8UC1);
+	dispt_ = GpuMat(size, CV_16SC1);
 
-	stream.waitForCompletion();
-	if (!ssgm_) { // todo: move to constructor
-		dispt_ = GpuMat(l.rows, l.cols, CV_16SC1);
-		ssgm_ = new sgm::StereoSGM(l.cols, l.rows, max_disp_, 8, 16, lbw_.step, dispt_.step / sizeof(short),
-			sgm::EXECUTE_INOUT_CUDA2CUDA, sgm::StereoSGM::Parameters(10, 120, uniqueness_, true));
+	ssgm_ = new sgm::StereoSGM(size.width, size.height, max_disp_, 8, 16,
+		lbw_.step, dispt_.step / sizeof(short),
+		sgm::EXECUTE_INOUT_CUDA2CUDA,
+		sgm::StereoSGM::Parameters(P1_, P2_, uniqueness_, true)
+	);
+}
+
+void FixstarsSGM::compute(ftl::rgbd::Frame &frame, cv::cuda::Stream &stream)
+{
+	/*if (!frame.hasChannel(ftl::rgbd::kChanLeftGray))
+	{
+		auto &rgb = frame.getChannel<GpuMat>(ftl::rgbd::kChanLeft, stream);
+		auto &gray = frame.setChannel<GpuMat>(ftl::rgbd::kChanLeftGray);
+		cv::cuda::cvtColor(rgb, gray, cv::COLOR_BGR2GRAY, 0, stream);
 	}
 
-	//auto start = std::chrono::high_resolution_clock::now();
+	if (!frame.hasChannel(ftl::rgbd::kChanRightGray))
+	{
+		auto &rgb = frame.getChannel<GpuMat>(ftl::rgbd::kChanRight, stream);
+		auto &gray = frame.setChannel<GpuMat>(ftl::rgbd::kChanRightGray);
+		cv::cuda::cvtColor(rgb, gray, cv::COLOR_BGR2GRAY, 0, stream);
+	}*/
+
+	const auto &l = frame.get<GpuMat>(Channel::Left);
+	const auto &r = frame.get<GpuMat>(Channel::Right);
+	auto &disp = frame.create<GpuMat>(Channel::Disparity, Format<float>(l.size()));
+
+	GpuMat l_scaled;
+	if (l.size() != size_)
+	{
+		GpuMat _r;
+		scaleInput(l, r, l_scaled, _r, stream);
+		cv::cuda::cvtColor(l_scaled, lbw_, cv::COLOR_BGR2GRAY, 0, stream);
+		cv::cuda::cvtColor(_r, rbw_, cv::COLOR_BGR2GRAY, 0, stream);
+	}
+	else
+	{
+		cv::cuda::cvtColor(l, lbw_, cv::COLOR_BGR2GRAY, 0, stream);
+		cv::cuda::cvtColor(r, rbw_, cv::COLOR_BGR2GRAY, 0, stream);
+	}
+
+	stream.waitForCompletion();
 	ssgm_->execute(lbw_.data, rbw_.data, dispt_.data);
-	//std::chrono::duration<double> elapsed =
-	//		std::chrono::high_resolution_clock::now() - start;
-	//LOG(INFO) << "CUDA sgm in " << elapsed.count() << "s";
-	
-	// todo: fix libSGM (return float data or provide mask separately)
-	// disparity values set to (256 << 5) in libSGM consistency check 
-	//Mat bad_pixels = (disp == (256 << 5)); 
-	
-	//disp.setTo(0, bad_pixels, stream_);
 	GpuMat left_pixels(dispt_, cv::Rect(0, 0, max_disp_, dispt_.rows));
 	left_pixels.setTo(0);
-
 	cv::cuda::threshold(dispt_, dispt_, 4096.0f, 0.0f, cv::THRESH_TOZERO_INV, stream);
 
-	if (use_filter_) {
-		// parameters need benchmarking, impact of image
-		// quick tests show with parameters (max_disp_, 25, 3)
-		// roughly 50% in disparity calculation and 50% in filter;
-		filter_->apply(dispt_, l, dispt_, stream);
+	// TODO: filter could be applied after upscaling (to the upscaled disparity image)
+	if (use_filter_)
+	{
+		filter_->apply(
+			dispt_,
+			(l.size() == size_) ? l : l_scaled,
+			dispt_,
+			stream
+		);
 	}
-	
-	dispt_.convertTo(disp, CV_32F, 1.0f/16.0f, stream);
+
+	GpuMat dispt_scaled;
+	if (l.size() != size_)
+	{
+		scaleDisparity(l.size(), dispt_, dispt_scaled, stream);
+	}
+	else
+	{
+		dispt_scaled = dispt_;
+	}
+
+	dispt_scaled.convertTo(disp, CV_32F, 1.0f / 16.0f, stream);
+
+#ifdef HAVE_OPTFLOW
+	if (use_off_) { off_.filter(frame, stream); }
+#endif
 }
 
 void FixstarsSGM::setMask(Mat &mask) {
 	return; // TODO(Nick) Not needed, but also code below does not work with new GPU pipeline
 	CHECK(mask.type() == CV_8UC1) << "mask type must be CV_8U";
-	
-	if (!ssgm_) { // todo: move to constructor
-		ssgm_ = new sgm::StereoSGM(mask.cols, mask.rows, max_disp_, 8, 16,
-			sgm::EXECUTE_INOUT_HOST2HOST,
-			sgm::StereoSGM::Parameters(10, 120, uniqueness_, true));
-	}
-	
-	mask_l_ = mask;
-	ssgm_->setMask((uint8_t*) mask.data, mask.cols);
+	if (!ssgm_) { init(size_); }
+	mask_l_ = GpuMat(mask);
+	ssgm_->setMask((uint8_t*)mask.data, mask.cols);
 }
\ No newline at end of file
diff --git a/components/rgbd-sources/src/algorithms/fixstars_sgm.hpp b/components/rgbd-sources/src/algorithms/fixstars_sgm.hpp
index 95511cb2cdc9cde452d6ffe47ec4e3c9fb99c2f9..d2c0c1d2a8fd459695bb02c9f66f17e423dc9fa5 100644
--- a/components/rgbd-sources/src/algorithms/fixstars_sgm.hpp
+++ b/components/rgbd-sources/src/algorithms/fixstars_sgm.hpp
@@ -5,47 +5,59 @@
 #ifndef _FTL_ALGORITHMS_FIXSTARS_SGM_HPP_
 #define _FTL_ALGORITHMS_FIXSTARS_SGM_HPP_
 
+#include <ftl/cuda_util.hpp>
 #include <opencv2/core.hpp>
 #include <opencv2/opencv.hpp>
-#include <libsgm.h>
-#include "../disparity.hpp"
 #include <opencv2/cudastereo.hpp>
+
+#include "../disparity.hpp"
 #include <ftl/configuration.hpp>
+#include <ftl/config.h>
 
-#include "ftl/cb_segmentation.hpp"
+#include <libsgm.h>
+#include "ftl/offilter.hpp"
 
 namespace ftl {
-namespace algorithms {
-
-/**
- * Fixstars libSGM stereo matcher. 
- * @see https://github.com/fixstars/libSGM
- *
- * NOTE: We are using a modified version that supports disparity of 256.
- * @see https://github.com/knicos/libSGM
- */
-class FixstarsSGM : public ftl::rgbd::detail::Disparity {
-	public:
-	explicit FixstarsSGM(nlohmann::json &config);
-
-	void compute(const cv::cuda::GpuMat &l, const cv::cuda::GpuMat &r, cv::cuda::GpuMat &disp, cv::cuda::Stream &stream) override;
-	void setMask(cv::Mat &mask) override;
-	
-	/* Factory creator */
-	static inline Disparity *create(ftl::Configurable *p, const std::string &name) {
-		return ftl::create<FixstarsSGM>(p, name);
-	}
-
-	private:
-	float uniqueness_;
-	bool use_filter_;
-	cv::Ptr<cv::cuda::DisparityBilateralFilter> filter_;
-	sgm::StereoSGM *ssgm_;
-	cv::cuda::GpuMat lbw_;
-	cv::cuda::GpuMat rbw_;
-	cv::cuda::GpuMat dispt_;
-};
-};
+	namespace algorithms {
+
+		/**
+		 * Fixstars libSGM stereo matcher.
+		 * @see https://github.com/fixstars/libSGM
+		 *
+		 * NOTE: We are using a modified version that supports disparity of 256.
+		 * @see https://github.com/knicos/libSGM
+		 */
+		class FixstarsSGM : public ftl::rgbd::detail::Disparity {
+		public:
+			explicit FixstarsSGM(nlohmann::json &config);
+
+			void compute(ftl::rgbd::Frame &frame, cv::cuda::Stream &stream) override;
+			void setMask(cv::Mat &mask) override;
+
+			/* Factory creator */
+			static inline Disparity *create(ftl::Configurable *p, const std::string &name) {
+				return ftl::create<FixstarsSGM>(p, name);
+			}
+
+		private:
+			void init(const cv::Size size);
+
+			float uniqueness_;
+			int P1_;
+			int P2_;
+			bool use_filter_;
+			bool use_off_;
+			cv::Ptr<cv::cuda::DisparityBilateralFilter> filter_;
+			sgm::StereoSGM *ssgm_;
+			cv::cuda::GpuMat lbw_;
+			cv::cuda::GpuMat rbw_;
+			cv::cuda::GpuMat dispt_;
+
+			#ifdef HAVE_OPTFLOW
+			ftl::rgbd::OFDisparityFilter off_;
+			#endif
+		};
+	};
 };
 
 #endif  // _FTL_ALGORITHMS_FIXSTARS_SGM_HPP_
diff --git a/components/rgbd-sources/src/algorithms/offilter.cu b/components/rgbd-sources/src/algorithms/offilter.cu
new file mode 100644
index 0000000000000000000000000000000000000000..6feee5ae1daf9fc8b4b165370dbb8646b1d7b8a1
--- /dev/null
+++ b/components/rgbd-sources/src/algorithms/offilter.cu
@@ -0,0 +1,91 @@
+#include <ftl/cuda_common.hpp>
+#include <ftl/rgbd/camera.hpp>
+#include <opencv2/core/cuda_stream_accessor.hpp>
+#include <qsort.h>
+
+__device__ void quicksort(float A[], size_t n)
+{
+	float tmp;
+	#define LESS(i, j) A[i] < A[j]
+	#define SWAP(i, j) tmp = A[i], A[i] = A[j], A[j] = tmp
+	QSORT(n, LESS, SWAP);
+}
+
+template<typename T>
+__device__  static bool inline isValidDisparity(T d) { return (0.0 < d) && (d < 256.0); } // TODO
+
+static const int max_history = 32; // TODO dynamic shared memory
+
+__global__ void temporal_median_filter_kernel(
+	cv::cuda::PtrStepSz<float> disp,
+	cv::cuda::PtrStepSz<int16_t> optflow,
+	cv::cuda::PtrStepSz<float> history,
+	int n_max,
+	int16_t threshold, // fixed point 10.5
+	float granularity  // 4 for Turing
+)
+{
+	float sorted[max_history]; // TODO: dynamic shared memory
+	for (STRIDE_Y(y, disp.rows)) {
+	for (STRIDE_X(x, disp.cols)) {
+
+		int flowx = optflow(round(y / granularity), 2 * round(x / granularity));
+		int flowy = optflow(round(y / granularity), 2 * round(x / granularity) + 1);
+
+		if ((abs(flowx) + abs(flowy)) > threshold)
+		{
+			// last element in history[x][y][t]
+			history(y, (x + 1) * n_max - 1) = 0.0;
+			return;
+		}
+
+		int count = history(y, (x + 1) * n_max - 1);
+		int n = count % (n_max - 1);
+
+		if (isValidDisparity(disp(y, x)))
+		{
+			history(y, (x + 1) * n_max - 1) += 1.0;
+			count++;
+			history(y, x * n_max + n) = disp(y, x);
+		}
+
+		int n_end = count;
+		if (n_end >= n_max)	{ n_end = n_max - 1; }
+
+		if (n_end != 0)
+		{
+			for (size_t i = 0; i < n_end; i++)
+			{
+				sorted[i] = history(y, x * n_max + i);
+			}
+
+			quicksort(sorted, n_end);
+			disp(y, x) = sorted[n_end / 2];
+		}
+	}}
+}
+
+namespace ftl {
+namespace cuda {
+	
+void optflow_filter(cv::cuda::GpuMat &disp, const cv::cuda::GpuMat &optflow,
+					cv::cuda::GpuMat &history, int n, float threshold,
+					cv::cuda::Stream &stream)
+{
+	dim3 grid(1, 1, 1);
+	dim3 threads(128, 1, 1);
+	grid.x = cv::cuda::device::divUp(disp.cols, 128);
+	grid.y = cv::cuda::device::divUp(disp.rows, 1);
+
+	// TODO: dynamic shared memory
+	temporal_median_filter_kernel<<<grid, threads, 0, cv::cuda::StreamAccessor::getStream(stream)>>>
+		(	disp, optflow, history, n,
+			round(threshold * (1 << 5)),	// TODO: documentation; 10.5 format
+			4								// TODO: (4 pixels granularity for Turing)
+		);
+
+	cudaSafeCall(cudaGetLastError());
+}
+
+}
+}
\ No newline at end of file
diff --git a/components/rgbd-sources/src/bitrate_settings.hpp b/components/rgbd-sources/src/bitrate_settings.hpp
index 6b6e1508ed87dac7e24f7af94b903782e2d9a3ce..3dbd23bc10129398d878cae0501bbc73d22bb3a8 100644
--- a/components/rgbd-sources/src/bitrate_settings.hpp
+++ b/components/rgbd-sources/src/bitrate_settings.hpp
@@ -5,26 +5,6 @@ namespace ftl {
 namespace rgbd {
 namespace detail {
 
-struct BitrateSetting {
-	int width;
-	int height;
-	int jpg_quality;
-	int png_compression;
-};
-
-static const BitrateSetting bitrate_settings[] = {
-	1280, 720, 95, 1,
-	1280, 720, 95, 1,
-	1280, 720, 95, 1,
-	1280, 720, 75, 1,
-	640, 360, 95, 1,
-	640, 360, 75, 5,
-	640, 360, 50, 5,
-	320, 160, 95, 5,
-	320, 160, 75, 5,
-	320, 160, 50, 9
-};
-
 }
 }
 }
diff --git a/components/rgbd-sources/src/calibrate.cpp b/components/rgbd-sources/src/calibrate.cpp
index 17090992f36bebe355144e4ce714acf891005463..3940520d2374661449c376020cb98c5802e2c674 100644
--- a/components/rgbd-sources/src/calibrate.cpp
+++ b/components/rgbd-sources/src/calibrate.cpp
@@ -53,7 +53,7 @@ Calibrate::Calibrate(nlohmann::json &config, cv::Size image_size, cv::cuda::Stre
 	else {
 		LOG(WARNING) << "Calibration not loaded";
 	}
-	
+
 	this->on("use_intrinsics", [this](const ftl::config::Event &e) {
 		_updateIntrinsics();
 	});
@@ -70,7 +70,7 @@ bool Calibrate::_loadCalibration(cv::Size img_size, std::pair<Mat, Mat> &map1, s
 			LOG(WARNING) << "Could not open intrinsics file";
 			return false;
 		}
-		
+
 		LOG(INFO) << "Intrinsics from: " << *ifile;
 	}
 	else {
@@ -78,19 +78,24 @@ bool Calibrate::_loadCalibration(cv::Size img_size, std::pair<Mat, Mat> &map1, s
 		return false;
 	}
 
+
+	cv::Size calib_size;
 	{
 		vector<Mat> K, D;
 		fs["K"] >> K;
 		fs["D"] >> D;
-		
-		K[0].copyTo(M1_);
-		K[1].copyTo(M2_);
+		fs["resolution"] >> calib_size;
+
+		K[0].copyTo(K1_);
+		K[1].copyTo(K2_);
 		D[0].copyTo(D1_);
 		D[1].copyTo(D2_);
 	}
 
-	CHECK(M1_.size() == Size(3, 3));
-	CHECK(M2_.size() == Size(3, 3));
+	fs.release();
+
+	CHECK(K1_.size() == Size(3, 3));
+	CHECK(K2_.size() == Size(3, 3));
 	CHECK(D1_.size() == Size(5, 1));
 	CHECK(D2_.size() == Size(5, 1));
 
@@ -101,37 +106,68 @@ bool Calibrate::_loadCalibration(cv::Size img_size, std::pair<Mat, Mat> &map1, s
 			LOG(WARNING) << "Could not open extrinsics file";
 			return false;
 		}
-		
+
 		LOG(INFO) << "Extrinsics from: " << *efile;
-	} else {
+	}
+	else {
 		LOG(WARNING) << "Calibration extrinsics file not found";
 		return false;
 	}
 
 	fs["R"] >> R_;
 	fs["T"] >> T_;
+	
+	/* re-calculate rectification from camera parameters
 	fs["R1"] >> R1_;
 	fs["R2"] >> R2_;
 	fs["P1"] >> P1_;
 	fs["P2"] >> P2_;
 	fs["Q"] >> Q_;
+	*/
+	fs.release();
 
 	img_size_ = img_size;
 
+	if (calib_size.empty())
+	{
+		LOG(WARNING) << "Calibration resolution missing!";
+	}
+	else
+	{
+		double scale_x = ((double) img_size.width) / ((double) calib_size.width);
+		double scale_y = ((double) img_size.height) / ((double) calib_size.height);
+	
+		Mat scale(cv::Size(3, 3), CV_64F, 0.0);
+		scale.at<double>(0, 0) = scale_x;
+		scale.at<double>(1, 1) = scale_y;
+		scale.at<double>(2, 2) = 1.0;
+
+		K1_ = scale * K1_;
+		K2_ = scale * K2_;
+	}
+
+	double alpha = value("alpha", 0.0);
+	cv::stereoRectify(K1_, D1_, K2_, D2_, img_size_, R_, T_, R1_, R2_, P1_, P2_, Q_, 0, alpha);
+
+	/* scaling not required as rectification is performed from scaled values
+	Q_.at<double>(0, 3) = Q_.at<double>(0, 3) * scale_x;
+	Q_.at<double>(1, 3) = Q_.at<double>(1, 3) * scale_y;
+	Q_.at<double>(2, 3) = Q_.at<double>(2, 3) * scale_x; // TODO: scaling?
+	Q_.at<double>(3, 3) = Q_.at<double>(3, 3) * scale_x;
+	*/
+
 	// cv::cuda::remap() works only with CV_32FC1
-	initUndistortRectifyMap(M1_, D1_, R1_, P1_, img_size_, CV_32FC1, map1.first, map2.first);
-	initUndistortRectifyMap(M2_, D2_, R2_, P2_, img_size_, CV_32FC1, map1.second, map2.second);
+	initUndistortRectifyMap(K1_, D1_, R1_, P1_, img_size_, CV_32FC1, map1.first, map2.first);
+	initUndistortRectifyMap(K2_, D2_, R2_, P2_, img_size_, CV_32FC1, map1.second, map2.second);
 
 	return true;
 }
 
 void Calibrate::updateCalibration(const ftl::rgbd::Camera &p) {
-	std::pair<Mat, Mat> map1, map2;
-
-	Q_.at<double>(3,2) = 1.0 / p.baseline;
-	Q_.at<double>(2,3) = p.fx;
-	Q_.at<double>(0,3) = p.cx;
-	Q_.at<double>(1,3) = p.cy;
+	Q_.at<double>(3, 2) = 1.0 / p.baseline;
+	Q_.at<double>(2, 3) = p.fx;
+	Q_.at<double>(0, 3) = p.cx;
+	Q_.at<double>(1, 3) = p.cy;
 
 	// FIXME:(Nick) Update camera matrix also...
 	_updateIntrinsics();
@@ -141,7 +177,6 @@ void Calibrate::_updateIntrinsics() {
 	// TODO: pass parameters?
 
 	Mat R1, R2, P1, P2;
-	std::pair<Mat, Mat> map1, map2;
 	ftl::rgbd::Camera params();
 
 	if (this->value("use_intrinsics", true)) {
@@ -155,28 +190,30 @@ void Calibrate::_updateIntrinsics() {
 		// no rectification
 		R1 = Mat::eye(Size(3, 3), CV_64FC1);
 		R2 = R1;
-		P1 = M1_;
-		P2 = M2_;
+		P1 = Mat::zeros(Size(4, 3), CV_64FC1);
+		P2 = Mat::zeros(Size(4, 3), CV_64FC1);
+		K1_.copyTo(Mat(P1, cv::Rect(0, 0, 3, 3)));
+		K2_.copyTo(Mat(P2, cv::Rect(0, 0, 3, 3)));
 	}
 
 	// Set correct camera matrices for
 	// getCameraMatrix(), getCameraMatrixLeft(), getCameraMatrixRight()
-	C_l_ = P1;
-	C_r_ = P2;
+	Kl_ = Mat(P1, cv::Rect(0, 0, 3, 3));
+	Kr_ = Mat(P1, cv::Rect(0, 0, 3, 3));
+
+	initUndistortRectifyMap(K1_, D1_, R1, P1, img_size_, CV_32FC1, map1_.first, map2_.first);
+	initUndistortRectifyMap(K2_, D2_, R2, P2, img_size_, CV_32FC1, map1_.second, map2_.second);
 
-	initUndistortRectifyMap(M1_, D1_, R1, P1, img_size_, CV_32FC1, map1.first, map2.first);
-	initUndistortRectifyMap(M2_, D2_, R2, P2, img_size_, CV_32FC1, map1.second, map2.second);
-	
 	// CHECK Is this thread safe!!!!
-	map1_gpu_.first.upload(map1.first);
-	map1_gpu_.second.upload(map1.second);
-	map2_gpu_.first.upload(map2.first);
-	map2_gpu_.second.upload(map2.second);
+	map1_gpu_.first.upload(map1_.first);
+	map1_gpu_.second.upload(map1_.second);
+	map2_gpu_.first.upload(map2_.first);
+	map2_gpu_.second.upload(map2_.second);
 }
 
 void Calibrate::rectifyStereo(GpuMat &l, GpuMat &r, Stream &stream) {
 	// cv::cuda::remap() can not use same Mat for input and output
-	
+
 	GpuMat l_tmp(l.size(), l.type());
 	GpuMat r_tmp(r.size(), r.type());
 	cv::cuda::remap(l, l_tmp, map1_gpu_.first, map2_gpu_.first, cv::INTER_LINEAR, 0, cv::Scalar(), stream);
@@ -186,6 +223,21 @@ void Calibrate::rectifyStereo(GpuMat &l, GpuMat &r, Stream &stream) {
 	r = r_tmp;
 }
 
+void Calibrate::rectifyStereo(cv::Mat &l, cv::Mat &r) {
+	// cv::cuda::remap() can not use same Mat for input and output
+
+	cv::remap(l, l, map1_.first, map2_.first, cv::INTER_LINEAR, 0, cv::Scalar());
+	cv::remap(r, r, map1_.second, map2_.second, cv::INTER_LINEAR, 0, cv::Scalar());
+
+	/*GpuMat l_tmp(l.size(), l.type());
+	GpuMat r_tmp(r.size(), r.type());
+	cv::cuda::remap(l, l_tmp, map1_gpu_.first, map2_gpu_.first, cv::INTER_LINEAR, 0, cv::Scalar(), stream);
+	cv::cuda::remap(r, r_tmp, map1_gpu_.second, map2_gpu_.second, cv::INTER_LINEAR, 0, cv::Scalar(), stream);
+	stream.waitForCompletion();
+	l = l_tmp;
+	r = r_tmp;*/
+}
+
 bool Calibrate::isCalibrated() {
 	return calibrated_;
 }
\ No newline at end of file
diff --git a/components/rgbd-sources/src/calibrate.hpp b/components/rgbd-sources/src/calibrate.hpp
index c5f4786831edb77435a4d205702e7909517829a9..4561b90a79129dd5a0d46d9d54bd005147d766a7 100644
--- a/components/rgbd-sources/src/calibrate.hpp
+++ b/components/rgbd-sources/src/calibrate.hpp
@@ -40,6 +40,11 @@ class Calibrate : public ftl::Configurable {
 	 */
 	void rectifyStereo(cv::cuda::GpuMat &l, cv::cuda::GpuMat &r, cv::cuda::Stream &stream);
 
+	/**
+	 * Rectify and remove distortions from from images l and r using cv::remap()
+	 */
+	void rectifyStereo(cv::Mat &l, cv::Mat &r);
+
 	bool isCalibrated();
 
 	void updateCalibration(const ftl::rgbd::Camera &p);
@@ -49,8 +54,9 @@ class Calibrate : public ftl::Configurable {
 	 * a 3D point cloud.
 	 */
 	const cv::Mat &getQ() const { return Q_; }
-	const cv::Mat &getCameraMatrixLeft() { return C_l_; }
-	const cv::Mat &getCameraMatrixRight() { return C_r_; }
+
+	const cv::Mat &getCameraMatrixLeft() { return Kl_; }
+	const cv::Mat &getCameraMatrixRight() { return Kr_; }
 	const cv::Mat &getCameraMatrix() { return getCameraMatrixLeft(); }
 
 private:
@@ -60,15 +66,22 @@ private:
 	private:
 	bool calibrated_;
 
+	std::pair<cv::Mat, cv::Mat> map1_;
+	std::pair<cv::Mat, cv::Mat> map2_;
 	std::pair<cv::cuda::GpuMat, cv::cuda::GpuMat> map1_gpu_;
 	std::pair<cv::cuda::GpuMat, cv::cuda::GpuMat> map2_gpu_;
 
-	cv::Mat Q_;
+	// parameters for rectification, see cv::stereoRectify() documentation
 	cv::Mat R_, T_, R1_, P1_, R2_, P2_;
-	cv::Mat M1_, D1_, M2_, D2_;
 
-	cv::Mat C_l_;
-	cv::Mat C_r_;
+	// disparity to depth matrix
+	cv::Mat Q_;
+	
+	// intrinsic paramters and distortion coefficients
+	cv::Mat K1_, D1_, K2_, D2_;
+
+	cv::Mat Kl_;
+	cv::Mat Kr_;
 
 	cv::Size img_size_;
 };
diff --git a/components/rgbd-sources/src/cuda_algorithms.hpp b/components/rgbd-sources/src/cuda_algorithms.hpp
index 7c7d510ffebbb6ab4a42c7a2540b17d10c9a34e2..439c16cfc21fef08086bb69b7e84b1c8b49fec74 100644
--- a/components/rgbd-sources/src/cuda_algorithms.hpp
+++ b/components/rgbd-sources/src/cuda_algorithms.hpp
@@ -11,6 +11,9 @@
 namespace ftl {
 namespace cuda {
 
+	void disparity_to_depth(const cv::cuda::GpuMat &disparity, cv::cuda::GpuMat &depth,
+				const ftl::rgbd::Camera &c, cv::cuda::Stream &stream);
+
 	/**
 	 * Disparity consistency algorithm.
 	 */
@@ -38,6 +41,9 @@ namespace cuda {
 	void disparity_to_depth(const cv::cuda::GpuMat &disparity, cv::cuda::GpuMat &depth,
 				const ftl::rgbd::Camera &c, cv::cuda::Stream &stream);
 
+	void optflow_filter(cv::cuda::GpuMat &disp, const cv::cuda::GpuMat &optflow,
+						cv::cuda::GpuMat &history, int n_max, float threshold,
+						cv::cuda::Stream &stream);
 
 }
 }
diff --git a/components/rgbd-sources/src/disparity.cpp b/components/rgbd-sources/src/disparity.cpp
index e92c72de765c7dcd69da1aa279de9181e3170087..7d9089c1ab5a2e47bd26ed87591ddb411dabf7f8 100644
--- a/components/rgbd-sources/src/disparity.cpp
+++ b/components/rgbd-sources/src/disparity.cpp
@@ -15,7 +15,11 @@ std::map<std::string, std::function<Disparity*(ftl::Configurable *, const std::s
 Disparity::Disparity(nlohmann::json &config)
 	: 	ftl::Configurable(config),
 		min_disp_(value("minimum",0)),
-		max_disp_(value("maximum", 256)) {}
+		max_disp_(value("maximum", 256)),
+		size_(value("width", 1280), value("height", 720))
+	{
+
+	}
 
 Disparity *Disparity::create(ftl::Configurable *parent, const std::string &name) {
 	nlohmann::json &config = ftl::config::resolve((!parent->getConfig()[name].is_null()) ? parent->getConfig()[name] : ftl::config::resolve(parent->getConfig())[name]); // ftl::config::resolve(parent->getConfig()[name]);
@@ -37,6 +41,28 @@ void Disparity::_register(const std::string &n,
 	(*algorithms__)[n] = f;
 }
 
+void Disparity::scaleInput(	const cv::cuda::GpuMat& left_in,
+							const cv::cuda::GpuMat& right_in,
+							cv::cuda::GpuMat& left_out,
+							cv::cuda::GpuMat& right_out,
+							cv::cuda::Stream &stream)
+{
+	cv::cuda::resize(left_in, left_scaled_, size_, 0.0, 0.0, cv::INTER_CUBIC, stream);
+	left_out = left_scaled_;
+	cv::cuda::resize(right_in, right_scaled_, size_, 0.0, 0.0, cv::INTER_CUBIC, stream);
+	right_out = right_scaled_;
+}
+
+void Disparity::scaleDisparity(	const cv::Size&		new_size,
+								cv::cuda::GpuMat&	in,
+								cv::cuda::GpuMat&	out,
+								cv::cuda::Stream&	stream)
+{
+	cv::cuda::multiply(in, (double) new_size.width / (double) in.cols, in);
+	cv::cuda::resize(in, dispt_scaled_, new_size, 0.0, 0.0, cv::INTER_NEAREST, stream);
+	out = dispt_scaled_;
+}
+
 // TODO:(Nick) Add remaining algorithms
 /*
 #include "algorithms/rtcensus.hpp"
diff --git a/components/rgbd-sources/src/disparity.hpp b/components/rgbd-sources/src/disparity.hpp
index e7e78b277544afd87870e62ca5b833b15d9cfd6d..44215871d37b2944c08d072d63afd5bf871082e4 100644
--- a/components/rgbd-sources/src/disparity.hpp
+++ b/components/rgbd-sources/src/disparity.hpp
@@ -8,6 +8,7 @@
 #include <opencv2/opencv.hpp>
 #include <nlohmann/json.hpp>
 #include <ftl/configurable.hpp>
+#include <ftl/rgbd/frame.hpp>
 
 namespace ftl {
 namespace rgbd {
@@ -26,13 +27,33 @@ class Disparity : public ftl::Configurable {
 	virtual void setMinDisparity(size_t min) { min_disp_ = min; }
 	virtual void setMaxDisparity(size_t max) { max_disp_ = max; }
 	
-	virtual void setMask(cv::Mat &mask) { mask_l_ = mask; }
+	virtual void setMask(cv::Mat &mask) { mask_l_ = cv::cuda::GpuMat(mask); }
+	virtual void setMask(cv::cuda::GpuMat &mask) { mask_l_ = mask; }
 	
+	void scaleInput(const cv::cuda::GpuMat& left_in,
+					const cv::cuda::GpuMat& right_in,
+					cv::cuda::GpuMat& left_out,
+					cv::cuda::GpuMat& right_out,
+					cv::cuda::Stream &stream);
+	
+	void scaleDisparity(const cv::Size &new_size,
+						cv::cuda::GpuMat& in,
+						cv::cuda::GpuMat& out,
+						cv::cuda::Stream &stream);
+
 	/**
 	 * Pure virtual function representing the actual computation of
 	 * disparity from left and right images to be implemented.
 	 */
-	virtual void compute(const cv::cuda::GpuMat &l, const cv::cuda::GpuMat &r, cv::cuda::GpuMat &disp, cv::cuda::Stream &stream)=0;
+	virtual void compute(Frame &frame, cv::cuda::Stream &stream)=0;
+	virtual void compute(cv::cuda::GpuMat &l, cv::cuda::GpuMat &r, cv::cuda::GpuMat &disp, cv::cuda::Stream &stream)
+	{
+		// FIXME: What were these for?
+		//ftl::rgbd::Frame frame;
+		//frame.create<cv::cuda::GpuMat>(ftl::rgbd::Channel::Left) = l;
+		//frame.create<cv::cuda::GpuMat>(ftl::rgbd::Channel::Right) = r;
+		//frame.create<cv::cuda::GpuMat>(ftl::rgbd::Channel::Disparity) = disp;
+	}
 
 	/**
 	 * Factory registration class.
@@ -54,11 +75,15 @@ class Disparity : public ftl::Configurable {
 	protected:
 	static void _register(const std::string &n, std::function<Disparity*(ftl::Configurable *, const std::string &)> f);
 	
-	protected:
-	//nlohmann::json &config_;
+protected:
 	int min_disp_;
 	int max_disp_;
-	cv::Mat mask_l_;
+	cv::Size size_;
+	
+	cv::cuda::GpuMat left_scaled_;
+	cv::cuda::GpuMat right_scaled_;
+	cv::cuda::GpuMat dispt_scaled_;
+	cv::cuda::GpuMat mask_l_;
 	
 	private:
 	static std::map<std::string,std::function<Disparity*(ftl::Configurable *, const std::string &)>> *algorithms__;
@@ -69,4 +94,3 @@ class Disparity : public ftl::Configurable {
 }
 
 #endif // _FTL_DISPARITY_HPP_
-
diff --git a/components/rgbd-sources/src/frame.cpp b/components/rgbd-sources/src/frame.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a56a19355526d9cdcdf25faaecdc67c05d09469d
--- /dev/null
+++ b/components/rgbd-sources/src/frame.cpp
@@ -0,0 +1,201 @@
+
+#include <ftl/rgbd/frame.hpp>
+
+using ftl::rgbd::Frame;
+using ftl::rgbd::Channels;
+using ftl::rgbd::Channel;
+
+static cv::Mat none;
+static cv::cuda::GpuMat noneGPU;
+
+void Frame::reset() {
+	channels_.clear();
+	gpu_.clear();
+}
+
+void Frame::download(Channel c, cv::cuda::Stream stream) {
+	download(Channels(c), stream);
+}
+
+void Frame::upload(Channel c, cv::cuda::Stream stream) {
+	upload(Channels(c), stream);
+}
+
+void Frame::download(Channels c, cv::cuda::Stream stream) {
+	for (size_t i=0u; i<Channels::kMax; ++i) {
+		if (c.has(i) && channels_.has(i) && gpu_.has(i)) {
+			data_[i].gpu.download(data_[i].host, stream);
+			gpu_ -= i;
+		}
+	}
+}
+
+void Frame::upload(Channels c, cv::cuda::Stream stream) {
+	for (size_t i=0u; i<Channels::kMax; ++i) {
+		if (c.has(i) && channels_.has(i) && !gpu_.has(i)) {
+			data_[i].gpu.upload(data_[i].host, stream);
+			gpu_ += i;
+		}
+	}
+}
+
+bool Frame::empty(ftl::rgbd::Channels channels) {
+	for (auto c : channels) {
+		if (empty(c)) return true;
+	}
+	return false;
+}
+
+void Frame::swapTo(ftl::rgbd::Channels channels, Frame &f) {
+	f.reset();
+
+	// 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);
+		}
+	}
+}
+
+template<> cv::Mat& Frame::get(ftl::rgbd::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("Frame channel does not exist");
+	}
+
+	return _get(channel).host;
+}
+
+template<> cv::cuda::GpuMat& Frame::get(ftl::rgbd::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("Frame channel does not exist");
+	}
+
+	return _get(channel).gpu;
+}
+
+template<> const cv::Mat& Frame::get(ftl::rgbd::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("Frame channel does not exist");
+
+	return _get(channel).host;
+}
+
+template<> const cv::cuda::GpuMat& Frame::get(ftl::rgbd::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("Frame channel does not exist");
+	}
+
+	return _get(channel).gpu;
+}
+
+template <> cv::Mat &Frame::create(ftl::rgbd::Channel c, const ftl::rgbd::FormatBase &f) {
+	if (c == Channel::None) {
+		throw ftl::exception("Cannot create a None channel");
+	}
+	channels_ += c;
+	gpu_ -= c;
+
+	auto &m = _get(c).host;
+
+	if (!f.empty()) {
+		m.create(f.size(), f.cvType);
+	}
+
+	return m;
+}
+
+template <> cv::cuda::GpuMat &Frame::create(ftl::rgbd::Channel c, const ftl::rgbd::FormatBase &f) {
+	if (c == Channel::None) {
+		throw ftl::exception("Cannot create a None channel");
+	}
+	channels_ += c;
+	gpu_ += c;
+
+	auto &m = _get(c).gpu;
+
+	if (!f.empty()) {
+		m.create(f.size(), f.cvType);
+	}
+
+	return m;
+}
+
+template <> cv::Mat &Frame::create(ftl::rgbd::Channel c) {
+	if (c == Channel::None) {
+		throw ftl::exception("Cannot create a None channel");
+	}
+	channels_ += c;
+	gpu_ -= c;
+
+	auto &m = _get(c).host;
+	return m;
+}
+
+template <> cv::cuda::GpuMat &Frame::create(ftl::rgbd::Channel c) {
+	if (c == Channel::None) {
+		throw ftl::exception("Cannot create a None channel");
+	}
+	channels_ += c;
+	gpu_ += c;
+
+	auto &m = _get(c).gpu;
+	return m;
+}
+
diff --git a/components/rgbd-sources/src/frameset.cpp b/components/rgbd-sources/src/frameset.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..9b9a807d8599c23141b6c3806546bf8038ef30f9
--- /dev/null
+++ b/components/rgbd-sources/src/frameset.cpp
@@ -0,0 +1,39 @@
+#include <ftl/rgbd/frameset.hpp>
+
+using ftl::rgbd::FrameSet;
+using ftl::rgbd::Channels;
+using ftl::rgbd::Channel;
+
+void FrameSet::upload(ftl::rgbd::Channels c, cudaStream_t stream) {
+	for (auto &f : frames) {
+		f.upload(c, stream);
+	}
+}
+
+void FrameSet::download(ftl::rgbd::Channels c, cudaStream_t stream) {
+	for (auto &f : frames) {
+		f.download(c, stream);
+	}
+}
+
+void FrameSet::swapTo(ftl::rgbd::FrameSet &fs) {
+	UNIQUE_LOCK(fs.mtx, lk);
+
+	//if (fs.frames.size() != frames.size()) {
+		// Assume "this" is correct and "fs" is not.
+		fs.sources.clear();
+		for (auto s : sources) fs.sources.push_back(s);
+		fs.frames.resize(frames.size());
+	//}
+
+	fs.timestamp = timestamp;
+	fs.count = static_cast<int>(count);
+	fs.stale = stale;
+	fs.mask = static_cast<unsigned int>(mask);
+
+	for (size_t i=0; i<frames.size(); ++i) {
+		frames[i].swapTo(Channels::All(), fs.frames[i]);
+	}
+
+	stale = true;
+}
diff --git a/components/rgbd-sources/src/group.cpp b/components/rgbd-sources/src/group.cpp
index f548806d5b78b00428269f1f4c7d7c5bced8a197..96ca3a82fd3e306656f047d4971ce4a29b00fe48 100644
--- a/components/rgbd-sources/src/group.cpp
+++ b/components/rgbd-sources/src/group.cpp
@@ -1,26 +1,62 @@
 #include <ftl/rgbd/group.hpp>
 #include <ftl/rgbd/source.hpp>
+#include <ftl/timer.hpp>
+
+#include <chrono>
 
 using ftl::rgbd::Group;
 using ftl::rgbd::Source;
 using ftl::rgbd::kFrameBufferSize;
 using std::vector;
+using std::chrono::milliseconds;
+using std::this_thread::sleep_for;
+using ftl::rgbd::Channel;
 
 Group::Group() : framesets_(kFrameBufferSize), head_(0) {
 	framesets_[0].timestamp = -1;
+	jobs_ = 0;
+	skip_ = false;
+	//setFPS(20);
+
+	mspf_ = ftl::timer::getInterval();
+	name_ = "NoName";
+
+	setLatency(5);
 }
 
 Group::~Group() {
+	for (auto s : sources_) {
+		s->removeCallback();
+	}
 
+	main_id_.cancel();
+	swap_id_.cancel();
+	cap_id_.cancel();
+
+	UNIQUE_LOCK(mutex_, lk);
+	// Make sure all jobs have finished
+	while (jobs_ > 0) {
+		sleep_for(milliseconds(10));
+	}
 }
 
+//void Group::setFPS(int fps) {
+//	mspf_ = 1000 / fps;
+//	ftl::timer::setInterval(mspf_);
+//}
+
 void Group::addSource(ftl::rgbd::Source *src) {
 	UNIQUE_LOCK(mutex_, lk);
 	size_t ix = sources_.size();
 	sources_.push_back(src);
 
-	src->setCallback([this,ix](int64_t timestamp, const cv::Mat &rgb, const cv::Mat &depth) {
+	src->setCallback([this,ix,src](int64_t timestamp, cv::Mat &rgb, cv::Mat &depth) {
 		if (timestamp == 0) return;
+
+		auto chan = src->getChannel();
+
+		//LOG(INFO) << "SRC CB: " << timestamp << " (" << framesets_[head_].timestamp << ")";
+
 		UNIQUE_LOCK(mutex_, lk);
 		if (timestamp > framesets_[head_].timestamp) {
 			// Add new frameset
@@ -35,79 +71,257 @@ void Group::addSource(ftl::rgbd::Source *src) {
 		for (size_t i=0; i<kFrameBufferSize; ++i) {
 			FrameSet &fs = framesets_[(head_+kFrameBufferSize-i) % kFrameBufferSize];
 			if (fs.timestamp == timestamp) {
+				lk.unlock();
+				SHARED_LOCK(fs.mtx, lk2);
+
 				//LOG(INFO) << "Adding frame: " << ix << " for " << timestamp;
-				rgb.copyTo(fs.channel1[ix]);
-				depth.copyTo(fs.channel2[ix]);
+				// Ensure channels match source mat format
+				//fs.channel1[ix].create(rgb.size(), rgb.type());
+				//fs.channel2[ix].create(depth.size(), depth.type());
+				fs.frames[ix].create<cv::Mat>(Channel::Colour, Format<uchar3>(rgb.size())); //.create(rgb.size(), rgb.type());
+				if (chan != Channel::None) fs.frames[ix].create<cv::Mat>(chan, ftl::rgbd::FormatBase(depth.cols, depth.rows, depth.type())); //.create(depth.size(), depth.type());
+
+				//cv::swap(rgb, fs.channel1[ix]);
+				//cv::swap(depth, fs.channel2[ix]);
+				cv::swap(rgb, fs.frames[ix].get<cv::Mat>(Channel::Colour));
+				if (chan != Channel::None) cv::swap(depth, fs.frames[ix].get<cv::Mat>(chan));
+
 				++fs.count;
 				fs.mask |= (1 << ix);
 
+				if (fs.count == sources_.size()) {
+					//LOG(INFO) << "COMPLETE SET: " << fs.timestamp;
+				} else if (fs.count > sources_.size()) {
+					LOG(ERROR) << "Too many frames for frame set: " << fs.timestamp << " sources=" << sources_.size();
+				} else {
+					//LOG(INFO) << "INCOMPLETE SET ("  << ix << "): " << fs.timestamp;
+				}
+
 				if (callback_ && fs.count == sources_.size()) {
-					//LOG(INFO) << "DOING CALLBACK";
-					if (callback_(fs)) {
-						//sources_[ix]->grab();
-						//LOG(INFO) << "GRAB";
+					try {
+						if (callback_(fs)) {
+							// TODO: Remove callback if returns false?
+						}
+					} catch (...) {
+						LOG(ERROR) << "Exception in group callback";
 					}
+
+					// Reset count to prevent multiple reads of these frames
+					//fs.count = 0;
 				}
 
 				return;
 			}
 		}
-		LOG(WARNING) << "Frame timestamp not found in buffer";
+		DLOG(WARNING) << "Frame timestamp not found in buffer";
 	});
 }
 
-// TODO: This should be a callback
-// Callback returns true if it wishes to continue receiving frames.
-void Group::sync(int N, int B) {
-	for (auto s : sources_) {
-		s->grab(N,B);
+void Group::addGroup(Group *grp) {
+	
+}
+
+void Group::_retrieveJob(ftl::rgbd::Source *src) {
+	try {
+		src->retrieve();
+	} catch (std::exception &ex) {
+		LOG(ERROR) << "Exception when retrieving frame";
+		LOG(ERROR) << ex.what();
+	}
+	catch (...) {
+		LOG(ERROR) << "Unknown exception when retrieving frame";
 	}
 }
 
-void Group::sync(std::function<bool(const ftl::rgbd::FrameSet &)> cb) {
-	callback_ = cb;
-	sync(-1,-1);
+void Group::_computeJob(ftl::rgbd::Source *src) {
+	try {
+		src->compute();
+	} catch (std::exception &ex) {
+		LOG(ERROR) << "Exception when computing frame";
+		LOG(ERROR) << ex.what();
+	}
+	catch (...) {
+		LOG(ERROR) << "Unknown exception when computing frame";
+	}
 }
 
-bool Group::getFrames(ftl::rgbd::FrameSet &fs, bool complete) {
-	// Use oldest frameset or search back until first complete set is found?
-	if (complete) {
-		UNIQUE_LOCK(mutex_, lk);
-		// Search backwards to find match
-		for (size_t i=0; i<kFrameBufferSize; ++i) {
-			FrameSet &f = framesets_[(head_+kFrameBufferSize-i) % kFrameBufferSize];
-			if (f.count == sources_.size()) {
-				LOG(INFO) << "Complete set found";
-				fs = f;  // FIXME: This needs to move or copy safely...
-				return true;
+void Group::sync(std::function<bool(ftl::rgbd::FrameSet &)> cb) {
+	if (latency_ == 0) {
+		callback_ = cb;
+	}
+
+	// 1. Capture camera frames with high precision
+	cap_id_ = ftl::timer::add(ftl::timer::kTimerHighPrecision, [this](int64_t ts) {
+		skip_ = jobs_ != 0;  // Last frame not finished so skip all steps
+
+		if (skip_) return true;
+
+		last_ts_ = ts;
+		for (auto s : sources_) {
+			s->capture(ts);
+		}
+
+		return true;
+	});
+
+	// 2. After capture, swap any internal source double buffers
+	swap_id_ = ftl::timer::add(ftl::timer::kTimerSwap, [this](int64_t ts) {
+		if (skip_) return true;
+		for (auto s : sources_) {
+			s->swap();
+		}
+		return true;
+	});
+
+	// 3. Issue IO retrieve ad compute jobs before finding a valid
+	// frame at required latency to pass to callback.
+	main_id_ = ftl::timer::add(ftl::timer::kTimerMain, [this,cb](int64_t ts) {
+		if (skip_) return true;
+		jobs_++;
+
+		for (auto s : sources_) {
+			jobs_ += 2;
+
+			ftl::pool.push([this,s](int id) {
+				_retrieveJob(s);
+				--jobs_;
+			});
+			ftl::pool.push([this,s](int id) {
+				_computeJob(s);
+				--jobs_;
+			});
+		}
+
+		// Find a previous frameset and specified latency and do the sync
+		// callback with that frameset.
+		if (latency_ > 0) {
+			ftl::rgbd::FrameSet *fs = nullptr;
+	
+			UNIQUE_LOCK(mutex_, lk);
+			fs = _getFrameset(latency_);
+
+			if (fs) {
+				UNIQUE_LOCK(fs->mtx, lk2);
+				lk.unlock();
+
+				try {
+					cb(*fs);
+					//LOG(INFO) << "Frameset processed (" << name_ << "): " << fs->timestamp;
+				} catch(...) {
+					LOG(ERROR) << "Exception in group sync callback";
+				}
+
+				// The buffers are invalid after callback so mark stale
+				fs->stale = true;
+			} else {
+				//LOG(INFO) << "NO FRAME FOUND: " << last_ts_ - latency_*mspf_;
 			}
 		}
-		LOG(WARNING) << "No complete frame set found";
-		return false;
-	}
 
-	return false;
+		jobs_--;
+		return true;
+	});
+
+	ftl::timer::start(true);
+}
+
+//ftl::rgbd::FrameSet &Group::_getRelativeFrameset(int rel) {
+//	int idx = (rel < 0) ? (head_+kFrameBufferSize+rel)%kFrameBufferSize : (head_+rel)%kFrameBufferSize;
+//	return framesets_[idx];
+//}
+
+ftl::rgbd::FrameSet *Group::_getFrameset(int f) {
+	const int64_t lookfor = last_ts_-f*mspf_;
+
+	for (size_t i=1; i<kFrameBufferSize; ++i) {
+		int idx = (head_+kFrameBufferSize-i)%kFrameBufferSize;
+
+		if (framesets_[idx].timestamp == lookfor && framesets_[idx].count != sources_.size()) {
+			LOG(INFO) << "Required frame not complete (timestamp="  << (framesets_[idx].timestamp) << " buffer=" << i << ")";
+			//framesets_[idx].stale = true;
+			continue;
+		}
+
+		if (framesets_[idx].stale) return nullptr;
+
+		if (framesets_[idx].timestamp == lookfor && framesets_[idx].count == sources_.size()) {
+			//framesets_[idx].stale = false;
+			return &framesets_[idx];
+		} else if (framesets_[idx].timestamp < lookfor && framesets_[idx].count == sources_.size()) {
+			//framesets_[idx].stale = true;
+			return &framesets_[idx];
+		}
+
+	}
+	return nullptr;
 }
 
 void Group::_addFrameset(int64_t timestamp) {
-	int count = (framesets_[head_].timestamp == -1) ? 1 : (timestamp - framesets_[head_].timestamp) / 40;
-	// Must make sure to also insert missing framesets
-	//LOG(INFO) << "Adding " << count << " framesets for " << timestamp << " head=" << framesets_[head_].timestamp;
+	int count = (framesets_[head_].timestamp == -1) ? 200 : (timestamp - framesets_[head_].timestamp) / mspf_;
+	//LOG(INFO) << "Massive timestamp difference: " << count;
+
+	// Allow for massive timestamp changes (Windows clock adjust)
+	// Only add a single frameset for large changes
+	if (count < -int(kFrameBufferSize) || count >= kFrameBufferSize-1) {
+		head_ = (head_+1) % kFrameBufferSize;
 
-	//if (count > 10 || count < 1) return;
+		#ifdef DEBUG_MUTEX
+		std::unique_lock<std::shared_timed_mutex> lk(framesets_[head_].mtx, std::defer_lock);
+		#else
+		std::unique_lock<std::shared_mutex> lk(framesets_[head_].mtx, std::defer_lock);
+		#endif
+		if (!lk.try_lock()) {
+			LOG(ERROR) << "Frameset in use!!";
+			return;
+		}
+		framesets_[head_].timestamp = timestamp;
+		framesets_[head_].count = 0;
+		framesets_[head_].mask = 0;
+		framesets_[head_].stale = false;
+		//framesets_[head_].channel1.resize(sources_.size());
+		//framesets_[head_].channel2.resize(sources_.size());
+		framesets_[head_].frames.resize(sources_.size());
 
+		if (framesets_[head_].sources.size() != sources_.size()) {
+			framesets_[head_].sources.clear();
+			for (auto s : sources_) framesets_[head_].sources.push_back(s);
+		}
+		return;
+	}
+
+	if (count < 1) return;
+
+	// Must make sure to also insert missing framesets
 	for (int i=0; i<count; ++i) {
-		int64_t lt = (framesets_[head_].timestamp == -1) ? timestamp-40 : framesets_[head_].timestamp;
+		int64_t lt = (framesets_[head_].timestamp == -1) ? timestamp-mspf_ : framesets_[head_].timestamp;
 		head_ = (head_+1) % kFrameBufferSize;
-		framesets_[head_].timestamp = lt+40;
+
+		#ifdef DEBUG_MUTEX
+		std::unique_lock<std::shared_timed_mutex> lk(framesets_[head_].mtx, std::defer_lock);
+		#else
+		std::unique_lock<std::shared_mutex> lk(framesets_[head_].mtx, std::defer_lock);
+		#endif
+		if (!lk.try_lock()) {
+			LOG(ERROR) << "Frameset in use!! (" << name_ << ") " << framesets_[head_].timestamp << " stale=" << framesets_[head_].stale;
+			continue;
+		}
+		framesets_[head_].timestamp = lt+mspf_;
 		framesets_[head_].count = 0;
 		framesets_[head_].mask = 0;
-		framesets_[head_].channel1.resize(sources_.size());
-		framesets_[head_].channel2.resize(sources_.size());
+		framesets_[head_].stale = false;
+		//framesets_[head_].channel1.resize(sources_.size());
+		//framesets_[head_].channel2.resize(sources_.size());
+		framesets_[head_].frames.resize(sources_.size());
 
-		framesets_[head_].sources.clear();
-		for (auto s : sources_) framesets_[head_].sources.push_back(s);
+		if (framesets_[head_].sources.size() != sources_.size()) {
+			framesets_[head_].sources.clear();
+			for (auto s : sources_) framesets_[head_].sources.push_back(s);
+		}
 	}
 }
 
+void Group::setName(const std::string &name) {
+	name_ = name;
+}
+
 
diff --git a/components/rgbd-sources/src/image.hpp b/components/rgbd-sources/src/image.hpp
index b389fd176ec246db3ca51180589673817897eb03..2e2391b7cb95b0c7b120d2992cf3e66c91ad3a1a 100644
--- a/components/rgbd-sources/src/image.hpp
+++ b/components/rgbd-sources/src/image.hpp
@@ -14,7 +14,9 @@ class ImageSource : public ftl::rgbd::detail::Source {
 
 	}
 
-	bool grab(int n, int b) { return false; };
+	bool capture(int64_t ts) { timestamp_ = ts; return true; }
+	bool retrieve() { return true; }
+	bool compute(int n, int b) { return false; };
 	bool isReady() { return false; };
 };
 
diff --git a/components/rgbd-sources/src/local.cpp b/components/rgbd-sources/src/local.cpp
index 410cb9f96c0a1c1ca48cadd52300e25b260c3aad..03b80ff52f60c95d374cfee289c0b3880bb766bd 100644
--- a/components/rgbd-sources/src/local.cpp
+++ b/components/rgbd-sources/src/local.cpp
@@ -9,11 +9,13 @@
 #include <thread>
 
 #include "local.hpp"
+#include "calibrate.hpp"
 #include <opencv2/core.hpp>
 #include <opencv2/opencv.hpp>
 #include <opencv2/xphoto.hpp>
 
 using ftl::rgbd::detail::LocalSource;
+using ftl::rgbd::detail::Calibrate;
 using cv::Mat;
 using cv::VideoCapture;
 using cv::Rect;
@@ -27,28 +29,15 @@ using std::this_thread::sleep_for;
 LocalSource::LocalSource(nlohmann::json &config)
 		: Configurable(config), timestamp_(0.0) {
 
-	REQUIRED({
-		{"flip","Switch left and right views","boolean"},
-		{"flip_vert","Rotate image 180 degrees","boolean"},
-		{"nostereo","Force single camera mode","boolean"},
-		{"width","Pixel width of camera source","number"},
-		{"height","Pixel height of camera source","number"},
-		{"max_fps","Maximum frames per second","number"},
-		{"scale","Change the input image or video scaling","number"}
-	});
-
-	flip_ = value("flip", false);
-	flip_v_ = value("flip_vert", false);
 	nostereo_ = value("nostereo", false);
-	downsize_ = value("scale", 1.0f);
 
 	// Use cameras
 	camera_a_ = new VideoCapture;
 	LOG(INFO) << "Cameras check... ";
-	camera_a_->open((flip_) ? 1 : 0);
+	camera_a_->open(0);
 
 	if (!nostereo_) {
-		camera_b_ = new VideoCapture((flip_) ? 0 : 1);
+		camera_b_ = new VideoCapture(1);
 	} else {
 		camera_b_ = nullptr;
 	}
@@ -82,26 +71,18 @@ LocalSource::LocalSource(nlohmann::json &config)
 		stereo_ = true;
 	}
 
-	tps_ = 1.0 / value("max_fps", 25.0);
+	// Allocate page locked host memory for fast GPU transfer
+	left_hm_ = cv::cuda::HostMem(height_, width_, CV_8UC3);
+	right_hm_ = cv::cuda::HostMem(height_, width_, CV_8UC3);
 }
 
 LocalSource::LocalSource(nlohmann::json &config, const string &vid)
 	:	Configurable(config), timestamp_(0.0) {
 
-	REQUIRED({
-		{"flip","Switch left and right views","boolean"},
-		{"flip_vert","Rotate image 180 degrees","boolean"},
-		{"nostereo","Force single camera mode","boolean"},
-		{"width","Pixel width of camera source","number"},
-		{"height","Pixel height of camera source","number"},
-		{"max_fps","Maximum frames per second","number"},
-		{"scale","Change the input image or video scaling","number"}
-	});
-
-	flip_ = value("flip", false);
-	flip_v_ = value("flip_vert", false);
+	//flip_ = value("flip", false);
+	//flip_v_ = value("flip_vert", false);
 	nostereo_ = value("nostereo", false);
-	downsize_ = value("scale", 1.0f);
+	//downsize_ = value("scale", 1.0f);
 
 	if (vid == "") {
 		LOG(FATAL) << "No video file specified";
@@ -138,10 +119,14 @@ LocalSource::LocalSource(nlohmann::json &config, const string &vid)
 		stereo_ = false;
 	}
 
-	tps_ = 1.0 / value("max_fps", 25.0);
+	// Allocate page locked host memory for fast GPU transfer
+	left_hm_ = cv::cuda::HostMem(height_, width_, CV_8UC3);
+	right_hm_ = cv::cuda::HostMem(height_, width_, CV_8UC3);
+
+	//tps_ = 1.0 / value("max_fps", 25.0);
 }
 
-bool LocalSource::left(cv::Mat &l) {
+/*bool LocalSource::left(cv::Mat &l) {
 	if (!camera_a_) return false;
 
 	if (!camera_a_->grab()) {
@@ -174,9 +159,9 @@ bool LocalSource::left(cv::Mat &l) {
 	}
 
 	return true;
-}
+}*/
 
-bool LocalSource::right(cv::Mat &r) {
+/*bool LocalSource::right(cv::Mat &r) {
 	if (!camera_a_->grab()) {
 		LOG(ERROR) << "Unable to grab from camera A";
 		return false;
@@ -212,10 +197,9 @@ bool LocalSource::right(cv::Mat &r) {
 	}
 
 	return true;
-}
+}*/
 
-bool LocalSource::get(cv::cuda::GpuMat &l_out, cv::cuda::GpuMat &r_out, cv::cuda::Stream &stream) {
-	Mat l, r;
+bool LocalSource::grab() {
 	if (!camera_a_) return false;
 
 	if (!camera_a_->grab()) {
@@ -238,8 +222,20 @@ bool LocalSource::get(cv::cuda::GpuMat &l_out, cv::cuda::GpuMat &r_out, cv::cuda
 
 	timestamp_ = timestamp;
 
+	return true;
+}
+
+bool LocalSource::get(cv::cuda::GpuMat &l_out, cv::cuda::GpuMat &r_out, Calibrate *c, cv::cuda::Stream &stream) {
+	Mat l, r;
+
+	// Use page locked memory
+	l = left_hm_.createMatHeader();
+	r = right_hm_.createMatHeader();
+
+	if (!camera_a_) return false;
+
 	if (camera_b_ || !stereo_) {
-		if (!camera_a_->retrieve(left_)) {
+		if (!camera_a_->retrieve(l)) {
 			LOG(ERROR) << "Unable to read frame from camera A";
 			return false;
 		}
@@ -255,23 +251,23 @@ bool LocalSource::get(cv::cuda::GpuMat &l_out, cv::cuda::GpuMat &r_out, cv::cuda
 		}
 
 		int resx = frame.cols / 2;
-		if (flip_) {
-			r = Mat(frame, Rect(0, 0, resx, frame.rows));
-			left_ = Mat(frame, Rect(resx, 0, frame.cols-resx, frame.rows));
-		} else {
-			left_ = Mat(frame, Rect(0, 0, resx, frame.rows));
+		//if (flip_) {
+		//	r = Mat(frame, Rect(0, 0, resx, frame.rows));
+		//	l = Mat(frame, Rect(resx, 0, frame.cols-resx, frame.rows));
+		//} else {
+			l = Mat(frame, Rect(0, 0, resx, frame.rows));
 			r = Mat(frame, Rect(resx, 0, frame.cols-resx, frame.rows));
-		}
+		//}
 	}
 
-	if (downsize_ != 1.0f) {
+	/*if (downsize_ != 1.0f) {
 		// cv::cuda::resize()
 
 		cv::resize(left_, left_, cv::Size((int)(left_.cols * downsize_), (int)(left_.rows * downsize_)),
 				0, 0, cv::INTER_LINEAR);
 		cv::resize(r, r, cv::Size((int)(r.cols * downsize_), (int)(r.rows * downsize_)),
 				0, 0, cv::INTER_LINEAR);
-	}
+	}*/
 
 	// Note: this seems to be too slow on CPU...
 	/*cv::Ptr<cv::xphoto::WhiteBalancer> wb;
@@ -279,15 +275,17 @@ bool LocalSource::get(cv::cuda::GpuMat &l_out, cv::cuda::GpuMat &r_out, cv::cuda
 	wb->balanceWhite(l, l);
 	wb->balanceWhite(r, r);*/
 
-	if (flip_v_) {
+	/*if (flip_v_) {
 		Mat tl, tr;
 		cv::flip(left_, tl, 0);
 		cv::flip(r, tr, 0);
 		left_ = tl;
 		r = tr;
-	}
+	}*/
+
+	c->rectifyStereo(l, r);
 
-	l_out.upload(left_, stream);
+	l_out.upload(l, stream);
 	r_out.upload(r, stream);
 
 	return true;
diff --git a/components/rgbd-sources/src/local.hpp b/components/rgbd-sources/src/local.hpp
index e3fcb91bd585d8090cfb50a8ee83bf49dd78f98d..9f21f5cf79b86f7cdee9455052c55a829eaf0da7 100644
--- a/components/rgbd-sources/src/local.hpp
+++ b/components/rgbd-sources/src/local.hpp
@@ -15,19 +15,20 @@ namespace ftl {
 namespace rgbd {
 namespace detail {
 
+class Calibrate;
+
 class LocalSource : public Configurable {
 	public:
 	explicit LocalSource(nlohmann::json &config);
 	LocalSource(nlohmann::json &config, const std::string &vid);
 	
-	bool left(cv::Mat &m);
-	bool right(cv::Mat &m);
-	bool get(cv::cuda::GpuMat &l, cv::cuda::GpuMat &r, cv::cuda::Stream &stream);
+	//bool left(cv::Mat &m);
+	//bool right(cv::Mat &m);
+	bool grab();
+	bool get(cv::cuda::GpuMat &l, cv::cuda::GpuMat &r, Calibrate *c, cv::cuda::Stream &stream);
 
 	unsigned int width() const { return width_; }
 	unsigned int height() const { return height_; }
-
-	cv::Mat &cachedLeft() { return left_; }
 	
 	//void setFramerate(float fps);
 	//float getFramerate() const;
@@ -38,18 +39,20 @@ class LocalSource : public Configurable {
 	
 	private:
 	double timestamp_;
-	double tps_;
+	//double tps_;
 	bool stereo_;
 	//float fps_;
-	bool flip_;
-	bool flip_v_;
+	//bool flip_;
+	//bool flip_v_;
 	bool nostereo_;
-	float downsize_;
+	//float downsize_;
 	cv::VideoCapture *camera_a_;
 	cv::VideoCapture *camera_b_;
 	unsigned int width_;
 	unsigned int height_;
-	cv::Mat left_;
+
+	cv::cuda::HostMem left_hm_;
+	cv::cuda::HostMem right_hm_;
 };
 
 }
diff --git a/components/rgbd-sources/src/middlebury_source.cpp b/components/rgbd-sources/src/middlebury_source.cpp
index 6f38f7bdcf9cd9a1de8812934e8da9c460face47..e82167fdcf6b4e2bfd1a38a00b50e3cdceeb2aef 100644
--- a/components/rgbd-sources/src/middlebury_source.cpp
+++ b/components/rgbd-sources/src/middlebury_source.cpp
@@ -3,6 +3,8 @@
 #include "disparity.hpp"
 #include "cuda_algorithms.hpp"
 
+#include "cuda_algorithms.hpp"
+
 using ftl::rgbd::detail::MiddleburySource;
 using ftl::rgbd::detail::Disparity;
 using std::string;
@@ -14,14 +16,13 @@ MiddleburySource::MiddleburySource(ftl::rgbd::Source *host)
 
 static bool loadMiddleburyCalib(const std::string &filename, ftl::rgbd::Camera &params, double scaling) {
 	FILE* fp = fopen(filename.c_str(), "r");
-	char buff[512];
 	
-	float cam0[3][3];
+	float cam0[3][3] = {};
 	float cam1[3][3];
-	float doffs;
-	float baseline;
-	int width;
-	int height;
+	float doffs = 0.0f;
+	float baseline = 0.0f;
+	int width = 0;
+	int height = 0;
 	int ndisp;
 	int isint;
 	int vmin;
@@ -29,8 +30,8 @@ static bool loadMiddleburyCalib(const std::string &filename, ftl::rgbd::Camera &
 	float dyavg;
 	float dymax;
 
-	if (fp != nullptr)
-	{
+	if (fp != nullptr) {
+		char buff[512];
 		if (fgets(buff, sizeof(buff), fp) != nullptr) sscanf(buff, "cam0 = [%f %f %f; %f %f %f; %f %f %f]\n", &cam0[0][0], &cam0[0][1], &cam0[0][2], &cam0[1][0], &cam0[1][1], &cam0[1][2], &cam0[2][0], &cam0[2][1], &cam0[2][2]);
 		if (fgets(buff, sizeof(buff), fp) != nullptr) sscanf(buff, "cam1 = [%f %f %f; %f %f %f; %f %f %f]\n", &cam1[0][0], &cam1[0][1], &cam1[0][2], &cam1[1][0], &cam1[1][1], &cam1[1][2], &cam1[2][0], &cam1[2][1], &cam1[2][2]);
 		if (fgets(buff, sizeof(buff), fp) != nullptr) sscanf(buff, "doffs = %f\n", &doffs);
@@ -120,7 +121,7 @@ MiddleburySource::MiddleburySource(ftl::rgbd::Source *host, const string &dir)
 	mask_l_ = (mask_l == 0);
 
 	if (!host_->getConfig()["disparity"].is_object()) {
-		host_->getConfig()["disparity"] = {{"algorithm","libsgm"}};
+		host_->getConfig()["disparity"] = ftl::config::json_t{{"algorithm","libsgm"}};
 	}
 	
 	disp_ = Disparity::create(host_, "disparity");
@@ -142,43 +143,6 @@ MiddleburySource::MiddleburySource(ftl::rgbd::Source *host, const string &dir)
 	ready_ = true;
 }
 
-static void disparityToDepth(const cv::cuda::GpuMat &disparity, cv::cuda::GpuMat &depth,
-							 const ftl::rgbd::Camera &c, cv::cuda::Stream &stream) {
-	double val = c.baseline * c.fx;
-	cv::cuda::add(disparity, c.doffs, depth, cv::noArray(), -1, stream);
-	cv::cuda::divide(val, depth, depth, 1.0f / 1000.0f, -1, stream);
-}
-
-/*static void disparityToDepthTRUE(const cv::Mat &disp, cv::Mat &depth, const ftl::rgbd::Camera &c) {
-	using namespace cv;
-
-	double doffs = 270.821 * 0.3;
-
-	Matx44d Q(
-		1.0,0.0,0.0,c.cx,
-		0.0,1.0,0.0,c.cy,
-		0.0,0.0,0.0,c.fx,
-		0.0,0.0,1.0/c.baseline,0.0);
-
-	for( int y = 0; y < disp.rows; y++ )
-    {
-        const float* sptr = disp.ptr<float>(y);
-        float* dptr = depth.ptr<float>(y);
-
-        for( int x = 0; x < disp.cols; x++)
-        {
-            double d = sptr[x] + doffs;
-            Vec4d homg_pt = Q*Vec4d(x, y, d, 1.0);
-            auto dvec = Vec3d(homg_pt.val);
-            dvec /= homg_pt[3];
-			dptr[x] = dvec[2] / 1000.0;
-
-            //if( fabs(d-minDisparity) <= FLT_EPSILON )
-            //    dptr[x][2] = bigZ;
-        }
-    }
-}*/
-
 void MiddleburySource::_performDisparity() {
 	if (depth_tmp_.empty()) depth_tmp_ = cv::cuda::GpuMat(left_.size(), CV_32FC1);
 	if (disp_tmp_.empty()) disp_tmp_ = cv::cuda::GpuMat(left_.size(), CV_32FC1);
@@ -195,7 +159,7 @@ void MiddleburySource::_performDisparity() {
 	//disparityToDepthTRUE(depth_, depth_, params_);
 }
 
-bool MiddleburySource::grab(int n, int b) {
+bool MiddleburySource::compute(int n, int b) {
 	//_performDisparity();
 	return true;
 }
diff --git a/components/rgbd-sources/src/middlebury_source.hpp b/components/rgbd-sources/src/middlebury_source.hpp
index 5f0e2be538a069747d633e3e4b9483eb8a7a75b2..d273d23a66d67c6618c0ac4a2062a780d9a3bddb 100644
--- a/components/rgbd-sources/src/middlebury_source.hpp
+++ b/components/rgbd-sources/src/middlebury_source.hpp
@@ -15,11 +15,13 @@ class Disparity;
 
 class MiddleburySource : public detail::Source {
 	public:
-	MiddleburySource(ftl::rgbd::Source *);
+	explicit MiddleburySource(ftl::rgbd::Source *);
 	MiddleburySource(ftl::rgbd::Source *, const std::string &dir);
 	~MiddleburySource() {};
 
-	bool grab(int n, int b);
+	bool capture(int64_t ts) { timestamp_ = ts; return true; }
+	bool retrieve() { return true; }
+	bool compute(int n, int b);
 	bool isReady() { return ready_; }
 
 	private:
diff --git a/components/rgbd-sources/src/net.cpp b/components/rgbd-sources/src/net.cpp
index 42b2c8fd60ca0b505cb11e956809937fd522fe5b..ba485c9402d888be0636902f3962cce2dd1e85ed 100644
--- a/components/rgbd-sources/src/net.cpp
+++ b/components/rgbd-sources/src/net.cpp
@@ -3,11 +3,14 @@
 #include <thread>
 #include <chrono>
 #include <tuple>
+#include <bitset>
 
 #include "colour.hpp"
 
 #include <ftl/rgbd/streamer.hpp>
 
+using ftl::rgbd::detail::NetFrame;
+using ftl::rgbd::detail::NetFrameQueue;
 using ftl::rgbd::detail::NetSource;
 using ftl::net::Universe;
 using ftl::UUID;
@@ -17,8 +20,57 @@ using std::vector;
 using std::this_thread::sleep_for;
 using std::chrono::milliseconds;
 using std::tuple;
+using ftl::rgbd::Channel;
 
-bool NetSource::_getCalibration(Universe &net, const UUID &peer, const string &src, ftl::rgbd::Camera &p, ftl::rgbd::channel_t chan) {
+// ===== NetFrameQueue =========================================================
+
+NetFrameQueue::NetFrameQueue(int size) : frames_(size) {
+	for (auto &f : frames_) f.timestamp = -1;
+}
+
+NetFrameQueue::~NetFrameQueue() {
+
+}
+
+NetFrame &NetFrameQueue::getFrame(int64_t ts, const cv::Size &s, int c1type, int c2type) {
+	UNIQUE_LOCK(mtx_, lk);
+
+	// Find matching timestamp
+	for (auto &f : frames_) {
+		if (f.timestamp == ts) return f;
+	}
+
+	// No match so find an empty slot
+	for (auto &f : frames_) {
+		if (f.timestamp == -1) {
+			f.timestamp = ts;
+			f.chunk_count = 0;
+			f.chunk_total = 0;
+			f.tx_size = 0;
+			f.channel1.create(s, c1type);
+			f.channel2.create(s, c2type);
+			return f;
+		}
+	}
+
+	// No empty slot, so give a fatal error
+	for (auto &f : frames_) {
+		LOG(ERROR) << "Stale frame: " << f.timestamp << " - " << f.chunk_count;
+	}
+	LOG(FATAL) << "Net Frame Queue not large enough: " << ts;
+	// FIXME: (Nick) Could auto resize the queue.
+	return frames_[0];  // To avoid missing return error...
+}
+
+void NetFrameQueue::freeFrame(NetFrame &f) {
+	UNIQUE_LOCK(mtx_, lk);
+	f.timestamp = -1;
+}
+
+
+// ===== NetSource =============================================================
+
+bool NetSource::_getCalibration(Universe &net, const UUID &peer, const string &src, ftl::rgbd::Camera &p, ftl::rgbd::Channel chan) {
 	try {
 		while(true) {
 			auto [cap,buf] = net.call<tuple<unsigned int,vector<unsigned char>>>(peer_, "source_details", src, chan);
@@ -60,11 +112,15 @@ bool NetSource::_getCalibration(Universe &net, const UUID &peer, const string &s
 }
 
 NetSource::NetSource(ftl::rgbd::Source *host)
-		: ftl::rgbd::detail::Source(host), active_(false), minB_(9), maxN_(1), current_frame_(0) {
+		: ftl::rgbd::detail::Source(host), active_(false), minB_(9), maxN_(1), adaptive_(0), queue_(3) {
 
 	gamma_ = host->value("gamma", 1.0f);
 	temperature_ = host->value("temperature", 6500);
 	default_quality_ = host->value("quality", 0);
+	last_bitrate_ = 0;
+
+	decoder_c1_ = nullptr;
+	decoder_c2_ = nullptr;
 
 	host->on("gamma", [this,host](const ftl::config::Event&) {
 		gamma_ = host->value("gamma", 1.0f);
@@ -90,10 +146,18 @@ NetSource::NetSource(ftl::rgbd::Source *host)
 		host_->getNet()->send(peer_, "update_cfg", host_->getURI() + "/baseline", host_->getConfig()["baseline"].dump());
 	});
 
+	host->on("doffs", [this,host](const ftl::config::Event&) {
+		params_.doffs = host_->value("doffs", params_.doffs);
+		host_->getNet()->send(peer_, "update_cfg", host_->getURI() + "/doffs", host_->getConfig()["doffs"].dump());
+	});
+
 	host->on("quality", [this,host](const ftl::config::Event&) {
 		default_quality_ = host->value("quality", 0);
 	});
 
+	abr_.setMaximumBitrate(host->value("max_bitrate", -1));
+	abr_.setMinimumBitrate(host->value("min_bitrate", -1));
+
 	_updateURI();
 
 	h_ = host_->getNet()->onConnect([this](ftl::net::Peer *p) {
@@ -104,6 +168,9 @@ NetSource::NetSource(ftl::rgbd::Source *host)
 }
 
 NetSource::~NetSource() {
+	if (decoder_c1_) ftl::codecs::free(decoder_c1_);
+	if (decoder_c2_) ftl::codecs::free(decoder_c2_);
+
 	if (uri_.size() > 0) {
 		host_->getNet()->unbind(uri_);
 	}
@@ -111,111 +178,136 @@ NetSource::~NetSource() {
 	host_->getNet()->removeCallback(h_);
 }
 
-void NetSource::_recvChunk(int64_t frame, int chunk, bool delta, const vector<unsigned char> &jpg, const vector<unsigned char> &d) {
-	cv::Mat tmp_rgb, tmp_depth;
+/*void NetSource::_checkAdaptive(int64_t ts) {
+	const int64_t current = ftl::timer::get_time();
+	int64_t net_latency = current - ts;
+
+	// Only change bit rate gradually
+	if (current - last_br_change_ > ftl::rgbd::detail::kAdaptationRate) {
+		// Was this frame late?
+		if (adaptive_ < ftl::rgbd::detail::kMaxBitrateLevels && net_latency > ftl::rgbd::detail::kLatencyThreshold) {
+			slow_log_ = (slow_log_ << 1) + 1;
+			std::bitset<32> bs(slow_log_);
+
+			// Enough late frames to reduce bit rate
+			if (bs.count() > ftl::rgbd::detail::kSlowFramesThreshold) {
+				adaptive_++;
+				slow_log_ = 0;
+				last_br_change_ = current;
+				LOG(WARNING) << "Adjust bitrate to " << adaptive_;
+			}
+		// No late frames in recent history...
+		} else if (adaptive_ > 0 && slow_log_ == 0) {
+			// TODO: (Nick) Don't change bitrate up so quickly as down?
+			// Try a higher bitrate again?
+			adaptive_--;
+		}
+	}
+}*/
+
+void NetSource::_createDecoder(int chan, const ftl::codecs::Packet &pkt) {
+	UNIQUE_LOCK(mutex_,lk);
+	auto *decoder = (chan == 0) ? decoder_c1_ : decoder_c2_;
+	if (decoder) {
+		if (!decoder->accepts(pkt)) {
+			ftl::codecs::free((chan == 0) ? decoder_c1_ : decoder_c2_);
+		} else {
+			return;
+		}
+	}
+
+	if (chan == 0) {
+		decoder_c1_ = ftl::codecs::allocateDecoder(pkt);
+	} else {
+		decoder_c2_ = ftl::codecs::allocateDecoder(pkt);
+	}
+}
 
+void NetSource::_recvPacket(short ttimeoff, const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) {
+	// Capture time here for better net latency estimate
+	int64_t now = std::chrono::time_point_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now()).time_since_epoch().count();
 	if (!active_) return;
 
-	// Decode in temporary buffers to prevent long locks
-	cv::imdecode(jpg, cv::IMREAD_COLOR, &tmp_rgb);
-	if (d.size() > 0) cv::imdecode(d, cv::IMREAD_UNCHANGED, &tmp_depth);
+	const ftl::rgbd::Channel chan = host_->getChannel();
+	int rchan = spkt.channel & 0x1;
 
-	// Apply colour correction to chunk
-	ftl::rgbd::colourCorrection(tmp_rgb, gamma_, temperature_);
-
-	// Build chunk head
-	int cx = (chunk % chunks_dim_) * chunk_width_;
-	int cy = (chunk / chunks_dim_) * chunk_height_;
-
-	// Make certain last frame has finished decoding before swap
-	while (frame > current_frame_ && chunk_count_ < 16 && chunk_count_ > 0) {
-		std::this_thread::yield();
-		//std::function<void(int)> j = ftl::pool.pop();
-		//if ((bool)j) j(-1);
-		//else std::this_thread::yield();
+	// Ignore any unwanted second channel
+	if (chan == ftl::rgbd::Channel::None && rchan > 0) {
+		LOG(INFO) << "Unwanted channel";
+		//return;
+		// TODO: Allow decode to be skipped
 	}
 
-	//{
-		// A new frame has been started... finish the last one
-		if (frame > current_frame_) {
-			// Lock host to prevent grab
-			UNIQUE_LOCK(host_->mutex(),lk);
-			if (frame > current_frame_) {
-				{
-					// Lock to allow buffer swap
-					UNIQUE_LOCK(mutex_,lk2);
-
-					chunk_count_ = 0;
-
-					// Swap the double buffers
-					cv::Mat tmp;
-					tmp = rgb_;
-					rgb_ = d_rgb_;
-					d_rgb_ = tmp;
-					tmp = depth_;
-					depth_ = d_depth_;
-					d_depth_ = tmp;
-
-					timestamp_ = current_frame_*40;  // FIXME: Don't hardcode 40ms
-					current_frame_ = frame;
-				}
+	NetFrame &frame = queue_.getFrame(spkt.timestamp, cv::Size(params_.width, params_.height), CV_8UC3, (isFloatChannel(chan) ? CV_32FC1 : CV_8UC3));
 
-				if (host_->callback()) {
-					//ftl::pool.push([this](id) {
-					//	UNIQUE_LOCK(host_->mutex(),lk);
-						host_->callback()(timestamp_, rgb_, depth_);
-					//});
-				}
-			}
-		} else if (frame < current_frame_) {
-			LOG(WARNING) << "Chunk dropped";
-			if (chunk == 0) N_--;
-			return;
-		}
-	//}
+	// Update frame statistics
+	frame.tx_size += pkt.data.size();
+
+	_createDecoder(rchan, pkt);
+	auto *decoder = (rchan == 0) ? decoder_c1_ : decoder_c2_;
+	if (!decoder) {
+		LOG(ERROR) << "No frame decoder available";
+		return;
+	}
+
+	decoder->decode(pkt, (rchan == 0) ? frame.channel1 : frame.channel2);
+
+	// Apply colour correction to chunk
+	//ftl::rgbd::colourCorrection(tmp_rgb, gamma_, temperature_);
 
 	// TODO:(Nick) Decode directly into double buffer if no scaling
 
-	{
-		SHARED_LOCK(mutex_, lk);
-		
-		cv::Rect roi(cx,cy,chunk_width_,chunk_height_);
-		cv::Mat chunkRGB = d_rgb_(roi);
-		cv::Mat chunkDepth = d_depth_(roi);
-
-		// Original size so just copy
-		if (tmp_rgb.cols == chunkRGB.cols) {
-			tmp_rgb.copyTo(chunkRGB);
-			if (!tmp_depth.empty() && tmp_depth.type() == CV_16U && chunkDepth.type() == CV_32F) {
-				tmp_depth.convertTo(chunkDepth, CV_32FC1, 1.0f/1000.0f); //(16.0f*10.0f));
-			} else if (!tmp_depth.empty() && tmp_depth.type() == CV_8UC3 && chunkDepth.type() == CV_8UC3) {
-				tmp_depth.copyTo(chunkDepth);
-			} else {
-				// Silent ignore?
-			}
-		// Downsized so needs a scale up
-		} else {
-			cv::resize(tmp_rgb, chunkRGB, chunkRGB.size());
-			tmp_depth.convertTo(tmp_depth, CV_32FC1, 1.0f/1000.0f);
-			if (!tmp_depth.empty() && tmp_depth.type() == CV_16U && chunkDepth.type() == CV_32F) {
-				tmp_depth.convertTo(tmp_depth, CV_32FC1, 1.0f/1000.0f); //(16.0f*10.0f));
-				cv::resize(tmp_depth, chunkDepth, chunkDepth.size());
-			} else if (!tmp_depth.empty() && tmp_depth.type() == CV_8UC3 && chunkDepth.type() == CV_8UC3) {
-				cv::resize(tmp_depth, chunkDepth, chunkDepth.size());
-			} else {
-				// Silent ignore?
-			}
-		}
+	if (timestamp_ > 0 && frame.timestamp <= timestamp_) {
+		LOG(ERROR) << "BAD DUPLICATE FRAME - " << frame.timestamp << " received=" << int(rchan) << " uri=" << uri_;
+		return;
 	}
 
-	{
-		
-		++chunk_count_;
+	// Calculate how many packets to expect for this frame
+	if (frame.chunk_total == 0) {
+		// Getting a second channel first means expect double packets
+		frame.chunk_total = pkt.block_total * ((spkt.channel >> 1) + 1);
+	}		
+
+	++frame.chunk_count;
+
+	if (frame.chunk_count > frame.chunk_total) LOG(FATAL) << "TOO MANY CHUNKS";
+
+	// Capture tx time of first received chunk
+	if (frame.chunk_count == 1) {
+		UNIQUE_LOCK(frame.mtx, flk);
+		if (frame.chunk_count == 1) {
+			frame.tx_latency = int64_t(ttimeoff);
+		}
 	}
 
-	if (chunk == 0) {
-		UNIQUE_LOCK(host_->mutex(),lk);
-		N_--;
+	// Last chunk now received
+	if (frame.chunk_count == frame.chunk_total) {
+		UNIQUE_LOCK(frame.mtx, flk);
+
+		if (frame.timestamp >= 0 && frame.chunk_count == frame.chunk_total) {
+			timestamp_ = frame.timestamp;
+			frame.tx_latency = now-(spkt.timestamp+frame.tx_latency);
+
+			adaptive_ = abr_.selectBitrate(frame);
+			//LOG(INFO) << "Frame finished: " << frame.timestamp;
+			auto cb = host_->callback();
+			if (cb) {
+				try {
+					cb(frame.timestamp, frame.channel1, frame.channel2);
+				} catch (...) {
+					LOG(ERROR) << "Exception in net frame callback";
+				}
+			} else {
+				LOG(ERROR) << "NO FRAME CALLBACK";
+			}
+
+			queue_.freeFrame(frame);
+
+			{
+				// Decrement expected frame counter
+				N_--;
+			}
+		}
 	}
 }
 
@@ -233,8 +325,8 @@ void NetSource::setPose(const Eigen::Matrix4d &pose) {
 	//Source::setPose(pose);
 }
 
-ftl::rgbd::Camera NetSource::parameters(ftl::rgbd::channel_t chan) {
-	if (chan == ftl::rgbd::kChanRight) {
+ftl::rgbd::Camera NetSource::parameters(ftl::rgbd::Channel chan) {
+	if (chan == ftl::rgbd::Channel::Right) {
 		auto uri = host_->get<string>("uri");
 		if (!uri) return params_;
 
@@ -249,7 +341,7 @@ ftl::rgbd::Camera NetSource::parameters(ftl::rgbd::channel_t chan) {
 void NetSource::_updateURI() {
 	UNIQUE_LOCK(mutex_,lk);
 	active_ = false;
-	prev_chan_ = ftl::rgbd::kChanNone;
+	prev_chan_ = ftl::rgbd::Channel::None;
 	auto uri = host_->get<string>("uri");
 
 	if (uri_.size() > 0) {
@@ -264,30 +356,27 @@ void NetSource::_updateURI() {
 		}
 		peer_ = *p;
 
-		has_calibration_ = _getCalibration(*host_->getNet(), peer_, *uri, params_, ftl::rgbd::kChanLeft);
-
-		host_->getNet()->bind(*uri, [this](int64_t frame, int chunk, bool delta, const vector<unsigned char> &jpg, const vector<unsigned char> &d) {
-			_recvChunk(frame, chunk, delta, jpg, d);
+		has_calibration_ = _getCalibration(*host_->getNet(), peer_, *uri, params_, ftl::rgbd::Channel::Left);
+
+		host_->getNet()->bind(*uri, [this](short ttimeoff, const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) {
+			//if (chunk == -1) {
+				//#ifdef HAVE_NVPIPE
+				//_recvVideo(frame, ttimeoff, bitrate, jpg, d);
+				//#else
+				//LOG(ERROR) << "Cannot receive HEVC, no NvPipe support";
+				//#endif
+			//} else {
+				//_recvChunk(frame, ttimeoff, bitrate, chunk, jpg, d);
+				_recvPacket(ttimeoff, spkt, pkt);
+			//}
 		});
 
 		N_ = 0;
 
-		// Initiate stream with request for first 10 frames
-		//try {
-		//	host_->getNet()->send(peer_, "get_stream", *uri, N_, 0, host_->getNet()->id(), *uri);
-		//} catch(...) {
-		//	LOG(ERROR) << "Could not connect to stream " << *uri;
-		//}
-
-		// Update chunk details
-		chunks_dim_ = ftl::rgbd::kChunkDim;
-		chunk_width_ = params_.width / chunks_dim_;
-		chunk_height_ = params_.height / chunks_dim_;
-		chunk_count_ = 0;
 		rgb_ = cv::Mat(cv::Size(params_.width, params_.height), CV_8UC3, cv::Scalar(0,0,0));
 		depth_ = cv::Mat(cv::Size(params_.width, params_.height), CV_32FC1, 0.0f);
-		d_rgb_ = cv::Mat(cv::Size(params_.width, params_.height), CV_8UC3, cv::Scalar(0,0,0));
-		d_depth_ = cv::Mat(cv::Size(params_.width, params_.height), CV_32FC1, 0.0f);
+		//d_rgb_ = cv::Mat(cv::Size(params_.width, params_.height), CV_8UC3, cv::Scalar(0,0,0));
+		//d_depth_ = cv::Mat(cv::Size(params_.width, params_.height), CV_32FC1, 0.0f);
 
 		uri_ = *uri;
 		active_ = true;
@@ -297,17 +386,17 @@ void NetSource::_updateURI() {
 	}
 }
 
-bool NetSource::grab(int n, int b) {
+bool NetSource::compute(int n, int b) {
 	// Choose highest requested number of frames
 	maxN_ = std::max(maxN_,(n == -1) ? ftl::rgbd::detail::kDefaultFrameCount : n);
 
 	// Choose best requested quality
-	minB_ = std::min(minB_,(b == -1) ? 0 : b);
+	minB_ = std::min(minB_,(b == -1) ? int(adaptive_) : b);
 
 	// Send k frames before end to prevent unwanted pause
 	// Unless only a single frame is requested
 	if ((N_ <= maxN_/2 && maxN_ > 1) || N_ == 0) {
-		const ftl::rgbd::channel_t chan = host_->getChannel();
+		const ftl::rgbd::Channel chan = host_->getChannel();
 
 		N_ = maxN_;
 
@@ -324,11 +413,13 @@ bool NetSource::grab(int n, int b) {
 		}
 
 		if (!host_->getNet()->send(peer_, "get_stream",
-				*host_->get<string>("uri"), N_, minB_,
+				*host_->get<string>("uri"), maxN_, minB_,
 				host_->getNet()->id(), *host_->get<string>("uri"))) {
 			active_ = false;
 		}
 
+		abr_.notifyChanged();
+
 		maxN_ = 1;  // Reset to single frame
 		minB_ = 9;  // Reset to worst quality
 	}
diff --git a/components/rgbd-sources/src/net.hpp b/components/rgbd-sources/src/net.hpp
index b99dc3487a6f4ab1949779935e2ed41699e9f4b0..51f31861fa3c9c39ea0cb53217e0fa3f764aeef3 100644
--- a/components/rgbd-sources/src/net.hpp
+++ b/components/rgbd-sources/src/net.hpp
@@ -2,65 +2,89 @@
 #ifndef _FTL_RGBD_NET_HPP_
 #define _FTL_RGBD_NET_HPP_
 
+#include <ftl/config.h>
+
 #include <ftl/net/universe.hpp>
 #include <ftl/rgbd/source.hpp>
+#include <ftl/rgbd/detail/abr.hpp>
 #include <ftl/threads.hpp>
+#include <ftl/rgbd/detail/netframe.hpp>
+#include <ftl/codecs/decoder.hpp>
 #include <string>
 
+#ifdef HAVE_NVPIPE
+#include <NvPipe.h>
+#endif
+
 namespace ftl {
 namespace rgbd {
 namespace detail {
 
 static const int kDefaultFrameCount = 30;
+static const int kLatencyThreshold = 5;		// Milliseconds delay considered as late
+static const int kSlowFramesThreshold = 5;	// Number of late frames before change
+static const int kAdaptationRate = 5000;	// Milliseconds between bitrate changes
 
 /**
- * RGBD source from either a stereo video file with left + right images, or
- * direct from two camera devices. A variety of algorithms are included for
- * calculating disparity, before converting to depth.  Calibration of the images
- * is also performed.
+ * A two channel network streamed source for RGB-Depth.
  */
 class NetSource : public detail::Source {
 	public:
 	explicit NetSource(ftl::rgbd::Source *);
 	~NetSource();
 
-	bool grab(int n, int b);
+	bool capture(int64_t ts) { return true; }
+	bool retrieve() { return true; }
+	bool compute(int n, int b);
 	bool isReady();
 
 	void setPose(const Eigen::Matrix4d &pose);
-	Camera parameters(channel_t chan);
+	Camera parameters(ftl::rgbd::Channel chan);
 
 	void reset();
 
 	private:
 	bool has_calibration_;
 	ftl::UUID peer_;
-	int N_;
+	std::atomic<int> N_;
 	bool active_;
 	std::string uri_;
 	ftl::net::callback_t h_;
 	SHARED_MUTEX mutex_;
-	int chunks_dim_;
-	int chunk_width_;
-	int chunk_height_;
 	cv::Mat idepth_;
 	float gamma_;
 	int temperature_;
 	int minB_;
 	int maxN_;
 	int default_quality_;
-	ftl::rgbd::channel_t prev_chan_;
-	int64_t current_frame_;
-	std::atomic<int> chunk_count_;
+	ftl::rgbd::Channel prev_chan_;
+
+	ftl::rgbd::detail::ABRController abr_;
+	int last_bitrate_;
+
+	//#ifdef HAVE_NVPIPE
+	//NvPipe *nv_channel1_decoder_;
+	//NvPipe *nv_channel2_decoder_;
+	//#endif
+
+	ftl::codecs::Decoder *decoder_c1_;
+	ftl::codecs::Decoder *decoder_c2_;
+
+	// Adaptive bitrate functionality
+	ftl::rgbd::detail::bitrate_t adaptive_;	 // Current adapted bitrate
+	//unsigned int slow_log_;		// Bit count of delayed frames
+	//int64_t last_br_change_;	// Time of last adaptive change
 
-	// Double buffering
-	cv::Mat d_depth_;
-	cv::Mat d_rgb_;
+	NetFrameQueue queue_;
 
-	bool _getCalibration(ftl::net::Universe &net, const ftl::UUID &peer, const std::string &src, ftl::rgbd::Camera &p, ftl::rgbd::channel_t chan);
+	bool _getCalibration(ftl::net::Universe &net, const ftl::UUID &peer, const std::string &src, ftl::rgbd::Camera &p, ftl::rgbd::Channel chan);
 	void _recv(const std::vector<unsigned char> &jpg, const std::vector<unsigned char> &d);
-	void _recvChunk(int64_t frame, int chunk, bool delta, const std::vector<unsigned char> &jpg, const std::vector<unsigned char> &d);
+	void _recvPacket(short ttimeoff, const ftl::codecs::StreamPacket &, const ftl::codecs::Packet &);
+	//void _recvChunk(int64_t frame, short ttimeoff, uint8_t bitrate, int chunk, const std::vector<unsigned char> &jpg, const std::vector<unsigned char> &d);
+	//void _recvVideo(int64_t ts, short ttimeoff, uint8_t bitrate, const std::vector<unsigned char> &chan1, const std::vector<unsigned char> &chan2);
 	void _updateURI();
+	//void _checkAdaptive(int64_t);
+	void _createDecoder(int chan, const ftl::codecs::Packet &);
 };
 
 }
diff --git a/components/rgbd-sources/src/offilter.cpp b/components/rgbd-sources/src/offilter.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..466aa9249517b91ac54726e821401e85272aa082
--- /dev/null
+++ b/components/rgbd-sources/src/offilter.cpp
@@ -0,0 +1,41 @@
+#include "ftl/offilter.hpp"
+#include "cuda_algorithms.hpp"
+
+#ifdef HAVE_OPTFLOW
+
+#include <loguru.hpp>
+
+using namespace ftl::rgbd;
+
+using cv::Mat;
+using cv::Size;
+
+using std::vector;
+
+template<typename T> static bool inline isValidDisparity(T d) { return (0.0 < d) && (d < 256.0); } // TODO
+
+OFDisparityFilter::OFDisparityFilter(Size size, int n_frames, float threshold) :
+	n_max_(n_frames + 1), threshold_(threshold)
+{
+	CHECK((n_max_ > 1) && (n_max_ <= 32)) << "History length must be between 0 and 31!";
+	disp_old_ = cv::cuda::GpuMat(cv::Size(size.width * n_max_, size.height), CV_32FC1);
+	
+	/*nvof_ = cv::cuda::NvidiaOpticalFlow_1_0::create(size.width, size.height,
+													cv::cuda::NvidiaOpticalFlow_1_0::NV_OF_PERF_LEVEL_SLOW,
+													true, false, false, 0);*/
+	
+}
+
+void OFDisparityFilter::filter(ftl::rgbd::Frame &frame, cv::cuda::Stream &stream)
+{
+	frame.upload(Channel::Flow, stream);
+	const cv::cuda::GpuMat &optflow = frame.get<cv::cuda::GpuMat>(Channel::Flow);
+	//frame.get<cv::cuda::GpuMat>(Channel::Disparity);
+	stream.waitForCompletion();
+	if (optflow.empty()) { return; }
+
+	cv::cuda::GpuMat &disp = frame.create<cv::cuda::GpuMat>(Channel::Disparity);
+	ftl::cuda::optflow_filter(disp, optflow, disp_old_, n_max_, threshold_, stream);
+}
+
+#endif  // HAVE_OPTFLOW
diff --git a/components/rgbd-sources/src/realsense_source.cpp b/components/rgbd-sources/src/realsense_source.cpp
index d6c6f487be89c3eafd4e3e8e6020272dd93e8e9d..df4c0fe2535426ac52808ea985911968efb74e15 100644
--- a/components/rgbd-sources/src/realsense_source.cpp
+++ b/components/rgbd-sources/src/realsense_source.cpp
@@ -41,7 +41,7 @@ RealsenseSource::~RealsenseSource() {
 
 }
 
-bool RealsenseSource::grab(int n, int b) {
+bool RealsenseSource::compute(int n, int b) {
     rs2::frameset frames;
 	if (!pipe_.poll_for_frames(&frames)) return false;  //wait_for_frames();
 
diff --git a/components/rgbd-sources/src/realsense_source.hpp b/components/rgbd-sources/src/realsense_source.hpp
index 2af26bbaffb99081e3311a70cb2c715c8fca5cee..371d305b7d27fc73ad85bba83965f58dcd28c45b 100644
--- a/components/rgbd-sources/src/realsense_source.hpp
+++ b/components/rgbd-sources/src/realsense_source.hpp
@@ -14,10 +14,12 @@ namespace detail {
 
 class RealsenseSource : public ftl::rgbd::detail::Source {
 	public:
-	RealsenseSource(ftl::rgbd::Source *host);
+	explicit RealsenseSource(ftl::rgbd::Source *host);
 	~RealsenseSource();
 
-	bool grab(int n=-1, int b=-1);
+	bool capture(int64_t ts) { timestamp_ = ts; return true; }
+	bool retrieve() { return true; }
+	bool compute(int n=-1, int b=-1);
 	bool isReady();
 
 	private:
diff --git a/components/rgbd-sources/src/snapshot.cpp b/components/rgbd-sources/src/snapshot.cpp
index 20202302693721ce8f136ecdd3db8cd227f42db0..7a80ee677c6275f2446d0f2b404e3bc778745d08 100644
--- a/components/rgbd-sources/src/snapshot.cpp
+++ b/components/rgbd-sources/src/snapshot.cpp
@@ -13,6 +13,8 @@ using cv::imdecode;
 using std::string;
 using std::vector;
 
+using cv::FileStorage;
+
 // TODO: move to camera_params
 using ftl::rgbd::Camera;
 
@@ -39,6 +41,12 @@ void from_json(const nlohmann::json& j, Camera &p) {
 	j.at("minDepth").get_to(p.minDepth);
 	j.at("maxDepth").get_to(p.maxDepth);
 }
+/*
+Mat getCameraMatrix(const ftl::rgbd::Camera &parameters) {
+	Mat m = (cv::Mat_<double>(3,3) << parameters.fx, 0.0, -parameters.cx, 0.0, parameters.fy, -parameters.cy, 0.0, 0.0, 1.0);
+	return m;
+}
+*/
 //
 
 SnapshotWriter::SnapshotWriter(const string &filename) {
@@ -69,9 +77,7 @@ SnapshotWriter::SnapshotWriter(const string &filename) {
 }
 
 SnapshotWriter::~SnapshotWriter() {
-	archive_entry_free(entry_);
-	archive_write_close(archive_);
-	archive_write_free(archive_);
+	if (archive_) writeIndex();
 }
 
 bool SnapshotWriter::addFile(const string &name, const uchar *buf, const size_t len) {
@@ -104,44 +110,83 @@ bool SnapshotWriter::addFile(const string &name, const vector<uchar> &buf) {
 	return addFile(name, buf.data(), buf.size());
 }
 
-bool SnapshotWriter::addMat(const string &name, const Mat &mat, const std::string &format) {
+bool SnapshotWriter::addMat(const string &name, const Mat &mat, const std::string &format, const vector<int> &params) {
 	if (mat.rows == 0 || mat.cols == 0) {
 		LOG(ERROR) << "empty mat";
 		return false;
 	}
 
 	vector<uchar> buf;
-	vector<int> params;
 	bool retval = true;
 	retval &= imencode("." + format, mat, buf, params);
 	retval &= addFile(name + "." + format, buf);
 	return retval;
 }
 
-bool SnapshotWriter::addEigenMatrix4d(const string &name, const Matrix4d &m, const string &format) {
-	Mat tmp;
-	cv::eigen2cv(m, tmp);
-	return addMat(name, tmp, format);
+void SnapshotWriter::addSource(const std::string &id, const vector<double> &params, const cv::Mat &extrinsic) {
+	frame_idx_.push_back(0);
+	sources_.push_back(id);
+	params_.push_back(params);
+	extrinsic_.push_back(extrinsic);
+	fname_rgb_.emplace_back();
+	fname_depth_.emplace_back();
 }
 
-bool SnapshotWriter::addCameraParams(const string &name, const Matrix4d &pose, const Camera &params) {
+void SnapshotWriter::addSource(const std::string &id, const ftl::rgbd::Camera &params, const Eigen::Matrix4d &extrinsic) {
+	vector<double> params_vec;
+	Mat extrinsic_cv;
+	cv::eigen2cv(extrinsic, extrinsic_cv);
+	params_vec.push_back(params.fx);
+	params_vec.push_back(params.fy);
+	params_vec.push_back(params.cx);
+	params_vec.push_back(params.cy);
+	params_vec.push_back(params.width);
+	params_vec.push_back(params.height);
+	params_vec.push_back(params.minDepth);
+	params_vec.push_back(params.maxDepth);
+	params_vec.push_back(params.baseline);
+	params_vec.push_back(params.doffs);
+	addSource(id, params_vec, extrinsic_cv);
+}
+
+
+bool SnapshotWriter::addRGBD(size_t source, const cv::Mat &rgb, const cv::Mat &depth, uint64_t time) {
+	// TODO: png option
+	if (time != 0) { LOG(WARNING) << "time parameter not used (not implemented)"; }
+
 	bool retval = true;
-	retval &= addEigenMatrix4d(name + "-POSE", pose);
+	string fname = std::to_string(source) + "/" + std::to_string(frame_idx_[source]++);
+	
+	fname_rgb_[source].push_back("RGB" + fname + ".jpg");
+	retval &= addMat("RGB" + fname, rgb, "jpg", {});
+
+	fname_depth_[source].push_back("DEPTH" + fname + ".tiff");
+	retval &= addMat("DEPTH" + fname, depth, "tiff", {});
 
-	nlohmann::json j;
-	to_json(j, params);
-	string str_params = j.dump();
-	retval &= addFile(name + "-PARAMS.json", (uchar*) str_params.c_str(), str_params.size());
 	return retval;
 }
 
-bool SnapshotWriter::addCameraRGBD(const string &name, const Mat &rgb, const Mat &depth) {
-	bool retval = true;
-	cv::Mat tdepth;
-	depth.convertTo(tdepth, CV_16UC1, 1000.0f);
-	retval &= addMat(name + "-RGB", rgb, "jpg");
-	retval &= addMat(name + "-D", tdepth, "png");
-	return retval;
+void SnapshotWriter::writeIndex() {
+	FileStorage fs(".yml", FileStorage::WRITE + FileStorage::MEMORY);
+
+	vector<string> channels{"time", "rgb_left", "depth_left"};
+
+	fs << "sources" << sources_;
+	fs << "params" <<params_;
+	fs << "extrinsic" << extrinsic_;
+	fs << "channels" << channels;
+	
+	fs << "rgb_left" << fname_rgb_;
+	fs << "depth_left" << fname_depth_;
+
+	string buf = fs.releaseAndGetString();
+	addFile("index.yml", (uchar*) buf.c_str(), buf.length());
+
+	archive_entry_free(entry_);
+	archive_write_close(archive_);
+	archive_write_free(archive_);
+	archive_ = nullptr;
+	entry_ = nullptr;
 }
 
 SnapshotStreamWriter::SnapshotStreamWriter(const string &filename, int delay) : 
@@ -150,11 +195,11 @@ SnapshotStreamWriter::SnapshotStreamWriter(const string &filename, int delay) :
 	}
 
 SnapshotStreamWriter::~SnapshotStreamWriter() {
-
+	
 }
 
 void SnapshotStreamWriter::addSource(ftl::rgbd::Source *src) {
-	writer_.addCameraParams(std::to_string(sources_.size()), src->getPose(), src->parameters());
+	writer_.addSource(src->getURI(), src->parameters(), src->getPose());
 	sources_.push_back(src);
 }
 
@@ -169,12 +214,19 @@ void SnapshotStreamWriter::run() {
 		auto duration = t_now.time_since_epoch();
 		auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(duration).count();
 
+		bool good = true;
 		for(size_t i = 0; i < sources_.size(); ++i) {
 			sources_[i]->getFrames(rgb[i], depth[i]);
+			good &= !rgb[i].empty() && !depth[i].empty();
+		}
+
+		if (!good) {
+			LOG(WARNING) << "Missing frames";
+			continue;
 		}
 
 		for(size_t i = 0; i < sources_.size(); ++i) {
-			writer_.addCameraRGBD(std::to_string(ms) + "-" + std::to_string(i), rgb[i], depth[i]);
+			writer_.addRGBD(i, rgb[i], depth[i]);
 		}
 
 		std::this_thread::sleep_until(t_wakeup);
@@ -197,10 +249,25 @@ void SnapshotStreamWriter::stop() {
 	if (wasrunning) thread_.join();
 }
 
-
+size_t Snapshot::getSourcesCount() { return sources.size(); }
+size_t Snapshot::getFramesCount() { return depth_left[0].size(); }
+	
+string Snapshot::getSourceURI(size_t camera) { return sources[camera]; }
+ftl::rgbd::Camera Snapshot::getParameters(size_t camera) { return parameters[camera]; }
+void Snapshot::getPose(size_t camera, cv::Mat &out) { out = extrinsic[camera]; }
+void Snapshot::getPose(size_t camera, Eigen::Matrix4d &out) {
+	Mat mat;
+	getPose(camera, mat);
+	cv::cv2eigen(mat, out);
+}
+void Snapshot::getLeftRGB(size_t camera, size_t frame, cv::Mat &data) { data = rgb_left[camera][frame]; }
+void Snapshot::getLeftDepth(size_t camera, size_t frame, cv::Mat &data) { data = depth_left[camera][frame]; }
 
 SnapshotReader::SnapshotReader(const string &filename) {
 	archive_ = archive_read_new();
+	int retval = ARCHIVE_OK;
+	string msg;
+
 	if (!archive_) goto error2;
 	archive_read_support_format_all(archive_);
 	archive_read_support_filter_all(archive_);
@@ -208,12 +275,22 @@ SnapshotReader::SnapshotReader(const string &filename) {
 	if (archive_read_open_filename(archive_, filename.c_str(), 4096) != ARCHIVE_OK)
 		goto error1;
 	
-	readArchive();
+	while((retval = archive_read_next_header(archive_, &entry_)) == ARCHIVE_OK) {
+		string path = string(archive_entry_pathname(entry_));
+		vector<uchar> data;
+		
+		if (readEntry(data)) { files_[path] = data; }
+	}
+
+	if (retval != ARCHIVE_EOF) { goto error1; }
+
 	return;
 	
 	error1:
-	LOG(ERROR) << archive_error_string(archive_);
+	msg = archive_error_string(archive_);
 	archive_read_free(archive_);
+	throw std::runtime_error(msg);
+
 	error2:
 	// throw exception; otherwise destructor might be called
 	throw std::runtime_error("SnapshotReader failed");
@@ -242,94 +319,200 @@ bool SnapshotReader::readEntry(vector<uchar> &data) {
 	}
 }
 
-SnapshotEntry& SnapshotReader::getEntry(const string &id) {
-	/*if (data_.find(id) == data_.end()) {
-		data_.emplace(id, SnapshotEntry{});
-	}*/
-	return data_[id];
-}
-
-/* read all entries to data_ */
-bool SnapshotReader::readArchive() {
-	int retval = ARCHIVE_OK;
-	vector<uchar> data;
-
-	while((retval = archive_read_next_header(archive_, &entry_)) == ARCHIVE_OK) {
-		string path = string(archive_entry_pathname(entry_));
-		if (path.rfind("-") == string::npos) {
-			LOG(WARNING) << "unrecognized file " << path;
-			continue;
-		}
-		string id = path.substr(0, path.find("-"));
-
-		SnapshotEntry &snapshot = getEntry(id);
+bool SnapshotReader::getDepth(const std::string &name, cv::Mat &data) {
+	if (files_.find(name) == files_.end()) {
+		LOG(ERROR) << name << " not found in archive";
+		return false;
+	}
 
-		// TODO: verify that input is valid
-		// TODO: check that earlier results are not overwritten (status)
+	const vector<uchar> &data_raw = files_[name];
+	const string ext = name.substr(name.find_last_of(".") + 1);
 
-		if (path.rfind("-RGB.") != string::npos) {
-			if (!readEntry(data)) continue;
-			snapshot.rgb = cv::imdecode(data, cv::IMREAD_COLOR);
-			snapshot.status &= ~1;
-		}
-		else if (path.rfind("-D.") != string::npos) {
-			if (!readEntry(data)) continue;
-			snapshot.depth = cv::imdecode(data, cv::IMREAD_ANYDEPTH);
-			snapshot.depth.convertTo(snapshot.depth, CV_32FC1, 1.0f / 1000.0f);
-			snapshot.status &= ~(1 << 1);
-		}
-		else if (path.rfind("-POSE.pfm") != string::npos) {
-			if (!readEntry(data)) continue;
-			Mat m_ = cv::imdecode(Mat(data), cv::IMREAD_ANYDEPTH);
-			if ((m_.rows != 4) || (m_.cols != 4)) continue;
-			cv::Matx44d pose_(m_);
-			cv::cv2eigen(pose_, snapshot.pose);
-			snapshot.status &= ~(1 << 2);
-		}
-		else if (path.rfind("-PARAMS.json") != string::npos) {
-			if (!readEntry(data)) continue;
-			nlohmann::json j = nlohmann::json::parse(string((const char*) data.data(), data.size()));
-			from_json(j, snapshot.params);
-			snapshot.status &= ~(1 << 3);
-		}
-		else {
-			LOG(WARNING) << "unknown file " << path;
-		}
+	if (ext == "tiff") {
+		data = cv::imdecode(data_raw, cv::IMREAD_ANYDEPTH);
 	}
-	
-	if (retval != ARCHIVE_EOF) {
-		LOG(ERROR) << archive_error_string(archive_);
+	else if (ext == "png") {
+		data = cv::imdecode(data_raw, cv::IMREAD_ANYDEPTH);
+		data.convertTo(data, CV_32FC1, 1.0f / 1000.0f);
+	}
+	else {
+		LOG(ERROR) << "Unsupported file extension for depth image: " << ext;
+		return false;
+	}
+
+	if (data.empty()) {
+		LOG(ERROR) << "Error decoding file: " << name;
 		return false;
 	}
 	
 	return true;
 }
 
-vector<string> SnapshotReader::getIds() {
-	vector<string> res;
-	res.reserve(data_.size());
-	for(auto itr = data_.begin(); itr != data_.end(); ++itr) {
-		res.push_back(itr->first);
+bool SnapshotReader::getRGB(const std::string &name, cv::Mat &data) {
+	if (files_.find(name) == files_.end()) {
+		LOG(ERROR) << name << " not found in archive";
+		return false;
 	}
-	return res;
-}
 
-bool SnapshotReader::getCameraRGBD(const string &id, Mat &rgb, Mat &depth,
-							 Matrix4d &pose, Camera &params) {
-	if (data_.find(id) == data_.end()) {
-		LOG(ERROR) << "entry not found: " << id;
+	const vector<uchar> &data_raw = files_[name];
+	const string ext = name.substr(name.find_last_of(".") + 1);
+	
+	if (!(ext == "png" || ext == "jpg")) { 
+		LOG(ERROR) << "Unsupported file extension for depth image: " << ext;
 		return false;
 	}
 
-	SnapshotEntry item = getEntry(id);
+	data = cv::imdecode(data_raw, cv::IMREAD_COLOR);
 
-	if (item.status != 0) {
-		LOG(ERROR) << "entry incomplete: " << id;
+	if (data.empty()) {
+		LOG(ERROR) << "Error decoding file: " << name;
+		return false;
 	}
-
-	rgb = item.rgb;
-	depth = item.depth;
-	params = item.params;
-	pose = item.pose;
+	
 	return true;
-}
\ No newline at end of file
+}
+
+Snapshot SnapshotReader::readArchive() {
+	Snapshot result;
+	
+	if (files_.find("index.yml") != files_.end()) {
+		LOG(INFO) << "Using new format snapshot archive";
+		string input;
+		{
+			vector<uchar> data = files_["index.yml"]; 
+			input = string((char*) data.data(), data.size());
+		}
+		FileStorage fs(input, FileStorage::READ | FileStorage::MEMORY);
+		
+		vector<string> &sources = result.sources;
+		vector<ftl::rgbd::Camera> &params = result.parameters;
+		vector<Mat> &extrinsic = result.extrinsic;
+		
+		vector<vector<Mat>> &rgb_left = result.rgb_left;
+		vector<vector<Mat>> &depth_left = result.depth_left;
+
+		vector<string> channels;
+
+		fs["sources"] >> sources;
+		fs["extrinsic"] >> extrinsic;
+		fs["channels"] >> channels;
+
+		cv::FileNode fn;
+		fn = fs["params"];
+		for (cv::FileNodeIterator it = fn.begin(); it != fn.end(); it++) {
+			vector<double> p;
+			*it >> p;
+
+			ftl::rgbd::Camera camera;
+			camera.fx = p[0];
+			camera.fy = p[1];
+			camera.cx = p[2];
+			camera.cy = p[3];
+			camera.width = p[4];
+			camera.height = p[5];
+			camera.minDepth = p[6];
+			camera.maxDepth = p[7];
+			camera.baseline = p[8];
+			camera.doffs = p[9];
+			params.push_back(camera);
+		}
+
+		vector<string> files;
+		for (auto const &channel : channels) {
+			files.clear();
+
+			if (channel == "time") {
+				//fs["time"] >> times;
+			}
+			else if (channel == "rgb_left") {
+				fn = fs["rgb_left"];
+				files.clear();
+				for (cv::FileNodeIterator it = fn.begin(); it != fn.end(); it++) {
+					*it >> files;
+					auto &images = rgb_left.emplace_back();
+					for (const string& file : files) {
+						Mat &img = images.emplace_back();
+						getRGB(file, img);
+					}
+				}
+			}
+			else if (channel == "depth_left") {
+				fn = fs["depth_left"];
+				files.clear();
+				for (cv::FileNodeIterator it = fn.begin(); it != fn.end(); it++) {
+					*it >> files;
+					auto &images = depth_left.emplace_back();
+					for (const string& file : files) {
+						Mat &img = images.emplace_back();
+						getDepth(file, img);
+					}
+				}
+			}
+			else {
+				LOG(ERROR) << "Unsupported channel: " << channel;
+			}
+		}
+
+		fs.release();
+	}
+	else {
+		LOG(INFO) << "Using old format snapshot archive";
+
+		result.n_cameras = 0;
+		result.n_frames = 1;
+
+		std::map<string,int> cammap;
+
+		for (auto const& [path, data] : files_) {
+			if (path.rfind("-") == string::npos) {
+				LOG(WARNING) << "unrecognized file " << path;
+				continue;
+			}
+			string id = path.substr(0, path.find("-"));
+			int idx;
+
+			if (cammap.find(id) == cammap.end()) {
+				cammap[id] = result.n_cameras;
+				idx = result.n_cameras;
+				result.n_cameras++;
+
+				result.rgb_left.emplace_back().emplace_back();
+				result.depth_left.emplace_back().emplace_back();
+				result.extrinsic.emplace_back();
+				result.parameters.emplace_back();
+			} else {
+				idx = cammap[id];
+			}
+
+			Mat &rgb = result.rgb_left[idx][0];
+			Mat &depth = result.depth_left[idx][0];
+			Mat &pose = result.extrinsic[idx];
+			Camera &params = result.parameters[idx];
+
+			// TODO: verify that input is valid
+			// TODO: check that earlier results are not overwritten (status)
+			
+			if (path.rfind("-RGB.") != string::npos) {
+				getRGB(path, rgb);
+			}
+			else if (path.rfind("-D.") != string::npos) {
+				getDepth(path, depth);
+			}
+			else if (path.rfind("-POSE.pfm") != string::npos) {
+				Mat m_ = cv::imdecode(Mat(data), cv::IMREAD_ANYDEPTH);
+				if ((m_.rows != 4) || (m_.cols != 4)) continue;
+				cv::Matx44d pose_(m_);
+				pose = m_;
+			}
+			else if (path.rfind("-PARAMS.json") != string::npos) {
+				nlohmann::json j = nlohmann::json::parse(string((const char*) data.data(), data.size()));
+				from_json(j, params);
+			}
+			else {
+				LOG(WARNING) << "unknown file " << path;
+			}
+		}
+	}
+
+	return result;
+}
diff --git a/components/rgbd-sources/src/snapshot_source.cpp b/components/rgbd-sources/src/snapshot_source.cpp
index 030e56dd48bf48bf351917274e2f26cc0e414cd3..73db6b86c3861cd0aa1105f4d9ff7dcd9cbe9fe3 100644
--- a/components/rgbd-sources/src/snapshot_source.cpp
+++ b/components/rgbd-sources/src/snapshot_source.cpp
@@ -13,19 +13,21 @@ using ftl::rgbd::detail::SnapshotSource;
 using std::string;
 using std::vector;
 
-SnapshotSource::SnapshotSource(ftl::rgbd::Source *host, SnapshotReader &reader, const string &id) : detail::Source(host) {
-    Eigen::Matrix4d pose;
-    reader.getCameraRGBD(id, rgb_, depth_, pose, params_);
+SnapshotSource::SnapshotSource(ftl::rgbd::Source *host, Snapshot &snapshot, const string &id) : detail::Source(host) {
+	snapshot_ = snapshot;
+	camera_idx_ = std::atoi(id.c_str());
+	frame_idx_ = 0;
 
-	if (rgb_.empty()) LOG(ERROR) << "Did not load snapshot rgb - " << id;
-	if (depth_.empty()) LOG(ERROR) << "Did not load snapshot depth - " << id;
-	if (params_.width != rgb_.cols) LOG(ERROR) << "Camera parameters corrupt for " << id;
+	Eigen::Matrix4d pose;
+	snapshot.getPose(camera_idx_, pose);
+	params_ = snapshot.getParameters(camera_idx_);
 
+	/*
 	ftl::rgbd::colourCorrection(rgb_, host->value("gamma", 1.0f), host->value("temperature", 6500));
-
 	host->on("gamma", [this,host](const ftl::config::Event&) {
 		ftl::rgbd::colourCorrection(rgb_, host->value("gamma", 1.0f), host->value("temperature", 6500));
 	});
+	*/
 
 	// Add calibration to config object
 	host_->getConfig()["focal"] = params_.fx;
@@ -49,4 +51,21 @@ SnapshotSource::SnapshotSource(ftl::rgbd::Source *host, SnapshotReader &reader,
 	LOG(INFO) << "POSE = " << pose;
 
     host->setPose(pose);
+
+	mspf_ = 1000 / host_->value("fps", 20);
+}
+
+bool SnapshotSource::compute(int n, int b) {
+	snapshot_.getLeftRGB(camera_idx_, frame_idx_, snap_rgb_);
+	snapshot_.getLeftDepth(camera_idx_, frame_idx_, snap_depth_);
+
+	snap_rgb_.copyTo(rgb_);
+	snap_depth_.copyTo(depth_);
+
+	auto cb = host_->callback();
+	if (cb) cb(timestamp_, rgb_, depth_);
+
+	frame_idx_ = (frame_idx_ + 1) % snapshot_.getFramesCount();
+
+	return true;
 }
diff --git a/components/rgbd-sources/src/snapshot_source.hpp b/components/rgbd-sources/src/snapshot_source.hpp
index 38b9d875ad9e9846d0c83e42d6b84668cfe7dd18..de1b0df48be79df732f51144226f5c7e6d2f0478 100644
--- a/components/rgbd-sources/src/snapshot_source.hpp
+++ b/components/rgbd-sources/src/snapshot_source.hpp
@@ -13,15 +13,25 @@ namespace detail {
 
 class SnapshotSource : public detail::Source {
 	public:
-	SnapshotSource(ftl::rgbd::Source *);
-	SnapshotSource(ftl::rgbd::Source *, ftl::rgbd::SnapshotReader &reader, const std::string &id);
+	explicit SnapshotSource(ftl::rgbd::Source *);
+	SnapshotSource(ftl::rgbd::Source *, ftl::rgbd::Snapshot &snapshot, const std::string &id);
 	~SnapshotSource() {};
 
-	bool grab(int n, int b) override { return true; };
+	bool capture(int64_t ts) { timestamp_ = ts; return true; }
+	bool retrieve() { return true; }
+	bool compute(int n, int b);
 	bool isReady() { return true; }
 
 	//void reset();
-
+	private:
+	size_t frame_idx_;
+	size_t camera_idx_;
+	
+	ftl::rgbd::Snapshot snapshot_;
+
+	cv::Mat snap_rgb_;
+	cv::Mat snap_depth_;
+	int mspf_;
 };
 
 }
diff --git a/components/rgbd-sources/src/source.cpp b/components/rgbd-sources/src/source.cpp
index 1014e24401b4d2dccdc361de3d4331ff2815eacb..35d23f27ad7edac18d3e3e02247296f1382be5e2 100644
--- a/components/rgbd-sources/src/source.cpp
+++ b/components/rgbd-sources/src/source.cpp
@@ -25,10 +25,11 @@ using ftl::rgbd::detail::NetSource;
 using ftl::rgbd::detail::ImageSource;
 using ftl::rgbd::detail::MiddleburySource;
 using ftl::rgbd::capability_t;
+using ftl::rgbd::Channel;
 
 Source::Source(ftl::config::json_t &cfg) : Configurable(cfg), pose_(Eigen::Matrix4d::Identity()), net_(nullptr) {
 	impl_ = nullptr;
-	params_ = {0};
+	params_ = {};
 	stream_ = 0;
 	timestamp_ = 0;
 	reset();
@@ -41,7 +42,7 @@ Source::Source(ftl::config::json_t &cfg) : Configurable(cfg), pose_(Eigen::Matri
 
 Source::Source(ftl::config::json_t &cfg, ftl::net::Universe *net) : Configurable(cfg), pose_(Eigen::Matrix4d::Identity()), net_(net) {
 	impl_ = nullptr;
-	params_ = {0};
+	params_ = {};
 	stream_ = 0;
 	timestamp_ = 0;
 	reset();
@@ -120,7 +121,8 @@ ftl::rgbd::detail::Source *Source::_createFileImpl(const ftl::URI &uri) {
 		} else if (ext == "tar" || ext == "gz") {
 #ifdef HAVE_LIBARCHIVE
 			ftl::rgbd::SnapshotReader reader(path);
-			return new ftl::rgbd::detail::SnapshotSource(this, reader, value("index", std::string("0")));  // TODO: Use URI fragment
+			auto snapshot = reader.readArchive();
+			return new ftl::rgbd::detail::SnapshotSource(this, snapshot, value("index", std::string("0")));  // TODO: Use URI fragment
 #else
 			LOG(ERROR) << "Cannot read snapshots, libarchive not installed";
 			return nullptr;
@@ -164,11 +166,20 @@ ftl::rgbd::detail::Source *Source::_createDeviceImpl(const ftl::URI &uri) {
 }
 
 void Source::getFrames(cv::Mat &rgb, cv::Mat &depth) {
+	if (bool(callback_)) LOG(WARNING) << "Cannot use getFrames and callback in source";
 	SHARED_LOCK(mutex_,lk);
-	//rgb_.copyTo(rgb);
-	//depth_.copyTo(depth);
+	rgb_.copyTo(rgb);
+	depth_.copyTo(depth);
+	//rgb = rgb_;
+	//depth = depth_;
+
+	/*cv::Mat tmp;
+	tmp = rgb;
 	rgb = rgb_;
+	rgb_ = tmp;
+	tmp = depth;
 	depth = depth_;
+	depth_ = tmp;*/
 }
 
 Eigen::Vector4d Source::point(uint ux, uint uy) {
@@ -217,19 +228,30 @@ capability_t Source::getCapabilities() const {
 
 void Source::reset() {
 	UNIQUE_LOCK(mutex_,lk);
-	channel_ = kChanNone;
+	channel_ = Channel::None;
 	if (impl_) delete impl_;
 	impl_ = _createImplementation();
 }
 
-bool Source::grab(int N, int B) {
+bool Source::capture(int64_t ts) {
+	//timestamp_ = ts;
+	if (impl_) return impl_->capture(ts);
+	else return true;
+}
+
+bool Source::retrieve() {
+	if (impl_) return impl_->retrieve();
+	else return true;
+}
+
+bool Source::compute(int N, int B) {
 	UNIQUE_LOCK(mutex_,lk);
 	if (!impl_ && stream_ != 0) {
 		cudaSafeCall(cudaStreamSynchronize(stream_));
 		if (depth_.type() == CV_32SC1) depth_.convertTo(depth_, CV_32F, 1.0f / 1000.0f);
 		stream_ = 0;
 		return true;
-	} else if (impl_ && impl_->grab(N,B)) {
+	} else if (impl_ && impl_->compute(N,B)) {
 		timestamp_ = impl_->timestamp_;
 		/*cv::Mat tmp;
 		rgb_.create(impl_->rgb_.size(), impl_->rgb_.type());
@@ -240,72 +262,17 @@ bool Source::grab(int N, int B) {
 		tmp = depth_;
 		depth_ = impl_->depth_;
 		impl_->depth_ = tmp;*/
+
+		// TODO:(Nick) Reduce buffer copies
 		impl_->rgb_.copyTo(rgb_);
 		impl_->depth_.copyTo(depth_);
+		//rgb_ = impl_->rgb_;
+		//depth_ = impl_->depth_;
 		return true;
 	}
 	return false;
 }
 
-void Source::writeFrames(const cv::Mat &rgb, const cv::Mat &depth) {
-	if (!impl_) {
-		UNIQUE_LOCK(mutex_,lk);
-		rgb.copyTo(rgb_);
-		depth.copyTo(depth_);
-	}
-}
-
-void Source::writeFrames(const ftl::cuda::TextureObject<uchar4> &rgb, const ftl::cuda::TextureObject<uint> &depth, cudaStream_t stream) {
-	if (!impl_) {
-		UNIQUE_LOCK(mutex_,lk);
-		rgb_.create(rgb.height(), rgb.width(), CV_8UC4);
-		cudaSafeCall(cudaMemcpy2DAsync(rgb_.data, rgb_.step, rgb.devicePtr(), rgb.pitch(), rgb_.cols * sizeof(uchar4), rgb_.rows, cudaMemcpyDeviceToHost, stream));
-		depth_.create(depth.height(), depth.width(), CV_32SC1);
-		cudaSafeCall(cudaMemcpy2DAsync(depth_.data, depth_.step, depth.devicePtr(), depth.pitch(), depth_.cols * sizeof(uint), depth_.rows, cudaMemcpyDeviceToHost, stream));
-		//cudaSafeCall(cudaStreamSynchronize(stream));  // TODO:(Nick) Don't wait here.
-		stream_ = stream;
-		//depth_.convertTo(depth_, CV_32F, 1.0f / 1000.0f);
-	} else {
-		LOG(ERROR) << "writeFrames cannot be done on this source: " << getURI();
-	}
-}
-
-void Source::writeFrames(const ftl::cuda::TextureObject<uchar4> &rgb, const ftl::cuda::TextureObject<float> &depth, cudaStream_t stream) {
-	if (!impl_) {
-		UNIQUE_LOCK(mutex_,lk);
-		rgb.download(rgb_, stream);
-		//rgb_.create(rgb.height(), rgb.width(), CV_8UC4);
-		//cudaSafeCall(cudaMemcpy2DAsync(rgb_.data, rgb_.step, rgb.devicePtr(), rgb.pitch(), rgb_.cols * sizeof(uchar4), rgb_.rows, cudaMemcpyDeviceToHost, stream));
-		depth.download(depth_, stream);
-		//depth_.create(depth.height(), depth.width(), CV_32FC1);
-		//cudaSafeCall(cudaMemcpy2DAsync(depth_.data, depth_.step, depth.devicePtr(), depth.pitch(), depth_.cols * sizeof(float), depth_.rows, cudaMemcpyDeviceToHost, stream));
-		
-		stream_ = stream;
-		cudaSafeCall(cudaStreamSynchronize(stream_));
-		cv::cvtColor(rgb_,rgb_, cv::COLOR_BGRA2BGR);
-		cv::cvtColor(rgb_,rgb_, cv::COLOR_Lab2BGR);
-	}
-}
-
-void Source::writeFrames(const ftl::cuda::TextureObject<uchar4> &rgb, const ftl::cuda::TextureObject<uchar4> &rgb2, cudaStream_t stream) {
-	if (!impl_) {
-		UNIQUE_LOCK(mutex_,lk);
-		rgb.download(rgb_, stream);
-		//rgb_.create(rgb.height(), rgb.width(), CV_8UC4);
-		//cudaSafeCall(cudaMemcpy2DAsync(rgb_.data, rgb_.step, rgb.devicePtr(), rgb.pitch(), rgb_.cols * sizeof(uchar4), rgb_.rows, cudaMemcpyDeviceToHost, stream));
-		rgb2.download(depth_, stream);
-		//depth_.create(depth.height(), depth.width(), CV_32FC1);
-		//cudaSafeCall(cudaMemcpy2DAsync(depth_.data, depth_.step, depth.devicePtr(), depth.pitch(), depth_.cols * sizeof(float), depth_.rows, cudaMemcpyDeviceToHost, stream));
-		
-		stream_ = stream;
-		cudaSafeCall(cudaStreamSynchronize(stream_));
-		cv::cvtColor(rgb_,rgb_, cv::COLOR_BGRA2BGR);
-		cv::cvtColor(rgb_,rgb_, cv::COLOR_Lab2BGR);
-		cv::cvtColor(depth_,depth_, cv::COLOR_BGRA2BGR);
-		cv::cvtColor(depth_,depth_, cv::COLOR_Lab2BGR);
-	}
-}
-
 bool Source::thumbnail(cv::Mat &t) {
 	if (!impl_ && stream_ != 0) {
 		cudaSafeCall(cudaStreamSynchronize(stream_));
@@ -314,7 +281,9 @@ bool Source::thumbnail(cv::Mat &t) {
 		return true;
 	} else if (impl_) {
 		UNIQUE_LOCK(mutex_,lk);
-		impl_->grab(1, 9);
+		impl_->capture(0);
+		impl_->swap();
+		impl_->compute(1, 9);
 		impl_->rgb_.copyTo(rgb_);
 		impl_->depth_.copyTo(depth_);
 	}
@@ -327,12 +296,17 @@ bool Source::thumbnail(cv::Mat &t) {
 	return !thumb_.empty();
 }
 
-bool Source::setChannel(ftl::rgbd::channel_t c) {
+bool Source::setChannel(ftl::rgbd::Channel c) {
 	channel_ = c;
 	// FIXME:(Nick) Verify channel is supported by this source...
 	return true;
 }
 
-const ftl::rgbd::Camera Source::parameters(ftl::rgbd::channel_t chan) const {
+const ftl::rgbd::Camera Source::parameters(ftl::rgbd::Channel chan) const {
 	return (impl_) ? impl_->parameters(chan) : parameters();
 }
+
+void Source::setCallback(std::function<void(int64_t, cv::Mat &, cv::Mat &)> cb) {
+	if (bool(callback_)) LOG(ERROR) << "Source already has a callback: " << getURI();
+	callback_ = cb;
+}
diff --git a/components/rgbd-sources/src/stereovideo.cpp b/components/rgbd-sources/src/stereovideo.cpp
index cb5e08a3a572d1534f5aae086e635551a6cbd32f..6573f74f4d7cf1f3761f98a66c68c6963e10af31 100644
--- a/components/rgbd-sources/src/stereovideo.cpp
+++ b/components/rgbd-sources/src/stereovideo.cpp
@@ -7,9 +7,12 @@
 #include "disparity.hpp"
 #include "cuda_algorithms.hpp"
 
+#include "cuda_algorithms.hpp"
+
 using ftl::rgbd::detail::Calibrate;
 using ftl::rgbd::detail::LocalSource;
 using ftl::rgbd::detail::StereoVideoSource;
+using ftl::rgbd::Channel;
 using std::string;
 
 StereoVideoSource::StereoVideoSource(ftl::rgbd::Source *host)
@@ -29,7 +32,8 @@ StereoVideoSource::~StereoVideoSource() {
 	delete lsrc_;
 }
 
-void StereoVideoSource::init(const string &file) {
+void StereoVideoSource::init(const string &file)
+{
 	capabilities_ = kCapVideo | kCapStereo;
 
 	if (ftl::is_video(file)) {
@@ -55,19 +59,31 @@ void StereoVideoSource::init(const string &file) {
 	}
 
 	cv::Size size = cv::Size(lsrc_->width(), lsrc_->height());
+	frames_ = std::vector<Frame>(2);
+
+#ifdef HAVE_OPTFLOW
+	use_optflow_ =  host_->value("use_optflow", false);
+	LOG(INFO) << "Using optical flow: " << (use_optflow_ ? "true" : "false");
+
+	nvof_ = cv::cuda::NvidiaOpticalFlow_1_0::create(size.width, size.height,
+													cv::cuda::NvidiaOpticalFlow_1_0::NV_OF_PERF_LEVEL_SLOW,
+													true, false, false, 0);
+
+#endif
+
 	calib_ = ftl::create<Calibrate>(host_, "calibration", size, stream_);
 
 	if (!calib_->isCalibrated()) LOG(WARNING) << "Cameras are not calibrated!";
 
 	// Generate camera parameters from camera matrix
-	cv::Mat q = calib_->getCameraMatrix();
+	cv::Mat K = calib_->getCameraMatrix();
 	params_ = {
-		q.at<double>(0,0),	// Fx
-		q.at<double>(1,1),	// Fy
-		-q.at<double>(0,2),	// Cx
-		-q.at<double>(1,2),	// Cy
-		(unsigned int)lsrc_->width(),
-		(unsigned int)lsrc_->height(),
+		K.at<double>(0,0),	// Fx
+		K.at<double>(1,1),	// Fy
+		-K.at<double>(0,2),	// Cx
+		-K.at<double>(1,2),	// Cy
+		(unsigned int) lsrc_->width(),
+		(unsigned int) lsrc_->height(),
 		0.0f,	// 0m min
 		15.0f,	// 15m max
 		1.0 / calib_->getQ().at<double>(3,2), // Baseline
@@ -113,15 +129,15 @@ void StereoVideoSource::init(const string &file) {
 	mask_l_ = (mask_l == 0);
 	
 	disp_ = Disparity::create(host_, "disparity");
-    if (!disp_) LOG(FATAL) << "Unknown disparity algorithm : " << *host_->get<ftl::config::json_t>("disparity");
+	if (!disp_) LOG(FATAL) << "Unknown disparity algorithm : " << *host_->get<ftl::config::json_t>("disparity");
 	disp_->setMask(mask_l_);
 
 	LOG(INFO) << "StereoVideo source ready...";
 	ready_ = true;
 }
 
-ftl::rgbd::Camera StereoVideoSource::parameters(ftl::rgbd::channel_t chan) {
-	if (chan == ftl::rgbd::kChanRight) {
+ftl::rgbd::Camera StereoVideoSource::parameters(Channel chan) {
+	if (chan == Channel::Right) {
 		cv::Mat q = calib_->getCameraMatrixRight();
 		ftl::rgbd::Camera params = {
 			q.at<double>(0,0),	// Fx
@@ -151,34 +167,85 @@ static void disparityToDepth(const cv::cuda::GpuMat &disparity, cv::cuda::GpuMat
 	cv::cuda::divide(val, disparity, depth, 1.0f / 1000.0f, -1, stream);
 }
 
-bool StereoVideoSource::grab(int n, int b) {
-	const ftl::rgbd::channel_t chan = host_->getChannel();
+bool StereoVideoSource::capture(int64_t ts) {
+	timestamp_ = ts;
+	lsrc_->grab();
+	return true;
+}
+
+bool StereoVideoSource::retrieve() {
+	auto &frame = frames_[0];
+	frame.reset();
+	auto &left = frame.create<cv::cuda::GpuMat>(Channel::Left);
+	auto &right = frame.create<cv::cuda::GpuMat>(Channel::Right);
+	lsrc_->get(left, right, calib_, stream2_);
 
-	if (chan == ftl::rgbd::kChanDepth) {
-		lsrc_->get(left_, right_, stream_);
-		if (depth_tmp_.empty()) depth_tmp_ = cv::cuda::GpuMat(left_.size(), CV_32FC1);
-		if (disp_tmp_.empty()) disp_tmp_ = cv::cuda::GpuMat(left_.size(), CV_32FC1);
-		calib_->rectifyStereo(left_, right_, stream_);
-		disp_->compute(left_, right_, disp_tmp_, stream_);
-		ftl::cuda::disparity_to_depth(disp_tmp_, depth_tmp_, params_, stream_);
-		left_.download(rgb_, stream_);
-		//rgb_ = lsrc_->cachedLeft();
-		depth_tmp_.download(depth_, stream_);
+#ifdef HAVE_OPTFLOW
+	// see comments in https://gitlab.utu.fi/nicolas.pope/ftl/issues/155
+	
+	if (use_optflow_)
+	{
+		auto &left_gray = frame.create<cv::cuda::GpuMat>(Channel::LeftGray);
+		auto &right_gray = frame.create<cv::cuda::GpuMat>(Channel::RightGray);
+
+		cv::cuda::cvtColor(left, left_gray, cv::COLOR_BGR2GRAY, 0, stream2_);
+		cv::cuda::cvtColor(right, right_gray, cv::COLOR_BGR2GRAY, 0, stream2_);
+
+		if (frames_[1].hasChannel(Channel::LeftGray))
+		{
+			//frames_[1].download(Channel::LeftGray);
+			auto &left_gray_prev = frames_[1].get<cv::cuda::GpuMat>(Channel::LeftGray);
+			auto &optflow = frame.create<cv::cuda::GpuMat>(Channel::Flow);
+			nvof_->calc(left_gray, left_gray_prev, optflow, stream2_);
+			// nvof_->upSampler() isn't implemented with CUDA
+			// cv::cuda::resize() does not work wiht 2-channel input
+			// cv::cuda::resize(optflow_, optflow, left.size(), 0.0, 0.0, cv::INTER_NEAREST, stream2_);
+		}
+	}
+#endif
 
+	stream2_.waitForCompletion();
+	return true;
+}
+
+void StereoVideoSource::swap() {
+	auto tmp = std::move(frames_[0]);
+	frames_[0] = std::move(frames_[1]);
+	frames_[1] = std::move(tmp);
+}
+
+bool StereoVideoSource::compute(int n, int b) {
+	auto &frame = frames_[1];
+	auto &left = frame.get<cv::cuda::GpuMat>(Channel::Left);
+	auto &right = frame.get<cv::cuda::GpuMat>(Channel::Right);
+
+	const ftl::rgbd::Channel chan = host_->getChannel();
+	if (left.empty() || right.empty()) return false;
+
+	if (chan == Channel::Depth) {
+		disp_->compute(frame, stream_);
+		
+		auto &disp = frame.get<cv::cuda::GpuMat>(Channel::Disparity);
+		auto &depth = frame.create<cv::cuda::GpuMat>(Channel::Depth);
+		if (depth.empty()) depth = cv::cuda::GpuMat(left.size(), CV_32FC1);
+
+		ftl::cuda::disparity_to_depth(disp, depth, params_, stream_);
+		
+		left.download(rgb_, stream_);
+		depth.download(depth_, stream_);
+		//frame.download(Channel::Left + Channel::Depth);
 		stream_.waitForCompletion();  // TODO:(Nick) Move to getFrames
-	} else if (chan == ftl::rgbd::kChanRight) {
-		lsrc_->get(left_, right_, stream_);
-		calib_->rectifyStereo(left_, right_, stream_);
-		left_.download(rgb_, stream_);
-		right_.download(depth_, stream_);
+	} else if (chan == Channel::Right) {
+		left.download(rgb_, stream_);
+		right.download(depth_, stream_);
 		stream_.waitForCompletion();  // TODO:(Nick) Move to getFrames
 	} else {
-		lsrc_->get(left_, right_, stream_);
-		calib_->rectifyStereo(left_, right_, stream_);
-		//rgb_ = lsrc_->cachedLeft();
-		left_.download(rgb_, stream_);
+		left.download(rgb_, stream_);
 		stream_.waitForCompletion();  // TODO:(Nick) Move to getFrames
 	}
+
+	auto cb = host_->callback();
+	if (cb) cb(timestamp_, rgb_, depth_);
 	return true;
 }
 
diff --git a/components/rgbd-sources/src/stereovideo.hpp b/components/rgbd-sources/src/stereovideo.hpp
index 7835742389330046a33b7f22289ac36e8cdab4d5..9d3325e1ac27ec544abeb409149cb89817dc29d2 100644
--- a/components/rgbd-sources/src/stereovideo.hpp
+++ b/components/rgbd-sources/src/stereovideo.hpp
@@ -26,9 +26,12 @@ class StereoVideoSource : public detail::Source {
 	StereoVideoSource(ftl::rgbd::Source*, const std::string &);
 	~StereoVideoSource();
 
-	bool grab(int n, int b);
+	void swap();
+	bool capture(int64_t ts);
+	bool retrieve();
+	bool compute(int n, int b);
 	bool isReady();
-	Camera parameters(channel_t chan);
+	Camera parameters(ftl::rgbd::Channel chan);
 
 	//const cv::Mat &getRight() const { return right_; }
 
@@ -38,16 +41,21 @@ class StereoVideoSource : public detail::Source {
 	Disparity *disp_;
 	
 	bool ready_;
+	bool use_optflow_;
 	
 	cv::cuda::Stream stream_;
+	cv::cuda::Stream stream2_;
+
+	std::vector<Frame> frames_;
 
-	cv::cuda::GpuMat left_;
-	cv::cuda::GpuMat right_;
-	cv::cuda::GpuMat disp_tmp_;
-	cv::cuda::GpuMat depth_tmp_;
-	
 	cv::Mat mask_l_;
 
+#ifdef HAVE_OPTFLOW
+	// see comments in https://gitlab.utu.fi/nicolas.pope/ftl/issues/155
+	cv::Ptr<cv::cuda::NvidiaOpticalFlow_1_0> nvof_;
+	cv::cuda::GpuMat optflow_;
+#endif
+
 	void init(const std::string &);
 };
 
diff --git a/components/rgbd-sources/src/streamer.cpp b/components/rgbd-sources/src/streamer.cpp
index 1d46ad7fd3c6ab9f35aa435bf6775927994eaceb..7a9118c9f47975a31d6389982b2adb818ed8a046 100644
--- a/components/rgbd-sources/src/streamer.cpp
+++ b/components/rgbd-sources/src/streamer.cpp
@@ -1,17 +1,23 @@
 #include <ftl/rgbd/streamer.hpp>
+#include <ftl/timer.hpp>
 #include <vector>
 #include <optional>
 #include <thread>
 #include <chrono>
 #include <tuple>
+#include <algorithm>
 
-#include "bitrate_settings.hpp"
+#include <ftl/rgbd/detail/abr.hpp>
+#include <ftl/codecs/encoder.hpp>
 
 using ftl::rgbd::Streamer;
 using ftl::rgbd::Source;
 using ftl::rgbd::detail::StreamSource;
 using ftl::rgbd::detail::StreamClient;
-using ftl::rgbd::detail::bitrate_settings;
+using ftl::rgbd::detail::ABRController;
+using ftl::codecs::definition_t;
+using ftl::codecs::device_t;
+using ftl::rgbd::Channel;
 using ftl::net::Universe;
 using std::string;
 using std::list;
@@ -23,18 +29,29 @@ using std::chrono::milliseconds;
 using std::tuple;
 using std::make_tuple;
 
+static const ftl::codecs::preset_t kQualityThreshold = ftl::codecs::kPresetLQThreshold;
+
 
 Streamer::Streamer(nlohmann::json &config, Universe *net)
-		: ftl::Configurable(config), late_(false), jobs_(0) {
+		: ftl::Configurable(config), late_(false) {
 
 	active_ = false;
 	net_ = net;
 	time_peer_ = ftl::UUID(0);
 	clock_adjust_ = 0;
+	mspf_ = ftl::timer::getInterval(); //1000 / value("fps", 20);
+	//last_dropped_ = 0;
+	//drop_count_ = 0;
+
+	encode_mode_ = ftl::rgbd::kEncodeVideo;
+	hq_devices_ = (value("disable_hardware_encode", false)) ? device_t::Software : device_t::Any;
+
+	//group_.setFPS(value("fps", 20));
+	group_.setLatency(4);
 
 	compress_level_ = value("compression", 1);
 	
-	net->bind("find_stream", [this](const std::string &uri) -> optional<UUID> {
+	net->bind("find_stream", [this](const std::string &uri) -> optional<ftl::UUID> {
 		SHARED_LOCK(mutex_,slk);
 
 		if (sources_.find(uri) != sources_.end()) {
@@ -75,7 +92,7 @@ Streamer::Streamer(nlohmann::json &config, Universe *net)
 	});
 
 	// Allow remote users to access camera calibration matrix
-	net->bind("source_details", [this](const std::string &uri, ftl::rgbd::channel_t chan) -> tuple<unsigned int,vector<unsigned char>> {
+	net->bind("source_details", [this](const std::string &uri, ftl::rgbd::Channel chan) -> tuple<unsigned int,vector<unsigned char>> {
 		vector<unsigned char> buf;
 		SHARED_LOCK(mutex_,slk);
 
@@ -94,11 +111,11 @@ Streamer::Streamer(nlohmann::json &config, Universe *net)
 		_addClient(source, N, rate, peer, dest);
 	});
 
-	net->bind("set_channel", [this](const string &uri, unsigned int chan) {
+	net->bind("set_channel", [this](const string &uri, Channel chan) {
 		SHARED_LOCK(mutex_,slk);
 
 		if (sources_.find(uri) != sources_.end()) {
-			sources_[uri]->src->setChannel((ftl::rgbd::channel_t)chan);
+			sources_[uri]->src->setChannel(chan);
 		}
 	});
 
@@ -112,6 +129,7 @@ Streamer::Streamer(nlohmann::json &config, Universe *net)
 }
 
 Streamer::~Streamer() {
+	timer_job_.cancel();
 	net_->unbind("find_stream");
 	net_->unbind("list_streams");
 	net_->unbind("source_calibration");
@@ -119,6 +137,19 @@ Streamer::~Streamer() {
 	net_->unbind("sync_streams");
 	net_->unbind("ping_streamer");
 	//pool_.stop();
+
+	{
+		UNIQUE_LOCK(mutex_,ulk);
+		for (auto &s : sources_) {
+			StreamSource *src = s.second;
+			src->clientCount = 0;
+		}
+	}
+	_cleanUp();
+	{
+		UNIQUE_LOCK(mutex_,ulk);
+		sources_.clear();
+	}
 }
 
 void Streamer::add(Source *src) {
@@ -132,7 +163,11 @@ void Streamer::add(Source *src) {
 		s->jobs = 0;
 		s->frame = 0;
 		s->clientCount = 0;
+		s->hq_count = 0;
+		s->lq_count = 0;
 		sources_[src->getID()] = s;
+
+		group_.addSource(src);
 	}
 
 	LOG(INFO) << "Streaming: " << src->getID();
@@ -149,7 +184,7 @@ void Streamer::_addClient(const string &source, int N, int rate, const ftl::UUID
 		if (rate < 0 || rate >= 10) return;
 		if (N < 0 || N > ftl::rgbd::kMaxFrames) return;
 
-		DLOG(INFO) << "Adding Stream Peer: " << peer.to_string() << " rate=" << rate << " N=" << N;
+		//DLOG(INFO) << "Adding Stream Peer: " << peer.to_string() << " rate=" << rate << " N=" << N;
 
 		s = sources_[source];
 
@@ -157,15 +192,33 @@ void Streamer::_addClient(const string &source, int N, int rate, const ftl::UUID
 		if (time_peer_ == ftl::UUID(0)) {
 			time_peer_ = peer;
 
-			// Also do a time sync (but should be repeated periodically)
-			auto start = std::chrono::high_resolution_clock::now();
-			int64_t mastertime = net_->call<int64_t>(peer, "__ping__");
-			auto elapsed = std::chrono::high_resolution_clock::now() - start;
-			int64_t latency = std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count();
-			clock_adjust_ = mastertime - (std::chrono::time_point_cast<std::chrono::milliseconds>(start).time_since_epoch().count() + (latency/2));
-			LOG(INFO) << "Clock adjustment: " << clock_adjust_;
-			LOG(INFO) << "Latency: " << (latency / 2);
-			LOG(INFO) << "Local: " << std::chrono::time_point_cast<std::chrono::milliseconds>(start).time_since_epoch().count() << ", master: " << mastertime;
+			// Do a time sync whenever the CPU is idle for 10ms or more.
+			// FIXME: Could be starved
+			timer_job_ = ftl::timer::add(ftl::timer::kTimerIdle10, [peer,this](int id) {
+				auto start = std::chrono::high_resolution_clock::now();
+				int64_t mastertime;
+
+				try {
+					mastertime = net_->call<int64_t>(peer, "__ping__");
+				} catch (...) {
+					// Reset time peer and remove timer
+					time_peer_ = ftl::UUID(0);
+					return false;
+				}
+
+				auto elapsed = std::chrono::high_resolution_clock::now() - start;
+				int64_t latency = std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count();
+				auto clock_adjust = mastertime - (ftl::timer::get_time() + (latency/2));
+
+				if (clock_adjust > 0) {
+					LOG(INFO) << "Clock adjustment: " << clock_adjust;
+					//LOG(INFO) << "Latency: " << (latency / 2);
+					//LOG(INFO) << "Local: " << std::chrono::time_point_cast<std::chrono::milliseconds>(start).time_since_epoch().count() << ", master: " << mastertime;
+					ftl::timer::setClockAdjustment(clock_adjust);
+				}
+
+				return true;
+			});
 		}
 	}
 
@@ -173,21 +226,48 @@ void Streamer::_addClient(const string &source, int N, int rate, const ftl::UUID
 
 	SHARED_LOCK(mutex_, slk);
 	UNIQUE_LOCK(s->mutex, lk2);
-	for (auto &client : s->clients[rate]) {
+	for (auto &client : s->clients) {
 		// If already listening, just update chunk counters
 		if (client.peerid == peer) {
-			client.txmax = N * kChunkCount;
+			client.txmax = N;
 			client.txcount = 0;
+
+			// Possible switch from high quality to low quality encoding or vice versa
+			if (client.preset < kQualityThreshold && rate >= kQualityThreshold) {
+				s->hq_count--;
+				s->lq_count++;
+				if (s->lq_encoder_c1) s->lq_encoder_c1->reset();
+				if (s->lq_encoder_c2) s->lq_encoder_c2->reset();
+			} else if (client.preset >= kQualityThreshold && rate < kQualityThreshold) {
+				s->hq_count++;
+				s->lq_count--;
+				if (s->hq_encoder_c1) s->hq_encoder_c1->reset();
+				if (s->hq_encoder_c2) s->hq_encoder_c2->reset();
+			}
+
+			client.preset = rate;
 			return;
 		}
 	}
 
 	// Not an existing client so add one
-	StreamClient &c = s->clients[rate].emplace_back();
+	StreamClient &c = s->clients.emplace_back();
 	c.peerid = peer;
 	c.uri = dest;
 	c.txcount = 0;
-	c.txmax = N * kChunkCount;
+	c.txmax = N;
+	c.preset = rate;
+
+	if (rate >= kQualityThreshold) {
+		if (s->lq_encoder_c1) s->lq_encoder_c1->reset();
+		if (s->lq_encoder_c2) s->lq_encoder_c2->reset();
+		s->lq_count++;
+	} else {
+		if (s->hq_encoder_c1) s->hq_encoder_c1->reset();
+		if (s->hq_encoder_c2) s->hq_encoder_c2->reset();
+		s->hq_count++;
+	}
+
 	++s->clientCount;
 }
 
@@ -200,237 +280,387 @@ void Streamer::remove(const std::string &) {
 }
 
 void Streamer::stop() {
-	active_ = false;
-	wait();
-}
-
-void Streamer::poll() {
-	//double wait = 1.0f / 25.0f;  // TODO:(Nick) Should be in config
-	//auto start = std::chrono::high_resolution_clock::now();
-	//int64_t now = std::chrono::time_point_cast<std::chrono::milliseconds>(start).time_since_epoch().count()+clock_adjust_;
-
-	//int64_t msdelay = 40 - (now % 40);
-	//while (msdelay >= 20) {
-	//	sleep_for(milliseconds(10));
-	//	now = std::chrono::time_point_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now()).time_since_epoch().count()+clock_adjust_;
-	//	msdelay = 40 - (now % 40);
-	//}
-	//LOG(INFO) << "Required Delay: " << (now / 40) << " = " << msdelay;
-
-	// Create frame jobs at correct FPS interval
-	_schedule();
-	//std::function<void(int)> j = ftl::pool.pop();
-	//if (j) j(-1);
-
-	//std::chrono::duration<double> elapsed =
-	//	std::chrono::high_resolution_clock::now() - start;
-
-	//if (elapsed.count() >= wait) {
-		//LOG(WARNING) << "Frame rate below optimal @ " << (1.0f / elapsed.count());
-	//} else {
-		//LOG(INFO) << "Frame rate @ " << (1.0f / elapsed.count());
-		// Otherwise, wait until next frame should start.
-		// FIXME:(Nick) Is this accurate enough? Almost certainly not
-		// TODO:(Nick) Synchronise by time corrections and use of fixed time points
-		// but this only works if framerate can be achieved.
-		//sleep_for(milliseconds((long long)((wait - elapsed.count()) * 1000.0f)));
-	//}
+	group_.stop();
 }
 
 void Streamer::run(bool block) {
-	active_ = true;
-
 	if (block) {
-		while (ftl::running && active_) {
-			poll();
-		}
+		group_.sync([this](FrameSet &fs) -> bool {
+			_process(fs);
+			return true;
+		});
 	} else {
 		// Create thread job for frame ticking
 		ftl::pool.push([this](int id) {
-			while (ftl::running && active_) {
-				poll();
-			}
+			group_.sync([this](FrameSet &fs) -> bool {
+				_process(fs);
+				return true;
+			});
 		});
 	}
 }
 
-// Must be called in source locked state or src.state must be atomic
-void Streamer::_swap(StreamSource *src) {
-	if (src->jobs == 0) {
+void Streamer::_cleanUp() {
+	for (auto &s : sources_) {
+		StreamSource *src = s.second;
 		UNIQUE_LOCK(src->mutex,lk);
 
-		for (unsigned int b=0; b<10; ++b) {
-			auto i = src->clients[b].begin();
-			while (i != src->clients[b].end()) {
-				// Client request completed so remove from list
-				if ((*i).txcount >= (*i).txmax) {
-					LOG(INFO) << "Remove client: " << (*i).uri;
-					i = src->clients[b].erase(i);
-					--src->clientCount;
+		auto i = src->clients.begin();
+		while (i != src->clients.end()) {
+			// Client request completed so remove from list
+			if ((*i).txcount >= (*i).txmax) {
+				// If peer was clock sync master, the remove that...
+				if ((*i).peerid == time_peer_) {
+					timer_job_.cancel();
+					time_peer_ = ftl::UUID(0);
+				}
+				LOG(INFO) << "Remove client: " << (*i).uri;
+
+				if ((*i).preset < kQualityThreshold) {
+					src->hq_count--;
 				} else {
-					i++;
+					src->lq_count--;
 				}
+
+				i = src->clients.erase(i);
+				--src->clientCount;
+			} else {
+				i++;
 			}
 		}
 
-		src->src->getFrames(src->rgb, src->depth);
+		if (src->hq_count == 0) {
+			if (src->hq_encoder_c1) ftl::codecs::free(src->hq_encoder_c1);
+			if (src->hq_encoder_c2) ftl::codecs::free(src->hq_encoder_c2);
+		}
 
-		//if (!src->rgb.empty() && src->prev_depth.empty()) {
-			//src->prev_depth = cv::Mat(src->rgb.size(), CV_16UC1, cv::Scalar(0));
-			//LOG(INFO) << "Creating prevdepth: " << src->rgb.cols << "," << src->rgb.rows;
-		//}
-		src->jobs = 0;
-		src->frame++;
+		if (src->lq_count == 0) {
+			if (src->lq_encoder_c1) ftl::codecs::free(src->lq_encoder_c1);
+			if (src->lq_encoder_c2) ftl::codecs::free(src->lq_encoder_c2);
+		}
+
+		if (src->clientCount == 0) {
+
+		}
 	}
 }
 
-void Streamer::wait() {
-	// Do some jobs in this thread, might as well...
-	std::function<void(int)> j;
-	while ((bool)(j=ftl::pool.pop())) {
-		j(-1);
-	}
+void Streamer::_process(ftl::rgbd::FrameSet &fs) {
+	// Prevent new clients during processing.
+	SHARED_LOCK(mutex_,slk);
 
-	// Wait for all jobs to complete before finishing frame
-	//UNIQUE_LOCK(job_mtx_, lk);
-	std::unique_lock<std::mutex> lk(job_mtx_);
-	job_cv_.wait_for(lk, std::chrono::seconds(20), [this]{ return jobs_ == 0; });
-	if (jobs_ != 0) {
-		LOG(FATAL) << "Deadlock detected";
+	if (fs.sources.size() != sources_.size()) {
+		LOG(ERROR) << "Incorrect number of sources in frameset: " << fs.sources.size() << " vs " << sources_.size();
+		return;
 	}
 
-	// Capture frame number?
-	frame_no_ = last_frame_;
-}
+	int totalclients = 0;
 
-void Streamer::_schedule() {
-	wait();
-	//std::mutex job_mtx;
-	//std::condition_variable job_cv;
-	//int jobs = 0;
+	frame_no_ = fs.timestamp;
 
-	//auto now = std::chrono::time_point_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now()).time_since_epoch().count()+clock_adjust_;
-	//LOG(INFO) << "Frame time = " << (now-(last_frame_*40)) << "ms";
+	for (int j=0; j<fs.sources.size(); ++j) {
+		StreamSource *src = sources_[fs.sources[j]->getID()];
+		SHARED_LOCK(src->mutex,lk);
 
-	// Prevent new clients during processing.
-	SHARED_LOCK(mutex_,slk);
+		// Don't do any work in the following cases
+		if (!src) continue;
+		if (!fs.sources[j]->isReady()) continue;
+		if (src->clientCount == 0) continue;
+		//if (fs.channel1[j].empty() || (fs.sources[j]->getChannel() != ftl::rgbd::kChanNone && fs.channel2[j].empty())) continue;
+		if (!fs.frames[j].hasChannel(Channel::Colour) || !fs.frames[j].hasChannel(fs.sources[j]->getChannel())) continue;
+
+		bool hasChan2 = fs.sources[j]->getChannel() != Channel::None;
+
+		totalclients += src->clientCount;
+
+		// Do we need to do high quality encoding?
+		if (src->hq_count > 0) {
+			if (!src->hq_encoder_c1) src->hq_encoder_c1 = ftl::codecs::allocateEncoder(
+					definition_t::HD1080, hq_devices_);
+			if (!src->hq_encoder_c2 && hasChan2) src->hq_encoder_c2 = ftl::codecs::allocateEncoder(
+					definition_t::HD1080, hq_devices_);
+
+			// Do we have the resources to do a HQ encoding?
+			if (src->hq_encoder_c1 && (!hasChan2 || src->hq_encoder_c2)) {
+				auto *enc1 = src->hq_encoder_c1;
+				auto *enc2 = src->hq_encoder_c2;
+
+				// Important to send channel 2 first if needed...
+				// Receiver only waits for channel 1 by default
+				// TODO: Each encode could be done in own thread
+				if (hasChan2) {
+					enc2->encode(fs.frames[j].get<cv::Mat>(fs.sources[j]->getChannel()), src->hq_bitrate, [this,src,hasChan2](const ftl::codecs::Packet &blk){
+						_transmitPacket(src, blk, 1, hasChan2, true);
+					});
+				} else {
+					if (enc2) enc2->reset();
+				}
+
+				enc1->encode(fs.frames[j].get<cv::Mat>(Channel::Colour), src->hq_bitrate, [this,src,hasChan2](const ftl::codecs::Packet &blk){
+					_transmitPacket(src, blk, 0, hasChan2, true);
+				});
+			}
+		}
 
-	for (auto s : sources_) {
-		string uri = s.first;
+		// Do we need to do low quality encoding?
+		if (src->lq_count > 0) {
+			if (!src->lq_encoder_c1) src->lq_encoder_c1 = ftl::codecs::allocateEncoder(
+					definition_t::SD480, device_t::Software);
+			if (!src->lq_encoder_c2 && hasChan2) src->lq_encoder_c2 = ftl::codecs::allocateEncoder(
+					definition_t::SD480, device_t::Software);
+
+			// Do we have the resources to do a LQ encoding?
+			if (src->lq_encoder_c1 && (!hasChan2 || src->lq_encoder_c2)) {
+				auto *enc1 = src->lq_encoder_c1;
+				auto *enc2 = src->lq_encoder_c2;
+
+				// Important to send channel 2 first if needed...
+				// Receiver only waits for channel 1 by default
+				if (hasChan2) {
+					enc2->encode(fs.frames[j].get<cv::Mat>(fs.sources[j]->getChannel()), src->lq_bitrate, [this,src,hasChan2](const ftl::codecs::Packet &blk){
+						_transmitPacket(src, blk, 1, hasChan2, false);
+					});
+				} else {
+					if (enc2) enc2->reset();
+				}
 
-		// No point in doing work if no clients
-		if (s.second->clientCount == 0) {
-			continue;
+				enc1->encode(fs.frames[j].get<cv::Mat>(Channel::Colour), src->lq_bitrate, [this,src,hasChan2](const ftl::codecs::Packet &blk){
+					_transmitPacket(src, blk, 0, hasChan2, false);
+				});
+			}
 		}
 
-		// There will be two jobs for this source...
-		//UNIQUE_LOCK(job_mtx_,lk);
-		jobs_ += 1 + kChunkCount;
-		//lk.unlock();
+		// Do we need to do low quality encoding?
+		/*if (src->lq_count > 0) {
+			if (!src->lq_encoder_c1) src->lq_encoder_c1 = ftl::codecs::allocateLQEncoder();
+			if (!src->lq_encoder_c2) src->lq_encoder_c2 = ftl::codecs::allocateLQEncoder();
+
+			// Do we have the resources to do a LQ encoding?
+			if (src->lq_encoder_c1 && src->lq_encoder_c2) {
+				const auto *enc1 = src->lq_encoder_c1;
+				const auto *enc2 = src->lq_encoder_c2;
+
+				// Do entire frame as single step
+				if (!enc1->useBlocks() || !enc2->useBlocks()) {
+					ftl::pool.push([this,&fs,j,src](int id) {
+						_encodeLQAndTransmit(src, fs.channel1[j], fs.channel2[j], -1);
+						std::unique_lock<std::mutex> lk(job_mtx_);
+						--jobs_;
+						if (jobs_ == 0) job_cv_.notify_one();
+					});
+
+					jobs_++;
+				// Or divide frame into blocks and encode each
+				} else {
+					// Create jobs for each chunk
+					for (int i=0; i<chunk_count_; ++i) {
+						// Add chunk job to thread pool
+						ftl::pool.push([this,&fs,j,i,src](int id) {
+							int chunk = i;
+							try {
+								_encodeLQAndTransmit(src, fs.channel1[j], fs.channel2[j], chunk);
+							} catch(...) {
+								LOG(ERROR) << "Encode Exception: " << chunk;
+							}
+
+							//src->jobs--;
+							std::unique_lock<std::mutex> lk(job_mtx_);
+							--jobs_;
+							if (jobs_ == 0) job_cv_.notify_one();
+						});
+					}
+
+					jobs_ += chunk_count_;
+				}
+			}
+		}*/
+	}
 
-		StreamSource *src = sources_[uri];
-		if (src == nullptr || src->jobs != 0) continue;
-		src->jobs = 1 + kChunkCount;
+	/*std::unique_lock<std::mutex> lk(job_mtx_);
+	job_cv_.wait_for(lk, std::chrono::seconds(20), [this]{ return jobs_ == 0; });
+	if (jobs_ != 0) {
+		LOG(FATAL) << "Deadlock detected";
+	}*/
 
-		// Grab job
-		ftl::pool.push([this,src](int id) {
-			//auto start = std::chrono::high_resolution_clock::now();
+	// Go to sleep if no clients instead of spinning the cpu
+	if (totalclients == 0 || sources_.size() == 0) {
+		// Make sure to unlock so clients can connect!
+		//lk.unlock();
+		slk.unlock();
+		sleep_for(milliseconds(50));
+	} else _cleanUp();
+}
 
-			auto start = std::chrono::high_resolution_clock::now();
-			int64_t now = std::chrono::time_point_cast<std::chrono::milliseconds>(start).time_since_epoch().count()+clock_adjust_;
-			int64_t target = now / 40;
+void Streamer::_transmitPacket(StreamSource *src, const ftl::codecs::Packet &pkt, int chan, bool hasChan2, bool hqonly) {
+	ftl::codecs::StreamPacket spkt = {
+		frame_no_,
+		static_cast<uint8_t>((chan & 0x1) | ((hasChan2) ? 0x2 : 0x0))
+	};
+
+	// Lock to prevent clients being added / removed
+	//SHARED_LOCK(src->mutex,lk);
+	auto c = src->clients.begin();
+	while (c != src->clients.end()) {
+		const ftl::codecs::preset_t b = (*c).preset;
+		if ((hqonly && b >= kQualityThreshold) || (!hqonly && b < kQualityThreshold)) {
+			++c;
+			continue;
+		}
 
-			// TODO:(Nick) A now%40 == 0 should be accepted
-			if (target != last_frame_) LOG(WARNING) << "Frame " << "(" << (target-last_frame_) << ") dropped by " << (now%40) << "ms";
+		try {
+			// TODO:(Nick) Send pose
+			short pre_transmit_latency = short(ftl::timer::get_time() - spkt.timestamp);
+			if (!net_->send((*c).peerid,
+					(*c).uri,
+					pre_transmit_latency,  // Time since timestamp for tx
+					spkt,
+					pkt)) {
 
-			// Use sleep_for for larger delays
-			int64_t msdelay = 40 - (now % 40);
-			//LOG(INFO) << "Required Delay: " << (now / 40) << " = " << msdelay;
-			while (msdelay >= 20) {
-				sleep_for(milliseconds(10));
-				now = std::chrono::time_point_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now()).time_since_epoch().count()+clock_adjust_;
-				msdelay = 40 - (now % 40);
+				// Send failed so mark as client stream completed
+				(*c).txcount = (*c).txmax;
+			} else {
+				// Count frame as completed only if last block and channel is 0
+				if (pkt.block_number == pkt.block_total - 1 && chan == 0) ++(*c).txcount;
 			}
+		} catch(...) {
+			(*c).txcount = (*c).txmax;
+		}
+		++c;
+	}
+}
 
-			// Spin loop until exact grab time
-			//LOG(INFO) << "Spin Delay: " << (now / 40) << " = " << (40 - (now%40));
-			target = now / 40;
-			while ((now/40) == target) {
-				_mm_pause();  // SSE2 nano pause intrinsic
-				now = std::chrono::time_point_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now()).time_since_epoch().count()+clock_adjust_;
-			};
-			last_frame_ = now/40;
-
-			try {
-				src->src->grab();
-			} catch (std::exception &ex) {
-				LOG(ERROR) << "Exception when grabbing frame";
-				LOG(ERROR) << ex.what();
-			}
-			catch (...) {
-				LOG(ERROR) << "Unknown exception when grabbing frame";
-			}
+/*void Streamer::_encodeHQAndTransmit(StreamSource *src, const cv::Mat &c1, const cv::Mat &c2, int block) {
+	bool hasChan2 = (!c2.empty() && src->src->getChannel() != ftl::rgbd::kChanNone);
 
-			//now = std::chrono::time_point_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now()).time_since_epoch().count()+clock_adjust_;
-			//if (now%40 > 0) LOG(INFO) << "Grab in: " << (now%40) << "ms";
+	LOG(INFO) << "Encode HQ: " << block;
 
-			//std::chrono::duration<double> elapsed =
-			//	std::chrono::high_resolution_clock::now() - start;
-			//LOG(INFO) << "Grab in " << elapsed.count() << "s";
+	vector<unsigned char> c1buff;
+	vector<unsigned char> c2buff;
 
-			src->jobs--;
-			_swap(src);
+	if (block == -1) {
+		src->hq_encoder_c1->encode(c1, c1buff, src->hq_bitrate, false);
+		if (hasChan2) src->hq_encoder_c2->encode(c2, c2buff, src->hq_bitrate, false);
+	} else {
+		//bool delta = (chunk+src->frame) % 8 > 0;  // Do XOR or not
+		int chunk_width = c1.cols / chunk_dim_;
+		int chunk_height = c1.rows / chunk_dim_;
+
+		// Build chunk heads
+		int cx = (block % chunk_dim_) * chunk_width;
+		int cy = (block / chunk_dim_) * chunk_height;
+		cv::Rect roi(cx,cy,chunk_width,chunk_height);
+		//vector<unsigned char> rgb_buf;
+		cv::Mat chunkRGB = c1(roi);
+		src->hq_encoder_c1->encode(chunkRGB, c1buff, src->hq_bitrate, false);
+
+		if (hasChan2) {
+			cv::Mat chunkDepth = c2(roi);
+			src->hq_encoder_c2->encode(chunkDepth, c2buff, src->hq_bitrate, false);
+		}
+	}
 
-			// Mark job as finished
-			std::unique_lock<std::mutex> lk(job_mtx_);
-			--jobs_;
-			job_cv_.notify_one();
-		});
+	// Lock to prevent clients being added / removed
+	SHARED_LOCK(src->mutex,lk);
+	auto c = src->clients.begin();
+	while (c != src->clients.end()) {
+		const int b = (*c).bitrate;
+		if (b >= kQualityThreshold) continue; // Not a HQ request
+
+		try {
+			// TODO:(Nick) Send pose
+			short pre_transmit_latency = short(ftl::timer::get_time() - frame_no_);
+			if (!net_->send((*c).peerid, (*c).uri, frame_no_, pre_transmit_latency, uint8_t(src->hq_bitrate), block, c1buff, c2buff)) {
+				// Send failed so mark as client stream completed
+				(*c).txcount = (*c).txmax;
+			} else {
+				++(*c).txcount;
+				//LOG(INFO) << "SENT CHUNK : " << frame_no_ << "-" << chunk;
+			}
+		} catch(...) {
+			(*c).txcount = (*c).txmax;
+		}
+		++c;
+	}
+}
 
-		// Create jobs for each chunk
-		for (int i=0; i<kChunkCount; ++i) {
-			// Add chunk job to thread pool
-			ftl::pool.push([this,src,i](int id) {
-				int chunk = i;
-				try {
-				if (!src->rgb.empty() && (src->src->getChannel() == ftl::rgbd::kChanNone || !src->depth.empty())) {
-					_encodeAndTransmit(src, chunk);
-				}
-				} catch(...) {
-					LOG(ERROR) << "Encode Exception: " << chunk;
-				}
+void Streamer::_encodeLQAndTransmit(StreamSource *src, const cv::Mat &c1, const cv::Mat &c2, int block) {
+	bool hasChan2 = (!c2.empty() && src->src->getChannel() != ftl::rgbd::kChanNone);
 
-				src->jobs--;
-				_swap(src);
-				std::unique_lock<std::mutex> lk(job_mtx_);
-				--jobs_;
-				job_cv_.notify_one();
-			});
+	LOG(INFO) << "Encode LQ: " << block;
+
+	vector<unsigned char> c1buff;
+	vector<unsigned char> c2buff;
+
+	if (block == -1) {
+		src->lq_encoder_c1->encode(c1, c1buff, src->lq_bitrate, false);
+		if (hasChan2) src->lq_encoder_c2->encode(c2, c2buff, src->lq_bitrate, false);
+	} else {
+		//bool delta = (chunk+src->frame) % 8 > 0;  // Do XOR or not
+		int chunk_width = c1.cols / chunk_dim_;
+		int chunk_height = c1.rows / chunk_dim_;
+
+		// Build chunk heads
+		int cx = (block % chunk_dim_) * chunk_width;
+		int cy = (block / chunk_dim_) * chunk_height;
+		cv::Rect roi(cx,cy,chunk_width,chunk_height);
+		//vector<unsigned char> rgb_buf;
+		cv::Mat chunkRGB = c1(roi);
+		//cv::resize(chunkRGB, downrgb, cv::Size(ABRController::getColourWidth(b) / chunk_dim_, ABRController::getColourHeight(b) / chunk_dim_));
+
+		src->lq_encoder_c1->encode(chunkRGB, c1buff, src->lq_bitrate, false);
+
+		if (hasChan2) {
+			cv::Mat chunkDepth = c2(roi);
+			//cv::resize(chunkDepth, tmp, cv::Size(ABRController::getDepthWidth(b) / chunk_dim_, ABRController::getDepthHeight(b) / chunk_dim_), 0, 0, cv::INTER_NEAREST);
+			src->lq_encoder_c2->encode(chunkDepth, c2buff, src->lq_bitrate, false);
 		}
 	}
-}
 
-void Streamer::_encodeAndTransmit(StreamSource *src, int chunk) {
-	bool hasChan2 = (!src->depth.empty() && src->src->getChannel() != ftl::rgbd::kChanNone);
+	// Lock to prevent clients being added / removed
+	SHARED_LOCK(src->mutex,lk);
+	auto c = src->clients.begin();
+	while (c != src->clients.end()) {
+		const int b = (*c).bitrate;
+		if (b < kQualityThreshold) continue; // Not an LQ request
+
+		try {
+			// TODO:(Nick) Send pose
+			short pre_transmit_latency = short(ftl::timer::get_time() - frame_no_);
+			if (!net_->send((*c).peerid, (*c).uri, frame_no_, pre_transmit_latency, uint8_t(src->hq_bitrate), block, c1buff, c2buff)) {
+				// Send failed so mark as client stream completed
+				(*c).txcount = (*c).txmax;
+			} else {
+				++(*c).txcount;
+				//LOG(INFO) << "SENT CHUNK : " << frame_no_ << "-" << chunk;
+			}
+		} catch(...) {
+			(*c).txcount = (*c).txmax;
+		}
+		++c;
+	}
+}*/
+
+/*void Streamer::_encodeImagesAndTransmit(StreamSource *src, const cv::Mat &rgb, const cv::Mat &depth, int chunk) {
+	bool hasChan2 = (!depth.empty() && src->src->getChannel() != ftl::rgbd::kChanNone);
 
-	bool delta = (chunk+src->frame) % 8 > 0;  // Do XOR or not
-	int chunk_width = src->rgb.cols / kChunkDim;
-	int chunk_height = src->rgb.rows / kChunkDim;
+	//bool delta = (chunk+src->frame) % 8 > 0;  // Do XOR or not
+	int chunk_width = rgb.cols / chunk_dim_;
+	int chunk_height = rgb.rows / chunk_dim_;
 
 	// Build chunk heads
-	int cx = (chunk % kChunkDim) * chunk_width;
-	int cy = (chunk / kChunkDim) * chunk_height;
+	int cx = (chunk % chunk_dim_) * chunk_width;
+	int cy = (chunk / chunk_dim_) * chunk_height;
 	cv::Rect roi(cx,cy,chunk_width,chunk_height);
-	vector<unsigned char> rgb_buf;
-	cv::Mat chunkRGB = src->rgb(roi);
+	//vector<unsigned char> rgb_buf;
+	cv::Mat chunkRGB = rgb(roi);
 	cv::Mat chunkDepth;
 	//cv::Mat chunkDepthPrev = src->prev_depth(roi);
 
 	cv::Mat d2, d3;
-	vector<unsigned char> d_buf;
+	//vector<unsigned char> d_buf;
 
 	if (hasChan2) {
-		chunkDepth = src->depth(roi);
+		chunkDepth = depth(roi);
 		if (chunkDepth.type() == CV_32F) chunkDepth.convertTo(d2, CV_16UC1, 1000); // 16*10);
 		else d2 = chunkDepth;
 		//if (delta) d3 = (d2 * 2) - chunkDepthPrev;
@@ -438,65 +668,68 @@ void Streamer::_encodeAndTransmit(StreamSource *src, int chunk) {
 		//d2.copyTo(chunkDepthPrev);
 	}
 
-	// For each allowed bitrate setting (0 = max quality)
-	for (unsigned int b=0; b<10; ++b) {
-		{
-			//SHARED_LOCK(src->mutex,lk);
-			if (src->clients[b].size() == 0) continue;
-		}
-		
-		// Max bitrate means no changes
-		if (b == 0) {
-			_encodeChannel1(chunkRGB, rgb_buf, b);
-			if (hasChan2) _encodeChannel2(d2, d_buf, src->src->getChannel(), b);
-
-		// Otherwise must downscale and change compression params
-		// TODO:(Nick) could reuse downscales
-		} else {
-			cv::Mat downrgb, downdepth;
-			cv::resize(chunkRGB, downrgb, cv::Size(bitrate_settings[b].width / kChunkDim, bitrate_settings[b].height / kChunkDim));
-			if (hasChan2) cv::resize(d2, downdepth, cv::Size(bitrate_settings[b].width / kChunkDim, bitrate_settings[b].height / kChunkDim));
-
-			_encodeChannel1(downrgb, rgb_buf, b);
-			if (hasChan2) _encodeChannel2(downdepth, d_buf, src->src->getChannel(), b);
+	// TODO: Verify these don't allocate memory if not needed.
+	// TODO: Reuse these buffers to reduce allocations.
+	vector<unsigned char> brgb[ftl::rgbd::detail::kMaxBitrateLevels];
+	vector<unsigned char> bdepth[ftl::rgbd::detail::kMaxBitrateLevels];
+
+	// Lock to prevent clients being added / removed
+	SHARED_LOCK(src->mutex,lk);
+	auto c = src->clients.begin();
+	while (c != src->clients.end()) {
+		const int b = (*c).bitrate;
+
+		if (brgb[b].empty()) {
+			// Max bitrate means no changes
+			if (b == 0) {
+				_encodeImageChannel1(chunkRGB, brgb[b], b);
+				if (hasChan2) _encodeImageChannel2(d2, bdepth[b], src->src->getChannel(), b);
+
+			// Otherwise must downscale and change compression params
+			} else {
+				cv::Mat downrgb, downdepth;
+				cv::resize(chunkRGB, downrgb, cv::Size(ABRController::getColourWidth(b) / chunk_dim_, ABRController::getColourHeight(b) / chunk_dim_));
+				if (hasChan2) cv::resize(d2, downdepth, cv::Size(ABRController::getDepthWidth(b) / chunk_dim_, ABRController::getDepthHeight(b) / chunk_dim_), 0, 0, cv::INTER_NEAREST);
+
+				_encodeImageChannel1(downrgb, brgb[b], b);
+				if (hasChan2) _encodeImageChannel2(downdepth, bdepth[b], src->src->getChannel(), b);
+			}
 		}
 
-		//if (chunk == 0) LOG(INFO) << "Sending chunk " << chunk << " : size = " << (d_buf.size()+rgb_buf.size()) << "bytes";
-
-		// Lock to prevent clients being added / removed
-		SHARED_LOCK(src->mutex,lk);
-		auto c = src->clients[b].begin();
-		while (c != src->clients[b].end()) {
-			try {
-				// TODO:(Nick) Send pose and timestamp
-				if (!net_->send((*c).peerid, (*c).uri, frame_no_, chunk, delta, rgb_buf, d_buf)) {
-					// Send failed so mark as client stream completed
-					(*c).txcount = (*c).txmax;
-				} else {
-					++(*c).txcount;
-				}
-			} catch(...) {
+		try {
+			// TODO:(Nick) Send pose
+			short pre_transmit_latency = short(ftl::timer::get_time() - frame_no_);
+			if (!net_->send((*c).peerid, (*c).uri, frame_no_, pre_transmit_latency, uint8_t(b), chunk, brgb[b], bdepth[b])) {
+				// Send failed so mark as client stream completed
 				(*c).txcount = (*c).txmax;
+			} else {
+				++(*c).txcount;
+				//LOG(INFO) << "SENT CHUNK : " << frame_no_ << "-" << chunk;
 			}
-			++c;
+		} catch(...) {
+			(*c).txcount = (*c).txmax;
 		}
+		++c;
 	}
 }
 
-void Streamer::_encodeChannel1(const cv::Mat &in, vector<unsigned char> &out, unsigned int b) {
-	vector<int> jpgparams = {cv::IMWRITE_JPEG_QUALITY, bitrate_settings[b].jpg_quality};
+void Streamer::_encodeImageChannel1(const cv::Mat &in, vector<unsigned char> &out, unsigned int b) {
+	vector<int> jpgparams = {cv::IMWRITE_JPEG_QUALITY, ABRController::getColourQuality(b)};
 	cv::imencode(".jpg", in, out, jpgparams);
 }
 
-bool Streamer::_encodeChannel2(const cv::Mat &in, vector<unsigned char> &out, ftl::rgbd::channel_t c, unsigned int b) {
+bool Streamer::_encodeImageChannel2(const cv::Mat &in, vector<unsigned char> &out, ftl::rgbd::channel_t c, unsigned int b) {
 	if (c == ftl::rgbd::kChanNone) return false;  // NOTE: Should not happen
 
 	if (isFloatChannel(c) && in.type() == CV_16U && in.channels() == 1) {
-		vector<int> params = {cv::IMWRITE_PNG_COMPRESSION, bitrate_settings[b].png_compression};
-		cv::imencode(".png", in, out, params);
+		vector<int> params = {cv::IMWRITE_PNG_COMPRESSION, ABRController::getDepthQuality(b)};
+		if (!cv::imencode(".png", in, out, params)) {
+			LOG(ERROR) << "PNG Encoding error";
+			return false;
+		}
 		return true;
 	} else if (!isFloatChannel(c) && in.type() == CV_8UC3) {
-		vector<int> params = {cv::IMWRITE_JPEG_QUALITY, bitrate_settings[b].jpg_quality};
+		vector<int> params = {cv::IMWRITE_JPEG_QUALITY, ABRController::getColourQuality(b)};
 		cv::imencode(".jpg", in, out, params);
 		return true;
 	} else {
@@ -510,4 +743,4 @@ Source *Streamer::get(const std::string &uri) {
 	SHARED_LOCK(mutex_,slk);
 	if (sources_.find(uri) != sources_.end()) return sources_[uri]->src;
 	else return nullptr;
-}
+}*/
diff --git a/components/rgbd-sources/src/virtual.cpp b/components/rgbd-sources/src/virtual.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..62b404155e1f302111e99183347d87c9dbda3a0b
--- /dev/null
+++ b/components/rgbd-sources/src/virtual.cpp
@@ -0,0 +1,138 @@
+#include <ftl/rgbd/virtual.hpp>
+
+using ftl::rgbd::VirtualSource;
+using ftl::rgbd::Source;
+using ftl::rgbd::Channel;
+
+class VirtualImpl : public ftl::rgbd::detail::Source {
+	public:
+	explicit VirtualImpl(ftl::rgbd::Source *host, const ftl::rgbd::Camera &params) : ftl::rgbd::detail::Source(host) {
+		params_ = params;
+		capabilities_ = ftl::rgbd::kCapMovable | ftl::rgbd::kCapVideo | ftl::rgbd::kCapStereo;
+	}
+
+	~VirtualImpl() {
+
+	}
+
+	bool capture(int64_t ts) override {
+		timestamp_ = ts;
+		return true;
+	}
+
+	bool retrieve() override {
+		return true;
+	}
+
+	bool compute(int n, int b) override {
+		if (callback) {
+			frame.reset();
+
+			try {
+				callback(frame);
+			} catch (std::exception &e) {
+				LOG(ERROR) << "Exception in render callback: " << e.what();
+			} catch (...) {
+				LOG(ERROR) << "Unknown exception in render callback";
+			}
+
+			if (frame.hasChannel(Channel::Colour) && frame.hasChannel(Channel::Depth)) {
+				frame.download(Channel::Colour + Channel::Depth);
+				cv::swap(frame.get<cv::Mat>(Channel::Colour), rgb_);
+				cv::swap(frame.get<cv::Mat>(Channel::Depth), depth_);
+				LOG(INFO) << "Written: " << rgb_.cols;
+			} else {
+				LOG(ERROR) << "Missing colour or depth frame in rendering";
+			}
+
+			auto cb = host_->callback();
+			if (cb) cb(timestamp_, rgb_, depth_);
+		}
+		return true;
+	}
+
+	bool isReady() override { return true; }
+
+	std::function<void(ftl::rgbd::Frame &)> callback;
+	ftl::rgbd::Frame frame;
+};
+
+VirtualSource::VirtualSource(ftl::config::json_t &cfg) : Source(cfg) {
+	auto params = params_;
+	impl_ = new VirtualImpl(this, params);
+}
+
+VirtualSource::~VirtualSource() {
+
+}
+
+void VirtualSource::onRender(const std::function<void(ftl::rgbd::Frame &)> &f) {
+	dynamic_cast<VirtualImpl*>(impl_)->callback = f;
+}
+
+/*
+void Source::writeFrames(int64_t ts, const cv::Mat &rgb, const cv::Mat &depth) {
+	if (!impl_) {
+		UNIQUE_LOCK(mutex_,lk);
+		rgb.copyTo(rgb_);
+		depth.copyTo(depth_);
+		timestamp_ = ts;
+	}
+}
+
+void Source::writeFrames(int64_t ts, const ftl::cuda::TextureObject<uchar4> &rgb, const ftl::cuda::TextureObject<uint> &depth, cudaStream_t stream) {
+	if (!impl_) {
+		UNIQUE_LOCK(mutex_,lk);
+		timestamp_ = ts;
+		rgb_.create(rgb.height(), rgb.width(), CV_8UC4);
+		cudaSafeCall(cudaMemcpy2DAsync(rgb_.data, rgb_.step, rgb.devicePtr(), rgb.pitch(), rgb_.cols * sizeof(uchar4), rgb_.rows, cudaMemcpyDeviceToHost, stream));
+		depth_.create(depth.height(), depth.width(), CV_32SC1);
+		cudaSafeCall(cudaMemcpy2DAsync(depth_.data, depth_.step, depth.devicePtr(), depth.pitch(), depth_.cols * sizeof(uint), depth_.rows, cudaMemcpyDeviceToHost, stream));
+		//cudaSafeCall(cudaStreamSynchronize(stream));  // TODO:(Nick) Don't wait here.
+		stream_ = stream;
+		//depth_.convertTo(depth_, CV_32F, 1.0f / 1000.0f);
+	} else {
+		LOG(ERROR) << "writeFrames cannot be done on this source: " << getURI();
+	}
+}
+
+void Source::writeFrames(int64_t ts, const ftl::cuda::TextureObject<uchar4> &rgb, const ftl::cuda::TextureObject<float> &depth, cudaStream_t stream) {
+	if (!impl_) {
+		UNIQUE_LOCK(mutex_,lk);
+		timestamp_ = ts;
+		rgb.download(rgb_, stream);
+		//rgb_.create(rgb.height(), rgb.width(), CV_8UC4);
+		//cudaSafeCall(cudaMemcpy2DAsync(rgb_.data, rgb_.step, rgb.devicePtr(), rgb.pitch(), rgb_.cols * sizeof(uchar4), rgb_.rows, cudaMemcpyDeviceToHost, stream));
+		depth.download(depth_, stream);
+		//depth_.create(depth.height(), depth.width(), CV_32FC1);
+		//cudaSafeCall(cudaMemcpy2DAsync(depth_.data, depth_.step, depth.devicePtr(), depth.pitch(), depth_.cols * sizeof(float), depth_.rows, cudaMemcpyDeviceToHost, stream));
+		
+		stream_ = stream;
+		cudaSafeCall(cudaStreamSynchronize(stream_));
+		cv::cvtColor(rgb_,rgb_, cv::COLOR_BGRA2BGR);
+		cv::cvtColor(rgb_,rgb_, cv::COLOR_Lab2BGR);
+
+		if (callback_) callback_(timestamp_, rgb_, depth_);
+	}
+}
+
+void Source::writeFrames(int64_t ts, const ftl::cuda::TextureObject<uchar4> &rgb, const ftl::cuda::TextureObject<uchar4> &rgb2, cudaStream_t stream) {
+	if (!impl_) {
+		UNIQUE_LOCK(mutex_,lk);
+		timestamp_ = ts;
+		rgb.download(rgb_, stream);
+		//rgb_.create(rgb.height(), rgb.width(), CV_8UC4);
+		//cudaSafeCall(cudaMemcpy2DAsync(rgb_.data, rgb_.step, rgb.devicePtr(), rgb.pitch(), rgb_.cols * sizeof(uchar4), rgb_.rows, cudaMemcpyDeviceToHost, stream));
+		rgb2.download(depth_, stream);
+		//depth_.create(depth.height(), depth.width(), CV_32FC1);
+		//cudaSafeCall(cudaMemcpy2DAsync(depth_.data, depth_.step, depth.devicePtr(), depth.pitch(), depth_.cols * sizeof(float), depth_.rows, cudaMemcpyDeviceToHost, stream));
+		
+		stream_ = stream;
+		cudaSafeCall(cudaStreamSynchronize(stream_));
+		cv::cvtColor(rgb_,rgb_, cv::COLOR_BGRA2BGR);
+		cv::cvtColor(rgb_,rgb_, cv::COLOR_Lab2BGR);
+		cv::cvtColor(depth_,depth_, cv::COLOR_BGRA2BGR);
+		cv::cvtColor(depth_,depth_, cv::COLOR_Lab2BGR);
+	}
+}
+*/
\ No newline at end of file
diff --git a/components/rgbd-sources/test/CMakeLists.txt b/components/rgbd-sources/test/CMakeLists.txt
index 96e1441c5da6134eb17070d77e5ce9dff3a355f1..78bb6cec7e8c411ffbfe982a1b65320f56439bd5 100644
--- a/components/rgbd-sources/test/CMakeLists.txt
+++ b/components/rgbd-sources/test/CMakeLists.txt
@@ -5,6 +5,29 @@ add_executable(source_unit
 )
 target_include_directories(source_unit PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../include")
 target_link_libraries(source_unit
-	ftlcommon Eigen3::Eigen ${CUDA_LIBRARIES})
+	ftlcommon ftlcodecs ftlnet Eigen3::Eigen ${CUDA_LIBRARIES})
 
 add_test(SourceUnitTest source_unit)
+
+### Channel Unit ###############################################################
+add_executable(channel_unit
+	./tests.cpp
+	./channel_unit.cpp
+)
+target_include_directories(channel_unit PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../include")
+target_link_libraries(channel_unit
+	ftlcommon)
+
+add_test(ChannelUnitTest channel_unit)
+
+### Frame Unit #################################################################
+add_executable(frame_unit
+	./tests.cpp
+	./frame_unit.cpp
+	../src/frame.cpp
+)
+target_include_directories(frame_unit PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../include")
+target_link_libraries(frame_unit
+	ftlcommon ftlcodecs)
+
+add_test(FrameUnitTest frame_unit)
diff --git a/components/rgbd-sources/test/channel_unit.cpp b/components/rgbd-sources/test/channel_unit.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..25171678540f1970088d84e1e842865a4249455e
--- /dev/null
+++ b/components/rgbd-sources/test/channel_unit.cpp
@@ -0,0 +1,88 @@
+#include "catch.hpp"
+#include <ftl/rgbd/channels.hpp>
+
+using ftl::rgbd::Channel;
+using ftl::rgbd::Channels;
+
+TEST_CASE("channel casting", "") {
+	SECTION("cast channel to channels") {
+		Channels cs(Channel::Depth);
+
+        REQUIRE( (unsigned int)cs > 0 );
+        REQUIRE( cs.count() == 1 );
+	}
+
+    SECTION("cast channels to channel") {
+		Channels cs(Channel::Depth);
+        Channel c = (Channel)cs;
+
+        REQUIRE( c == Channel::Depth );
+	}
+}
+
+TEST_CASE("Channel or-ing", "") {
+	SECTION("Add channel to channel mask") {
+		Channels cs(Channel::Depth);
+
+        cs |= Channel::Right;
+
+        REQUIRE( (cs.count() == 2) );
+        REQUIRE( cs.has(Channel::Right) );
+        REQUIRE( cs.has(Channel::Depth) );
+	}
+
+    SECTION("Combine multiple channels in assignment") {
+		Channels cs;
+
+        cs = Channel::Right | Channel::Flow | Channel::Left;
+
+        REQUIRE( (cs.count() == 3) );
+        REQUIRE( cs.has(Channel::Right) );
+        REQUIRE( cs.has(Channel::Flow) );
+        REQUIRE( cs.has(Channel::Left) );
+	}
+
+    SECTION("Combine multiple channels at init") {
+		Channels cs = Channel::Right | Channel::Flow | Channel::Left;
+
+        REQUIRE( (cs.count() == 3) );
+        REQUIRE( cs.has(Channel::Right) );
+        REQUIRE( cs.has(Channel::Flow) );
+        REQUIRE( cs.has(Channel::Left) );
+	}
+}
+
+TEST_CASE("Channel adding", "") {
+	SECTION("Add channel to channel mask") {
+		Channels cs(Channel::Depth);
+
+        cs += Channel::Right;
+
+        REQUIRE( (cs.count() == 2) );
+        REQUIRE( cs.has(Channel::Right) );
+        REQUIRE( cs.has(Channel::Depth) );
+	}
+
+    SECTION("Combine multiple channels in assignment") {
+		Channels cs;
+
+        cs = Channel::Right + Channel::Flow + Channel::Left;
+
+        REQUIRE( (cs.count() == 3) );
+        REQUIRE( cs.has(Channel::Right) );
+        REQUIRE( cs.has(Channel::Flow) );
+        REQUIRE( cs.has(Channel::Left) );
+	}
+}
+
+TEST_CASE("Channel subtracting", "") {
+	SECTION("Remove channel from channel mask") {
+		Channels cs = Channel::Right | Channel::Flow | Channel::Left;
+
+        cs -= Channel::Flow;
+
+        REQUIRE( (cs.count() == 2) );
+        REQUIRE( cs.has(Channel::Right) );
+        REQUIRE( cs.has(Channel::Left) );
+	}
+}
diff --git a/components/rgbd-sources/test/frame_unit.cpp b/components/rgbd-sources/test/frame_unit.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..6ad528a28fd677e207b05362c0021f7d302784dc
--- /dev/null
+++ b/components/rgbd-sources/test/frame_unit.cpp
@@ -0,0 +1,283 @@
+#include "catch.hpp"
+#include <ftl/rgbd/frame.hpp>
+
+using ftl::rgbd::Frame;
+using ftl::rgbd::Channel;
+using ftl::rgbd::Channels;
+using ftl::rgbd::Format;
+
+TEST_CASE("Frame::create() cpu mat", "") {
+	SECTION("in empty channel with format") {
+		Frame f;
+		auto &m = f.create<cv::Mat>(Channel::Colour, Format<float4>(200,200));
+
+		REQUIRE( m.type() == CV_32FC4 );
+		REQUIRE( m.cols == 200 );
+		REQUIRE( m.rows == 200 );
+	}
+
+	SECTION("in non-empty channel with format") {
+		Frame f;
+		f.create<cv::Mat>(Channel::Colour, Format<float>(200,100));
+		auto &m = f.create<cv::Mat>(Channel::Colour, Format<float4>(200,200));
+
+		REQUIRE( m.type() == CV_32FC4 );
+		REQUIRE( m.cols == 200 );
+		REQUIRE( m.rows == 200 );
+	}
+}
+
+TEST_CASE("Frame::get()", "") {
+	SECTION("get a non-existant host channel") {
+		Frame f;
+		bool hadexception = false;
+
+		try {
+			f.get<cv::Mat>(Channel::Colour);
+		} catch (ftl::exception &e) {
+			hadexception = true;
+		}
+
+		REQUIRE( hadexception );
+	}
+
+	SECTION("get a non-existant gpu channel") {
+		Frame f;
+		bool hadexception = false;
+
+		try {
+			f.get<cv::cuda::GpuMat>(Channel::Colour);
+		} catch (ftl::exception &e) {
+			hadexception = true;
+		}
+
+		REQUIRE( hadexception );
+	}
+
+	SECTION("get a valid host channel") {
+		Frame f;
+		bool hadexception = false;
+
+		try {
+			f.create<cv::Mat>(Channel::Colour, Format<uchar3>(1024,1024));
+			auto &m = f.get<cv::Mat>(Channel::Colour);
+
+			REQUIRE( m.type() == CV_8UC3 );
+			REQUIRE( m.cols == 1024 );
+			REQUIRE( m.rows == 1024 );
+		} catch (ftl::exception &e) {
+			hadexception = true;
+		}
+
+		REQUIRE( !hadexception );
+	}
+
+	SECTION("get a valid gpu channel") {
+		Frame f;
+		bool hadexception = false;
+
+		try {
+			f.create<cv::cuda::GpuMat>(Channel::Colour, Format<uchar3>(1024,1024));
+			auto &m = f.get<cv::cuda::GpuMat>(Channel::Colour);
+
+			REQUIRE( m.type() == CV_8UC3 );
+			REQUIRE( m.cols == 1024 );
+			REQUIRE( m.rows == 1024 );
+		} catch (ftl::exception &e) {
+			hadexception = true;
+		}
+
+		REQUIRE( !hadexception );
+	}
+
+	SECTION("get a cpu mat from gpu channel") {
+		Frame f;
+		bool hadexception = false;
+
+		try {
+			f.create<cv::cuda::GpuMat>(Channel::Colour, Format<uchar3>(1024,1024));
+			REQUIRE( f.isGPU(Channel::Colour) );
+
+			auto &m = f.get<cv::Mat>(Channel::Colour);
+
+			REQUIRE( f.isCPU(Channel::Colour) );
+			REQUIRE( m.type() == CV_8UC3 );
+			REQUIRE( m.cols == 1024 );
+			REQUIRE( m.rows == 1024 );
+		} catch (ftl::exception &e) {
+			hadexception = true;
+		}
+
+		REQUIRE( !hadexception );
+	}
+
+	SECTION("get a gpu mat from cpu channel") {
+		Frame f;
+		bool hadexception = false;
+
+		try {
+			f.create<cv::Mat>(Channel::Colour, Format<uchar3>(1024,1024));
+			REQUIRE( f.isCPU(Channel::Colour) );
+			
+			auto &m = f.get<cv::cuda::GpuMat>(Channel::Colour);
+
+			REQUIRE( f.isGPU(Channel::Colour) );
+			REQUIRE( m.type() == CV_8UC3 );
+			REQUIRE( m.cols == 1024 );
+			REQUIRE( m.rows == 1024 );
+		} catch (ftl::exception &e) {
+			hadexception = true;
+		}
+
+		REQUIRE( !hadexception );
+	}
+}
+
+TEST_CASE("Frame::createTexture()", "") {
+	SECTION("Missing format and no existing mat") {
+		Frame f;
+		bool hadexception = false;
+
+		try {
+			f.createTexture<float>(Channel::Depth);
+		} catch (ftl::exception &e) {
+			hadexception = true;
+		}
+
+		REQUIRE( hadexception );
+	}
+
+	SECTION("Missing format but with existing host mat") {
+		Frame f;
+		bool hadexception = false;
+
+		try {
+			f.create<cv::Mat>(Channel::Depth, Format<float>(100,100));
+			auto &t = f.createTexture<float>(Channel::Depth);
+
+			REQUIRE( t.width() == 100 );
+			REQUIRE( t.cvType() == CV_32FC1 );
+		} catch (ftl::exception &e) {
+			hadexception = true;
+		}
+
+		REQUIRE( !hadexception );
+	}
+
+	SECTION("Missing format but with incorrect existing host mat") {
+		Frame f;
+		bool hadexception = false;
+
+		try {
+			f.create<cv::Mat>(Channel::Depth, Format<uchar4>(100,100));
+			f.createTexture<float>(Channel::Depth);
+		} catch (ftl::exception &e) {
+			hadexception = true;
+		}
+
+		REQUIRE( hadexception );
+	}
+
+	SECTION("With format and no existing mat") {
+		Frame f;
+		bool hadexception = false;
+
+		try {
+			auto &t = f.createTexture<float>(Channel::Depth, Format<float>(1024,1024));
+			REQUIRE( t.cvType() == CV_32FC1 );
+			REQUIRE( t.cudaTexture() > 0 );
+			REQUIRE( t.devicePtr() != nullptr );
+
+			auto &m = f.get<cv::cuda::GpuMat>(Channel::Depth);
+			REQUIRE( m.data == reinterpret_cast<uchar*>(t.devicePtr()) );
+			REQUIRE( m.type() == CV_32FC1 );
+		} catch (ftl::exception &e) {
+			hadexception = true;
+		}
+
+		REQUIRE( !hadexception );
+	}
+
+	SECTION("Unchanged type is same texture object") {
+		Frame f;
+		bool hadexception = false;
+
+		try {
+			auto &t = f.createTexture<float>(Channel::Depth, Format<float>(1024,1024));
+			REQUIRE( t.cvType() == CV_32FC1 );
+			
+			auto tex = t.cudaTexture();
+			float *ptr = t.devicePtr();
+
+			REQUIRE( ptr != nullptr );
+
+			auto &t2 = f.createTexture<float>(Channel::Depth, Format<float>(1024,1024));
+
+			REQUIRE( tex == t2.cudaTexture() );
+			REQUIRE( ptr == t2.devicePtr() );
+		} catch (ftl::exception &e) {
+			hadexception = true;
+		}
+
+		REQUIRE( !hadexception );
+	}
+}
+
+TEST_CASE("Frame::getTexture()", "") {
+	SECTION("Missing texture") {
+		Frame f;
+		bool hadexception = false;
+
+		try {
+			f.getTexture<float>(Channel::Depth);
+		} catch (ftl::exception &e) {
+			hadexception = true;
+		}
+
+		REQUIRE( hadexception );
+	}
+
+	SECTION("Texture of incorrect type") {
+		Frame f;
+		bool hadexception = false;
+
+		try {
+			f.createTexture<uchar4>(Channel::Depth, Format<uchar4>(100,100));
+			f.getTexture<float>(Channel::Depth);
+		} catch (ftl::exception &e) {
+			hadexception = true;
+		}
+
+		REQUIRE( hadexception );
+	}
+
+	SECTION("Valid texture get") {
+		Frame f;
+		bool hadexception = false;
+
+		try {
+			f.createTexture<uchar4>(Channel::Colour, Format<uchar4>(100,100));
+			auto &t = f.getTexture<uchar4>(Channel::Colour);
+
+			REQUIRE( t.cvType() == CV_8UC4 );
+			REQUIRE( t.width() == 100 );
+		} catch (ftl::exception &e) {
+			hadexception = true;
+		}
+
+		REQUIRE( !hadexception );
+	}
+}
+
+TEST_CASE("Frame::swapTo()", "") {
+	SECTION("Single host channel to empty frame") {
+		Frame f1;
+		Frame f2;
+
+		f1.create<cv::Mat>(Channel::Colour, Format<uchar3>(100,100));
+		f1.swapTo(Channels::All(), f2);
+
+		REQUIRE( f2.hasChannel(Channel::Colour) );
+		REQUIRE( (f2.get<cv::Mat>(Channel::Colour).cols == 100) );
+	}
+}
diff --git a/components/rgbd-sources/test/source_unit.cpp b/components/rgbd-sources/test/source_unit.cpp
index b27b72e070f6e2116632b967699243a9e80926d8..dca38be2ffb6e06b8be416c8de71a7a799c77867 100644
--- a/components/rgbd-sources/test/source_unit.cpp
+++ b/components/rgbd-sources/test/source_unit.cpp
@@ -10,66 +10,79 @@ static std::string last_type = "";
 namespace ftl {
 namespace rgbd {
 
+class Snapshot {};
+
 class SnapshotReader {
 	public:
-	SnapshotReader(const std::string &) {}
+	explicit SnapshotReader(const std::string &) {}
+	Snapshot readArchive() { return Snapshot(); };
 };
 
 namespace detail {
 
 class ImageSource : public ftl::rgbd::detail::Source {
 	public:
-	ImageSource(ftl::rgbd::Source *host) : ftl::rgbd::detail::Source(host) {
+	explicit ImageSource(ftl::rgbd::Source *host) : ftl::rgbd::detail::Source(host) {
 		last_type = "image";
 	}
 	ImageSource(ftl::rgbd::Source *host, const std::string &f) : ftl::rgbd::detail::Source(host) {
 		last_type = "image";
 	}
 
-	bool grab(int n, int b) { return true; };
+	bool capture(int64_t ts) { return true; }
+	bool retrieve() { return true; }
+	bool compute(int n, int b) { return true; };
 	bool isReady() { return true; };
 };
 
 class StereoVideoSource : public ftl::rgbd::detail::Source {
 	public:
-	StereoVideoSource(ftl::rgbd::Source *host) : ftl::rgbd::detail::Source(host) {
+	explicit StereoVideoSource(ftl::rgbd::Source *host) : ftl::rgbd::detail::Source(host) {
 		last_type = "video";
 	}
 	StereoVideoSource(ftl::rgbd::Source *host, const std::string &f) : ftl::rgbd::detail::Source(host) {
 		last_type = "video";
 	}
 
-	bool grab(int n, int b) { return true; };
+	bool capture(int64_t ts) { return true; }
+	bool retrieve() { return true; }
+	bool compute(int n, int b) { return true; };
 	bool isReady() { return true; };
 };
 
 class NetSource : public ftl::rgbd::detail::Source {
 	public:
-	NetSource(ftl::rgbd::Source *host) : ftl::rgbd::detail::Source(host) {
+	explicit NetSource(ftl::rgbd::Source *host) : ftl::rgbd::detail::Source(host) {
 		last_type = "net";
 	}
 
-	bool grab(int n, int b) { return true; };
+	bool capture(int64_t ts) { return true; }
+	bool retrieve() { return true; }
+	bool compute(int n, int b) { return true; };
 	bool isReady() { return true; };
 };
 
 class SnapshotSource : public ftl::rgbd::detail::Source {
 	public:
-	SnapshotSource(ftl::rgbd::Source *host, ftl::rgbd::SnapshotReader &r, const std::string &) : ftl::rgbd::detail::Source(host) {
+	SnapshotSource(ftl::rgbd::Source *host, ftl::rgbd::Snapshot &r, const std::string &) : ftl::rgbd::detail::Source(host) {
 		last_type = "snapshot";
 	}
 
-	bool grab(int n, int b) { return true; };
+	bool capture(int64_t ts) { return true; }
+	bool retrieve() { return true; }
+	bool compute(int n, int b) { return true; };
 	bool isReady() { return true; };
 };
 
 class RealsenseSource : public ftl::rgbd::detail::Source {
 	public:
-	RealsenseSource(ftl::rgbd::Source *host) : ftl::rgbd::detail::Source(host) {
+	explicit RealsenseSource(ftl::rgbd::Source *host) : ftl::rgbd::detail::Source(host) {
 		last_type = "realsense";
 	}
 
-	bool grab(int n, int b) { return true; };
+	bool capture(int64_t ts) { return true; }
+	bool retrieve() { return true; }
+	bool compute(int n, int b) { return true; };
 	bool isReady() { return true; };
 };
 
@@ -79,7 +92,9 @@ class MiddleburySource : public ftl::rgbd::detail::Source {
 		last_type = "middlebury";
 	}
 
-	bool grab(int n, int b) { return true; };
+	bool capture(int64_t ts) { return true; }
+	bool retrieve() { return true; }
+	bool compute(int n, int b) { return true; };
 	bool isReady() { return true; };
 };
 
@@ -107,11 +122,11 @@ using ftl::rgbd::Source;
 using ftl::config::json_t;
 
 TEST_CASE("ftl::create<Source>(cfg)", "[rgbd]") {
-	json_t global = {{"$id","ftl://test"}};
+	json_t global = json_t{{"$id","ftl://test"}};
 	ftl::config::configure(global);
 
 	SECTION("with valid image file uri") {
-		json_t cfg = {
+		json_t cfg = json_t{
 			{"$id","ftl://test/1"},
 			{"uri","file://" FTL_SOURCE_DIRECTORY "/components/rgbd-sources/test/data/image.png"}
 		};
@@ -124,7 +139,7 @@ TEST_CASE("ftl::create<Source>(cfg)", "[rgbd]") {
 	}
 
 	SECTION("with valid video file uri") {
-		json_t cfg = {
+		json_t cfg = json_t{
 			{"$id","ftl://test/2"},
 			{"uri","file://" FTL_SOURCE_DIRECTORY "/components/rgbd-sources/test/data/video.mp4"}
 		};
@@ -137,7 +152,7 @@ TEST_CASE("ftl::create<Source>(cfg)", "[rgbd]") {
 	}
 
 	SECTION("with valid net uri") {
-		json_t cfg = {
+		json_t cfg = json_t{
 			{"$id","ftl://test/2"},
 			{"uri","ftl://utu.fi/dummy"}
 		};
@@ -150,7 +165,7 @@ TEST_CASE("ftl::create<Source>(cfg)", "[rgbd]") {
 	}
 
 	SECTION("with an invalid uri") {
-		json_t cfg = {
+		json_t cfg = json_t{
 			{"$id","ftl://test/2"},
 			{"uri","not a uri"}
 		};
@@ -162,7 +177,7 @@ TEST_CASE("ftl::create<Source>(cfg)", "[rgbd]") {
 	}
 
 	SECTION("with an invalid file uri") {
-		json_t cfg = {
+		json_t cfg = json_t{
 			{"$id","ftl://test/2"},
 			{"uri","file:///not/a/file"}
 		};
@@ -174,7 +189,7 @@ TEST_CASE("ftl::create<Source>(cfg)", "[rgbd]") {
 	}
 
 	SECTION("with a missing file") {
-		json_t cfg = {
+		json_t cfg = json_t{
 			{"$id","ftl://test/2"},
 			{"uri","file:///data/image2.png"}
 		};
@@ -187,11 +202,11 @@ TEST_CASE("ftl::create<Source>(cfg)", "[rgbd]") {
 }
 
 TEST_CASE("Source::set(uri)", "[rgbd]") {
-	json_t global = {{"$id","ftl://test"}};
+	json_t global = json_t{{"$id","ftl://test"}};
 	ftl::config::configure(global);
 
 	SECTION("change to different valid URI type") {
-		json_t cfg = {
+		json_t cfg = json_t{
 			{"$id","ftl://test/1"},
 			{"uri","file://" FTL_SOURCE_DIRECTORY "/components/rgbd-sources/test/data/image.png"}
 		};
diff --git a/components/scene-sources/include/ftl/scene/framescene.hpp b/components/scene-sources/include/ftl/scene/framescene.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..0235a60331899ca125514e0905c845bafd1e466c
--- /dev/null
+++ b/components/scene-sources/include/ftl/scene/framescene.hpp
@@ -0,0 +1,28 @@
+#ifndef _FTL_SCENE_FRAMESCENE_HPP_
+#define _FTL_SCENE_FRAMESCENE_HPP_
+
+#include <ftl/scene/scene.hpp>
+
+namespace ftl {
+namespace scene {
+
+/**
+ * A scene represented internally as a set of image frames that together
+ * define a point cloud.
+ */
+class FrameScene : public ftl::scene::Scene {
+	public:
+	FrameScene();
+	~FrameScene();
+
+	bool update(ftl::rgbd::FrameSet &);
+
+	bool render(ftl::rgbd::Source *, ftl::rgbd::Frame &);
+	bool encode(std::vector<uint8_t> &);
+	bool decode(const std::vector<uint8_t> &);
+};
+
+}
+}
+
+#endif  // _FTL_SCENE_FRAMESCENE_HPP_
diff --git a/components/scene-sources/include/ftl/scene/scene.hpp b/components/scene-sources/include/ftl/scene/scene.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..856819cf403982d54ea3fd12a3cd195b9d437919
--- /dev/null
+++ b/components/scene-sources/include/ftl/scene/scene.hpp
@@ -0,0 +1,21 @@
+#ifndef _FTL_RECONSTRUCT_SCENE_HPP_
+#define _FTL_RECONSTRUCT_SCENE_HPP_
+
+namespace ftl {
+namespace scene {
+
+class Scene {
+    public:
+    Scene();
+    virtual ~Scene();
+
+    virtual bool render(ftl::rgbd::Source *, ftl::rgbd::Frame &)=0;
+
+	virtual bool encode(std::vector<uint8_t> &)=0;
+	virtual bool decode(const std::vector<uint8_t> &)=0;
+};
+
+}  // scene
+}  // ftl
+
+#endif  // _FTL_RECONSTRUCT_SCENE_HPP_
diff --git a/config/config.jsonc b/config/config.jsonc
index 080b0969a7367be4a2eb65bf9e7efaf2e7c20582..17055e8a32bb76bf8f189d38d2fb394fb0c30613 100644
--- a/config/config.jsonc
+++ b/config/config.jsonc
@@ -163,15 +163,7 @@
 		],
 		"display": { "$ref": "#displays/none" },
 		"virtual": { "$ref": "#virtual_cams/default" },
-		"voxelhash": { "$ref": "#hash_conf/default" },
-		"merge": {
-			"register": false,
-			"targetsource" : "ftl://utu.fi#vision_default/source",
-			"maxerror": 25,
-			"iterations" : 10,
-			"delay" : 500,
-			"patternsize" : [9, 6]
-		}
+		"voxelhash": { "$ref": "#hash_conf/default" }
 	},
 
 	"reconstruction_lab": {
@@ -185,15 +177,6 @@
 		"display": { "$ref": "#displays/none" },
 		"virtual": { "$ref": "#virtual_cams/default" },
 		"voxelhash": { "$ref": "#hash_conf/default" },
-		"merge": {
-			"targetsource" : "ftl://utu.fi/node4#vision_default/source",
-			"register": false,
-			"chain": false,
-			"maxerror": 25,
-			"iterations" : 10,
-			"delay" : 500,
-			"patternsize" : [9, 6]
-		},
 		"stream": {}
 	},
 
@@ -207,20 +190,9 @@
 		"display": { "$ref": "#displays/left" },
 		"virtual": { "$ref": "#virtual_cams/default" },
 		"voxelhash": { "$ref": "#hash_conf/default" },
-		"registration": {
-			"reference-source" : "ftl://utu.fi/node4#vision_default/source",
-			"calibration" : {
-				"max_error": 25,
-				"run": false,
-				"iterations" : 10,
-				"delay" : 500,
-				"patternsize" : [9, 6]
-				}
-		},
 		"stream": {}
 	},
 
-
 	"gui_node5": {
 		"net": {
 			"peers": ["tcp://ftl-node-5:9001"]
@@ -246,5 +218,34 @@
 		"net": {
 			"peers": ["ws://localhost:8080/"]
 		}
+	},
+	
+	"registration_default" : {
+		"n_views" : 500,
+		"load_input": false,
+		"save_input": true,
+		"save_extrinsic" : false,
+		"save_intrinsic" : false,
+		"optimize_intrinsic" : true,
+		"calibration_data_dir" : "",
+		"output_directory" : "/smb/ftl-master.local/Shared/Config/",
+		"net": {
+			"peers": [
+				"tcp://10.0.0.3:9001",
+				"tcp://10.0.0.4:9001",
+				"tcp://10.0.0.5:9001",
+				"tcp://10.0.0.6:9001",
+				"tcp://10.0.0.7:9001",
+				"tcp://10.0.0.8:9001"
+			]
+		},
+		"sources": [
+			{"type": "net", "uri":"ftl://utu.fi/node1#vision_default/source"},
+			{"type": "net", "uri":"ftl://utu.fi/node2#vision_default/source"},
+			{"type": "net", "uri":"ftl://utu.fi/node3#vision_default/source"},
+			{"type": "net", "uri":"ftl://utu.fi/node4#vision_default/source"},
+			{"type": "net", "uri":"ftl://utu.fi/node5#vision_default/source"},
+			{"type": "net", "uri":"ftl://utu.fi/node6#vision_default/source"}
+		]
 	}
 }
diff --git a/config/config_nick.jsonc b/config/config_nick.jsonc
index 5e3a29a40abd6da9c577f185584f937c94cf44d0..53673a170693bd7ffd6d4386977417897fa55b8a 100644
--- a/config/config_nick.jsonc
+++ b/config/config_nick.jsonc
@@ -128,7 +128,15 @@
 				"SDFUseGradients": false,
 				"showBlockBorders": false
 			},
-			"baseline": 0.5,
+			"baseline": 0.05,
+			"focal": 700,
+			"width": 1280,
+			"height": 720,
+			"maxDepth": 15.0,
+			"minDepth": 0.05,
+			"splatting": true,
+			"texturing": true,
+			"upsampling": false,
 			"uri": "device:virtual"
 		}
 	},
@@ -151,12 +159,12 @@
 			"mls": true,
 			"voxels": false,
 			"clipping": false,
-			"bbox_x_max": 1.5,
-			"bbox_x_min": -1.5,
+			"bbox_x_max": 0.6,
+			"bbox_x_min": -0.6,
 			"bbox_y_max": 3.0,
 			"bbox_y_min": -3.0,
-			"bbox_z_max": 2.5,
-			"bbox_z_min": 0.0,
+			"bbox_z_max": 3.5,
+			"bbox_z_min": 2.0,
 			"cudaDevice": 1
 		},
 		"rs": {
@@ -507,6 +515,34 @@
 		"stream": {}
 	},
 
+	"reconstruction_snap10": {
+		"net": {
+			"peers": [],
+			"listen": "tcp://*:9002"
+		},
+		"sources": [
+			{"uri":"file:///home/nick/Pictures/FTL/snaptest10.tar.gz#0", "index": "camera0"},
+			{"uri":"file:///home/nick/Pictures/FTL/snaptest10.tar.gz#1", "index": "camera1"},
+			{"uri":"file:///home/nick/Pictures/FTL/snaptest10.tar.gz#2", "index": "camera2"},
+			{"uri":"file:///home/nick/Pictures/FTL/snaptest10.tar.gz#3", "index": "camera3"},
+			{"uri":"file:///home/nick/Pictures/FTL/snaptest10.tar.gz#4", "index": "camera4"}
+		],
+		"display": { "$ref": "#displays/left" },
+		"virtual": { "$ref": "#virtual_cams/default" },
+		"voxelhash": { "$ref": "#hash_conf/default" },
+		"merge": {
+			"$id": "ftl://blah/blah",
+			"targetsource" : "ftl://utu.fi/node3#vision_default/source",
+			"register": false,
+			"chain": false,
+			"maxerror": 100,
+			"iterations" : 10,
+			"delay" : 500,
+			"patternsize" : [9, 6]
+		},
+		"stream": {}
+	},
+
 	"reconstruction_lab": {
 		"net": {
 			"peers": ["tcp://ftl-node-4:9001",
diff --git a/config/config_vision.jsonc b/config/config_vision.jsonc
new file mode 100644
index 0000000000000000000000000000000000000000..b73446cefe06aaaf3663b8fe2823822878b5a1a8
--- /dev/null
+++ b/config/config_vision.jsonc
@@ -0,0 +1,129 @@
+{
+	//"$id": "ftl://utu.fi",
+	"$schema": "",
+	"calibrations": {
+		"default": {
+			"use_intrinsics": true,
+			"use_extrinsics": true,
+			"alpha": 0.0
+		}
+	},
+	
+	"disparity": {
+		"libsgm": {
+			"algorithm": "libsgm",
+			"width": 1280,
+			"height": 720,
+			"use_cuda": true,
+			"minimum": 0,
+			"maximum": 256,
+			"tau": 0.0,
+			"gamma": 0.0,
+			"window_size": 5,
+			"sigma": 1.5,
+			"lambda": 8000.0,
+			"uniqueness":  0.65,
+			"use_filter": true,
+			"P1": 8,
+			"P2": 130,
+			"filter_radius": 11,
+			"filter_iter": 2,
+			"use_off": true,
+			"off_size": 24,
+			"off_threshold": 0.75,
+			"T": 60,
+			"T_add": 0,
+			"T_del": 25,
+			"T_x" : 3.0,
+			"alpha" : 0.6,
+			"beta" : 1.7,
+			"epsilon" : 15.0
+		},
+		
+		"rtcensus": {
+			"algorithm": "rtcensus",
+			"use_cuda": true,
+			"minimum": 0,
+			"maximum": 256,
+			"tau": 0.0,
+			"gamma": 0.0,
+			"window_size": 5,
+			"sigma": 1.5,
+			"lambda": 8000.0,
+			"use_filter": true,
+			"filter_radius": 3,
+			"filter_iter": 4	
+		}
+	},
+	
+	"sources": {
+		"stereocam": {
+			"uri": "device:video",
+			"feed": {
+				"flip": false,
+				"nostereo": false,
+				"scale": 1.0,
+				"flip_vert": false,
+				"max_fps": 500,
+				"width": 1280,
+				"height": 720,
+				"crosshair": false
+			},
+			"use_optflow" : true,
+			"calibration": { "$ref": "#calibrations/default" },
+			"disparity": { "$ref": "#disparity/libsgm" }
+		},
+		"stereovid": {},
+		"localhost": {}
+		
+	},
+	
+	"vision_default": {
+		"fps": 20,
+		"source": { "$ref": "#sources/stereocam" },
+		"middlebury": { "$ref": "#middlebury/none" },
+		"display": { "$ref": "#displays/none" },
+		"net": { "$ref": "#net/default_vision" },
+		"stream": { }
+	},
+	
+	// Listen to localhost
+	"net": {
+		"default_vision": {
+			"listen": "tcp://*:9001",
+			"peers": [],
+			"tcp_send_buffer": 100000 //204800
+		},
+		"default_reconstruct": {
+			"listen": "tcp://*:9002",
+			"peers": []
+		}
+	},
+	
+	"displays": {
+		"none": {
+			"flip_vert": false,
+			"disparity": false,
+			"points": false,
+			"depth": false,
+			"left": false,
+			"right": false
+		},
+		"left": {
+			"flip_vert": false,
+			"disparity": false,
+			"points": false,
+			"depth": false,
+			"left": true,
+			"right": false
+		}
+	},
+	
+	"middlebury": {
+		"none": {
+			"dataset": "",
+			"threshold": 10.0,
+			"scale": 0.25
+		}
+	}
+}
diff --git a/web-service/src/index.js b/web-service/src/index.js
index f4096ecdcb17b32510a3baa6e74245771a49e02f..c6b37247ac0c0e63ad0eefbd8b81ad5594d6b432 100644
--- a/web-service/src/index.js
+++ b/web-service/src/index.js
@@ -11,32 +11,56 @@ let peer_uris = {};
 
 let uri_data = {};
 
+/**
+ * A client stream request object. Each source maintains a list of clients who
+ * are wanting frames from that source. Clients can only request N frames at a
+ * time, after that if no new request is received then the client is removed.
+ * 
+ * @param {Peer} peer Peer websocket wrapper
+ * @param {number} N Number of frames requested
+ * @param {number} rate Bitrate index requested
+ * @param {string} dest URI destination
+ */
 function RGBDClient(peer, N, rate, dest) {
 	this.peer = peer;
-	this.txmax = N;
+	this.txmax = N*16;  // 16 is for 16 blocks per frame... this will change in the near future
 	this.rate = rate;
 	this.dest = dest;
 	this.txcount = 0;
 }
 
-RGBDClient.prototype.push = function(uri, rgb, depth) {
-	this.peer.send(uri, rgb, depth);
+/**
+ * Actually send a frame over network to the client.
+ */
+RGBDClient.prototype.push = function(uri, frame, ttime, chunk,  rgb, depth) {
+	this.peer.send(uri, frame, ttime, chunk, rgb, depth);
 	this.txcount++;
 }
 
+/**
+ * A video stream. Each peer provides a list of these streams. Each stream can
+ * receive frames from the source and forward those frames to any clients.
+ * Therefore each of these stream objects maintains a list of clients and
+ * loops over them whenever a new frame is received.
+ * 
+ * @param {string} uri Address of the stream
+ * @param {Peer} peer Origin of the stream
+ */
 function RGBDStream(uri, peer) {
 	this.uri = uri;
 	this.peer = peer;
 	this.title = "";
-	this.rgb = null;
-	this.depth = null;
+	this.rgb = null;		// TODO: No longer works as an image
+	this.depth = null;		// TODO: No longer works as an image
 	this.pose = null;
 	this.clients = [];
 	this.rxcount = 10;
 	this.rxmax = 10;
 
-	peer.bind(uri, (rgb, depth) => {
-		this.pushFrames(rgb, depth);
+	// Add RPC handler to receive frames from the source
+	peer.bind(uri, (frame, ttime, chunk, rgb, depth) => {
+		// Forward frames to all clients
+		this.pushFrames(frame, ttime, chunk, rgb, depth);
 		this.rxcount++;
 		if (this.rxcount >= this.rxmax && this.clients.length > 0) {
 			this.subscribe();
@@ -60,16 +84,16 @@ RGBDStream.prototype.addClient = function(peer, N, rate, dest) {
 RGBDStream.prototype.subscribe = function() {
 	this.rxcount = 0;
 	this.rxmax = 10;
-	console.log("Subscribe to ", this.uri);
+	//console.log("Subscribe to ", this.uri);
 	this.peer.send("get_stream", this.uri, 10, 0, [Peer.uuid], this.uri);
 }
 
-RGBDStream.prototype.pushFrames = function(rgb, depth) {
+RGBDStream.prototype.pushFrames = function(frame, ttime, chunk, rgb, depth) {
 	this.rgb = rgb;
 	this.depth = depth;
 
 	for (let i=0; i < this.clients.length; i++) {
-		this.clients[i].push(this.uri, rgb, depth);
+		this.clients[i].push(this.uri, frame, ttime, chunk, rgb, depth);
 	}
 
 	let i=0;
@@ -139,7 +163,7 @@ app.ws('/', (ws, req) => {
 	let p = new Peer(ws);
 
 	p.on("connect", (peer) => {
-		console.log("Node connected...");
+		console.log("Node connected...", peer.string_id);
 		peer_uris[peer.string_id] = [];
 		peer_by_id[peer.string_id] = peer;
 
@@ -150,6 +174,7 @@ app.ws('/', (ws, req) => {
 			peer.name = obj.title;
 			peer.master = (obj.kind == "master");
 			console.log("Peer name = ", peer.name);
+			console.log("Details: ", details);
 
 			checkStreams(peer);
 		});
@@ -175,6 +200,15 @@ app.ws('/', (ws, req) => {
 		checkStreams(p);
 	});
 
+	// Used to sync clocks
+	p.bind("__ping__", () => {
+		return Date.now();
+	});
+
+	p.bind("node_details", () => {
+		return ['{"title": "FTL Web-Service", "id": "0", "kind": "master"}'];
+	});
+
 	p.bind("list_streams", () => {
 		return Object.keys(uri_data);
 	});
@@ -189,15 +223,25 @@ app.ws('/', (ws, req) => {
 		}
 	});
 
-	p.proxy("source_calibration", (cb, uri) => {
+	// Requests camera calibration information
+	p.proxy("source_details", (cb, uri, chan) => {
 		let peer = uri_data[uri].peer;
 		if (peer) {
-			peer.rpc("source_calibration", cb, uri);
+			peer.rpc("source_details", cb, uri, chan);
 		}
 	});
 
-	p.bind("set_pose", (uri, vec) => {
+	// Get the current position of a camera
+	p.proxy("get_pose", (cb, uri) => {
 		//console.log("SET POSE");
+		let peer = uri_data[uri].peer;
+		if (peer) {
+			peer.rpc("get_pose", cb, uri);
+		}
+	});
+
+	// Change the position of a camera
+	p.bind("set_pose", (uri, vec) => {
 		let peer = uri_data[uri].peer;
 		if (peer) {
 			uri_data[uri].pose = vec;
@@ -205,6 +249,7 @@ app.ws('/', (ws, req) => {
 		}
 	});
 
+	// Request from frames from a source
 	p.bind("get_stream", (uri, N, rate, pid, dest) => {
 		let peer = uri_data[uri].peer;
 		if (peer) {
@@ -213,6 +258,7 @@ app.ws('/', (ws, req) => {
 		}
 	});
 
+	// Register a new stream
 	p.bind("add_stream", (uri) => {
 		console.log("Adding stream: ", uri);
 		//uri_to_peer[streams[i]] = peer;
diff --git a/web-service/src/peer.js b/web-service/src/peer.js
index b1fc40dd8e6f7198a5857fcaec92d08bc9628307..51cd78e6a9ad07ec17f478646b3b60171e6280bc 100644
--- a/web-service/src/peer.js
+++ b/web-service/src/peer.js
@@ -6,6 +6,7 @@ const kConnecting = 1;
 const kConnected = 2;
 const kDisconnected = 3;
 
+// Generate a unique id for this webservice
 let my_uuid = new Uint8Array(16);
 my_uuid[0] = 44;
 my_uuid = Buffer.from(my_uuid);
@@ -13,6 +14,10 @@ my_uuid = Buffer.from(my_uuid);
 const kMagic = 0x0009340053640912;
 const kVersion = 0;
 
+/**
+ * Wrap a web socket with a MsgPack RCP protocol that works with our C++ version.
+ * @param {websocket} ws Websocket object
+ */
 function Peer(ws) {
 	this.sock = ws;
 	this.status = kConnecting;
@@ -79,26 +84,34 @@ function Peer(ws) {
 
 Peer.uuid = my_uuid;
 
+/**
+ * @private
+ */
 Peer.prototype._dispatchNotification = function(name, args) {
 	if (this.bindings.hasOwnProperty(name)) {
+		//console.log("Notification for: ", name);
 		this.bindings[name].apply(this, args);
 	} else {
 		console.log("Missing handler for: ", name);
 	}
 }
 
+/**
+ * @private
+ */
 Peer.prototype._dispatchCall = function(name, id, args) {
 	if (this.bindings.hasOwnProperty(name)) {
-		console.log("Call for:", name, id);
-		let res = this.bindings[name].apply(this, args);
+		//console.log("Call for:", name, id);
 
 		try {
+			let res = this.bindings[name].apply(this, args);
 			this.sock.send(encode([1,id,name,res]));
 		} catch(e) {
+			console.error("Could to dispatch or return call");
 			this.close();
 		}
 	} else if (this.proxies.hasOwnProperty(name)) {
-		console.log("Proxy for:", name, id);
+		//console.log("Proxy for:", name, id);
 		args.unshift((res) => {
 			try {
 				this.sock.send(encode([1,id,name,res]));
@@ -112,6 +125,9 @@ Peer.prototype._dispatchCall = function(name, id, args) {
 	}
 }
 
+/**
+ * @private
+ */
 Peer.prototype._dispatchResponse = function(id, res) {
 	if (this.callbacks.hasOwnProperty(id)) {
 		this.callbacks[id].call(this, res);
@@ -121,6 +137,14 @@ Peer.prototype._dispatchResponse = function(id, res) {
 	}
 }
 
+/**
+ * Register an RPC handler that will be called from a remote machine. Remotely
+ * passed arguments are provided to the given function as normal arguments, and
+ * if the function returns a value, it will be returned over the network also.
+ * 
+ * @param {string} name The name of the function
+ * @param {function} f A function or lambda to be callable remotely
+ */
 Peer.prototype.bind = function(name, f) {
 	if (this.bindings.hasOwnProperty(name)) {
 		//console.error("Duplicate bind to same procedure");
@@ -130,6 +154,10 @@ Peer.prototype.bind = function(name, f) {
 	}
 }
 
+/**
+ * Allow an RPC call to pass through to another machine with minimal local
+ * processing.
+ */
 Peer.prototype.proxy = function(name, f) {
 	if (this.proxies.hasOwnProperty(name)) {
 		//console.error("Duplicate proxy to same procedure");
@@ -139,6 +167,13 @@ Peer.prototype.proxy = function(name, f) {
 	}
 }
 
+/**
+ * Call a procedure on a remote machine.
+ * 
+ * @param {string} name Name of the procedure
+ * @param {function} cb Callback to receive return value as argument
+ * @param {...} args Any number of arguments to also pass to remote procedure
+ */
 Peer.prototype.rpc = function(name, cb, ...args) {
 	let id = this.cbid++;
 	this.callbacks[id] = cb;
@@ -158,6 +193,12 @@ Peer.prototype.sendB = function(name, args) {
 	}
 }
 
+/**
+ * Call a remote procedure but with no return value expected.
+ * 
+ * @param {string} name Name of the procedure
+ * @param {...} args Any number of arguments to also pass to remote procedure
+ */
 Peer.prototype.send = function(name, ...args) {
 	try {
 		this.sock.send(encode([0, name, args]));
@@ -171,6 +212,9 @@ Peer.prototype.close = function() {
 	this.status = kDisconnected;
 }
 
+/**
+ * @private
+ */
 Peer.prototype._notify = function(evt, ...args) {
 	if (this.events.hasOwnProperty(evt)) {
 		for (let i=0; i<this.events[evt].length; i++) {
@@ -180,6 +224,13 @@ Peer.prototype._notify = function(evt, ...args) {
 	}
 }
 
+/**
+ * Register a callback for socket events. Events include: 'connect',
+ * 'disconnect' and 'error'.
+ * 
+ * @param {string} evt Event name
+ * @param {function} f Callback on event
+ */
 Peer.prototype.on = function(evt, f) {
 	if (!this.events.hasOwnProperty(evt)) {
 		this.events[evt] = [];