From 8bca96759cff41ab3d54f7737850bdca9a584330 Mon Sep 17 00:00:00 2001
From: Nicolas Pope <nicolas.pope@utu.fi>
Date: Fri, 28 Jun 2019 12:59:29 +0300
Subject: [PATCH] Refactor GUI components

---
 applications/gui/CMakeLists.txt               |   4 +
 applications/gui/src/camera.cpp               | 295 +++++++++
 applications/gui/src/camera.hpp               |  71 +++
 applications/gui/src/gltexture.cpp            |  33 +
 applications/gui/src/gltexture.hpp            |  24 +
 applications/gui/src/main.cpp                 | 590 +-----------------
 applications/gui/src/media_panel.cpp          | 139 +++++
 applications/gui/src/media_panel.hpp          |  24 +
 applications/gui/src/pose_window.cpp          | 114 +++-
 applications/gui/src/pose_window.hpp          |   7 +-
 applications/gui/src/screen.cpp               | 308 +++++++++
 applications/gui/src/screen.hpp               |  74 +++
 applications/gui/src/src_window.cpp           | 116 ++--
 applications/gui/src/src_window.hpp           |  22 +-
 applications/reconstruct/src/registration.cpp |   2 +-
 .../reconstruct/src/scene_rep_hash_sdf.cu     |   2 +-
 .../common/cpp/include/ftl/configurable.hpp   |  19 +-
 .../common/cpp/include/ftl/configuration.hpp  |  11 +-
 .../common/cpp/include/nlohmann/json.hpp      |   9 +-
 components/common/cpp/src/configurable.cpp    |   9 +-
 .../net/cpp/include/ftl/net/universe.hpp      |   2 +-
 components/net/cpp/src/universe.cpp           |   4 +-
 .../rgbd-sources/include/ftl/rgbd/source.hpp  |  20 +
 components/rgbd-sources/src/net.cpp           |  28 -
 24 files changed, 1170 insertions(+), 757 deletions(-)
 create mode 100644 applications/gui/src/camera.cpp
 create mode 100644 applications/gui/src/camera.hpp
 create mode 100644 applications/gui/src/gltexture.cpp
 create mode 100644 applications/gui/src/gltexture.hpp
 create mode 100644 applications/gui/src/media_panel.cpp
 create mode 100644 applications/gui/src/media_panel.hpp
 create mode 100644 applications/gui/src/screen.cpp
 create mode 100644 applications/gui/src/screen.hpp

diff --git a/applications/gui/CMakeLists.txt b/applications/gui/CMakeLists.txt
index 6b0d3944c..cda2b89d2 100644
--- a/applications/gui/CMakeLists.txt
+++ b/applications/gui/CMakeLists.txt
@@ -8,6 +8,10 @@ set(GUISRC
 	src/src_window.cpp
 	src/config_window.cpp
 	src/pose_window.cpp
+	src/screen.cpp
+	src/gltexture.cpp
+	src/camera.cpp
+	src/media_panel.cpp
 )
 
 add_executable(ftl-gui ${GUISRC})
diff --git a/applications/gui/src/camera.cpp b/applications/gui/src/camera.cpp
new file mode 100644
index 000000000..bb715383b
--- /dev/null
+++ b/applications/gui/src/camera.cpp
@@ -0,0 +1,295 @@
+#include "camera.hpp"
+#include "pose_window.hpp"
+#include "screen.hpp"
+
+#include <nanogui/glutil.h>
+
+using ftl::rgbd::isValidDepth;
+using ftl::gui::GLTexture;
+using ftl::gui::PoseWindow;
+
+// TODO(Nick) MOVE
+class StatisticsImage {
+private:
+	std::vector<float> data_;
+	cv::Size size_;
+	int n_;
+
+public:
+	StatisticsImage(cv::Size size) {
+		size_ = size;
+		data_ = std::vector<float>(size.width * size.height * 2, 0.0f);
+		n_ = 0;
+	}
+
+	void update(const cv::Mat &in);
+	void getStdDev(cv::Mat &out);
+	void getMean(cv::Mat &out);
+};
+
+void StatisticsImage::update(const cv::Mat &in) {
+	DCHECK(in.type() == CV_32F);
+	DCHECK(in.size() == size_);
+	// Welford's Method
+
+	n_++;
+	for (int i = 0; i < size_.width * size_.height; i++) {
+		float x = ((float*) in.data)[i];
+		if (!isValidDepth(x)) { continue; } // invalid value
+		float &m = data_[2*i];
+		float &S = data_[2*i+1];
+		float m_prev = m;
+		m = m + (x - m) / n_;
+		S = S + (x - m) * (x - m_prev);
+	}
+}
+
+void StatisticsImage::getStdDev(cv::Mat &in) {
+	in = cv::Mat(size_, CV_32F, 0.0f);
+
+	for (int i = 0; i < size_.width * size_.height; i++) {
+		float &m = data_[2*i];
+		float &S = data_[2*i+1];
+		((float*) in.data)[i] = sqrt(S / n_);
+	}
+}
+
+void StatisticsImage::getMean(cv::Mat &in) {
+	in = cv::Mat(size_, CV_32F, 0.0f);
+
+	for (int i = 0; i < size_.width * size_.height; i++) {
+		((float*) in.data)[i] = data_[2*i];
+	}
+}
+
+class StatisticsImageNSamples {
+private:
+	std::vector<cv::Mat> samples_;
+	cv::Size size_;
+	int i_;
+	int n_;
+
+public:
+	StatisticsImageNSamples(cv::Size size, int n) {
+		size_ = size;
+		samples_ = std::vector<cv::Mat>(n);
+		i_ = 0;
+		n_ = n;
+	}
+
+	void update(const cv::Mat &in);
+	void getStdDev(cv::Mat &out);
+	void getVariance(cv::Mat &out);
+	void getMean(cv::Mat &out);
+};
+
+void StatisticsImageNSamples::update(const cv::Mat &in) {
+	DCHECK(in.type() == CV_32F);
+	DCHECK(in.size() == size_);
+
+	i_ = (i_ + 1) % n_;
+	in.copyTo(samples_[i_]);
+}
+
+void StatisticsImageNSamples::getStdDev(cv::Mat &out) {
+	cv::Mat var;
+	getVariance(var);
+	cv::sqrt(var, out);
+}
+
+void StatisticsImageNSamples::getVariance(cv::Mat &out) {
+	// Welford's Method
+	cv::Mat mat_m(size_, CV_32F, 0.0f);
+	cv::Mat mat_S(size_, CV_32F, 0.0f);
+
+	float n = 0.0f;
+	for (int i_sample = (i_ + 1) % n_; i_sample != i_; i_sample = (i_sample + 1) % n_) {
+		if (samples_[i_sample].size() != size_) continue;
+		n += 1.0f;
+		for (int i = 0; i < size_.width * size_.height; i++) {
+			float &x = ((float*) samples_[i_sample].data)[i];
+			float &m = ((float*) mat_m.data)[i];
+			float &S = ((float*) mat_S.data)[i];
+			float m_prev = m;
+
+			if (!isValidDepth(x)) continue;
+
+			m = m + (x - m) / n;
+			S = S + (x - m) * (x - m_prev);
+		}
+	}
+
+	mat_S.copyTo(out);
+}
+
+void StatisticsImageNSamples::getMean(cv::Mat &in) {}
+
+static Eigen::Affine3d create_rotation_matrix(float ax, float ay, float az) {
+  Eigen::Affine3d rx =
+      Eigen::Affine3d(Eigen::AngleAxisd(ax, Eigen::Vector3d(1, 0, 0)));
+  Eigen::Affine3d ry =
+      Eigen::Affine3d(Eigen::AngleAxisd(ay, Eigen::Vector3d(0, 1, 0)));
+  Eigen::Affine3d rz =
+      Eigen::Affine3d(Eigen::AngleAxisd(az, Eigen::Vector3d(0, 0, 1)));
+  return rz * rx * ry;
+}
+
+ftl::gui::Camera::Camera(ftl::gui::Screen *screen, ftl::rgbd::Source *src) : screen_(screen), src_(src) {
+	eye_ = Eigen::Vector3d(0.0f, 0.0f, 0.0f);
+	neye_ = Eigen::Vector4d(0.0f, 0.0f, 0.0f, 0.0f);
+	rotmat_.setIdentity();
+	//up_ = Eigen::Vector3f(0,1.0f,0);
+	lerpSpeed_ = 0.999f;
+	depth_ = false;
+	ftime_ = (float)glfwGetTime();
+	pause_ = false;
+
+	channel_ = ftl::rgbd::kChanLeft;
+
+	channels_.push_back(ftl::rgbd::kChanLeft);
+	channels_.push_back(ftl::rgbd::kChanDepth);
+
+	// Create pose window...
+	posewin_ = new PoseWindow(screen, src_->getURI());
+	posewin_->setTheme(screen->windowtheme);
+	posewin_->setVisible(false);
+}
+
+ftl::gui::Camera::~Camera() {
+
+}
+
+ftl::rgbd::Source *ftl::gui::Camera::source() {
+	return src_;
+}
+
+void ftl::gui::Camera::setPose(const Eigen::Matrix4d &p) {
+	eye_[0] = p(0,3);
+	eye_[1] = p(1,3);
+	eye_[2] = p(2,3);
+
+	double sx = Eigen::Vector3d(p(0,0), p(1,0), p(2,0)).norm();
+	double sy = Eigen::Vector3d(p(0,1), p(1,1), p(2,1)).norm();
+	double sz = Eigen::Vector3d(p(0,2), p(1,2), p(2,2)).norm();
+
+	Eigen::Matrix4d rot = p;
+	rot(0,3) = 0.0;
+	rot(1,3) = 0.0;
+	rot(2,3) = 0.0;
+	rot(0,0) = rot(0,0) / sx;
+	rot(1,0) = rot(1,0) / sx;
+	rot(2,0) = rot(2,0) / sx;
+	rot(0,1) = rot(0,1) / sy;
+	rot(1,1) = rot(1,1) / sy;
+	rot(2,1) = rot(2,1) / sy;
+	rot(0,2) = rot(0,2) / sz;
+	rot(1,2) = rot(1,2) / sz;
+	rot(2,2) = rot(2,2) / sz;
+	rotmat_ = rot;
+}
+
+void ftl::gui::Camera::mouseMovement(int rx, int ry, int button) {
+	if (button == 1) {
+		float rrx = ((float)ry * 0.2f * delta_);
+		//orientation_[2] += std::cos(orientation_[1])*((float)rel[1] * 0.2f * delta_);
+		float rry = (float)rx * 0.2f * delta_;
+		float rrz = 0.0;
+
+
+		Eigen::Affine3d r = create_rotation_matrix(rrx, -rry, rrz);
+		rotmat_ = rotmat_ * r.matrix();
+	}
+}
+
+void ftl::gui::Camera::keyMovement(int key, int modifiers) {
+	if (key == 263 || key == 262) {
+		float mag = (modifiers & 0x1) ? 0.01f : 0.1f;
+		float scalar = (key == 263) ? -mag : mag;
+		neye_ += rotmat_*Eigen::Vector4d(scalar,0.0,0.0,1.0);
+		return;
+	} else if (key == 264 || key == 265) {
+		float mag = (modifiers & 0x1) ? 0.01f : 0.1f;
+		float scalar = (key == 264) ? -mag : mag;
+		neye_ += rotmat_*Eigen::Vector4d(0.0,0.0,scalar,1.0);
+		return;
+	} else if (key == 266 || key == 267) {
+		float mag = (modifiers & 0x1) ? 0.01f : 0.1f;
+		float scalar = (key == 266) ? -mag : mag;
+		neye_ += rotmat_*Eigen::Vector4d(0.0,scalar,0.0,1.0);
+		return;
+	}
+}
+
+void ftl::gui::Camera::showPoseWindow() {
+	posewin_->setVisible(true);
+}
+
+void ftl::gui::Camera::showSettings() {
+
+}
+
+const GLTexture &ftl::gui::Camera::thumbnail() {
+
+}
+
+const GLTexture &ftl::gui::Camera::captureFrame() {
+	float now = (float)glfwGetTime();
+	delta_ = now - ftime_;
+	ftime_ = now;
+
+	if (src_ && src_->isReady()) {
+		cv::Mat rgb, depth;
+
+		// Lerp the Eye
+		eye_[0] += (neye_[0] - eye_[0]) * lerpSpeed_ * delta_;
+		eye_[1] += (neye_[1] - eye_[1]) * lerpSpeed_ * delta_;
+		eye_[2] += (neye_[2] - eye_[2]) * lerpSpeed_ * delta_;
+
+		Eigen::Translation3d trans(eye_);
+		Eigen::Affine3d t(trans);
+		Eigen::Matrix4d viewPose = t.matrix() * rotmat_;
+
+		src_->setPose(viewPose);
+		src_->grab();
+		src_->getFrames(rgb, depth);
+
+		if (!stats_ && depth.rows > 0) {
+			stats_ = new StatisticsImageNSamples(depth.size(), 25);
+		}
+		
+		if (stats_ && depth.rows > 0) { 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);
+				texture_.update(tmp);
+				break;
+			
+			case ftl::rgbd::kChanDeviation:
+				if (depth.rows == 0) { break; }
+				//imageSize = Vector2f(depth.cols, depth.rows);
+				stats_->getStdDev(tmp);
+				tmp.convertTo(tmp, CV_8U, 50.0);
+				applyColorMap(tmp, tmp, cv::COLORMAP_HOT);
+				texture_.update(tmp);
+				break;
+
+			default:
+				if (rgb.rows == 0) { break; }
+				//imageSize = Vector2f(rgb.cols,rgb.rows);
+				texture_.update(rgb);
+		}
+	}
+
+	return texture_;
+}
+
+nlohmann::json ftl::gui::Camera::getMetaData() {
+
+}
diff --git a/applications/gui/src/camera.hpp b/applications/gui/src/camera.hpp
new file mode 100644
index 000000000..590bdbcee
--- /dev/null
+++ b/applications/gui/src/camera.hpp
@@ -0,0 +1,71 @@
+#ifndef _FTL_GUI_CAMERA_HPP_
+#define _FTL_GUI_CAMERA_HPP_
+
+#include <ftl/rgbd/source.hpp>
+#include "gltexture.hpp"
+
+#include <string>
+
+class StatisticsImageNSamples;
+
+namespace ftl {
+namespace gui {
+
+class Screen;
+class PoseWindow;
+
+class Camera {
+	public:
+	Camera(ftl::gui::Screen *screen, ftl::rgbd::Source *src);
+	~Camera();
+
+	ftl::rgbd::Source *source();
+
+	int width() { return (src_) ? src_->parameters().width : 0; }
+	int height() { return (src_) ? src_->parameters().height : 0; }
+
+	void setPose(const Eigen::Matrix4d &p);
+
+	void mouseMovement(int rx, int ry, int button);
+	void keyMovement(int key, int modifiers);
+
+	void showPoseWindow();
+	void showSettings();
+
+	void setChannel(ftl::rgbd::channel_t c) { channel_ = c; };
+
+	void togglePause();
+	void isPaused();
+	const std::vector<ftl::rgbd::channel_t> &availableChannels();
+
+	const GLTexture &thumbnail();
+	const GLTexture &captureFrame();
+
+	nlohmann::json getMetaData();
+
+	StatisticsImageNSamples *stats_ = nullptr;
+
+	private:
+	Screen *screen_;
+	ftl::rgbd::Source *src_;
+	GLTexture thumb_;
+	GLTexture texture_;
+	ftl::gui::PoseWindow *posewin_;
+	nlohmann::json meta_;
+	Eigen::Vector4d neye_;
+	Eigen::Vector3d eye_;
+	//Eigen::Vector3f orientation_;
+	Eigen::Matrix4d rotmat_;
+	float ftime_;
+	float delta_;
+	float lerpSpeed_;
+	bool depth_;
+	bool pause_;
+	ftl::rgbd::channel_t channel_;
+	std::vector<ftl::rgbd::channel_t> channels_;
+};
+
+}
+}
+
+#endif  // _FTL_GUI_CAMERA_HPP_
diff --git a/applications/gui/src/gltexture.cpp b/applications/gui/src/gltexture.cpp
new file mode 100644
index 000000000..752894a3c
--- /dev/null
+++ b/applications/gui/src/gltexture.cpp
@@ -0,0 +1,33 @@
+#include "gltexture.hpp"
+
+#include <nanogui/opengl.h>
+#include <loguru.hpp>
+
+using ftl::gui::GLTexture;
+
+GLTexture::GLTexture() {
+	glid_ = std::numeric_limits<unsigned int>::max();
+}
+
+GLTexture::~GLTexture() {
+	//glDeleteTextures(1, &glid_);
+}
+
+void GLTexture::update(cv::Mat &m) {
+	if (glid_ == std::numeric_limits<unsigned int>::max()) {
+		glGenTextures(1, &glid_);
+		glBindTexture(GL_TEXTURE_2D, glid_);
+		cv::Mat m(cv::Size(100,100), CV_8UC3);
+		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, m.cols, m.rows, 0, GL_RGB, GL_UNSIGNED_BYTE, m.data);
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+	}
+	if (m.rows == 0) return;
+	glBindTexture(GL_TEXTURE_2D, glid_);
+	// TODO Allow for other formats
+	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, m.cols, m.rows, 0, GL_BGR, GL_UNSIGNED_BYTE, m.data);
+	auto err = glGetError();
+	if (err != 0) LOG(ERROR) << "OpenGL Texture error: " << err;
+}
diff --git a/applications/gui/src/gltexture.hpp b/applications/gui/src/gltexture.hpp
new file mode 100644
index 000000000..4fb0bdaef
--- /dev/null
+++ b/applications/gui/src/gltexture.hpp
@@ -0,0 +1,24 @@
+#ifndef _FTL_GUI_GLTEXTURE_HPP_
+#define _FTL_GUI_GLTEXTURE_HPP_
+
+#include <opencv2/opencv.hpp>
+
+namespace ftl {
+namespace gui {
+
+class GLTexture {
+	public:
+	GLTexture();
+	~GLTexture();
+
+	void update(cv::Mat &m);
+	unsigned int texture() const { return glid_; }
+
+	private:
+	unsigned int glid_;
+};
+
+}
+}
+
+#endif  // _FTL_GUI_GLTEXTURE_HPP_
diff --git a/applications/gui/src/main.cpp b/applications/gui/src/main.cpp
index 69512cc07..974afff14 100644
--- a/applications/gui/src/main.cpp
+++ b/applications/gui/src/main.cpp
@@ -5,594 +5,8 @@
 
 #include <loguru.hpp>
 
-#include <opencv2/opencv.hpp>
+#include "screen.hpp"
 
-#include <nanogui/opengl.h>
-#include <nanogui/glutil.h>
-#include <nanogui/screen.h>
-#include <nanogui/window.h>
-#include <nanogui/layout.h>
-#include <nanogui/imageview.h>
-#include <nanogui/combobox.h>
-#include <nanogui/label.h>
-#include <nanogui/toolbutton.h>
-
-#include "ctrl_window.hpp"
-#include "src_window.hpp"
-
-using std::string;
-using ftl::rgbd::Source;
-
-using ftl::rgbd::isValidDepth;
-
-/*struct SourceViews {
-	ftl::rgbd::RGBDSource *source;
-	GLTexture texture;
-	nanogui::ImageView *view;
-};*/
-
-class StatisticsImage {
-private:
-	std::vector<float> data_;
-	cv::Size size_;
-	int n_;
-
-public:
-	StatisticsImage(cv::Size size) {
-		size_ = size;
-		data_ = std::vector<float>(size.width * size.height * 2, 0.0f);
-		n_ = 0;
-	}
-
-	void update(const cv::Mat &in);
-	void getStdDev(cv::Mat &out);
-	void getMean(cv::Mat &out);
-};
-
-void StatisticsImage::update(const cv::Mat &in) {
-	DCHECK(in.type() == CV_32F);
-	DCHECK(in.size() == size_);
-	// Welford's Method
-
-	n_++;
-	for (int i = 0; i < size_.width * size_.height; i++) {
-		float x = ((float*) in.data)[i];
-		if (!isValidDepth(x)) { continue; } // invalid value
-		float &m = data_[2*i];
-		float &S = data_[2*i+1];
-		float m_prev = m;
-		m = m + (x - m) / n_;
-		S = S + (x - m) * (x - m_prev);
-	}
-}
-
-void StatisticsImage::getStdDev(cv::Mat &in) {
-	in = cv::Mat(size_, CV_32F, 0.0f);
-
-	for (int i = 0; i < size_.width * size_.height; i++) {
-		float &m = data_[2*i];
-		float &S = data_[2*i+1];
-		((float*) in.data)[i] = sqrt(S / n_);
-	}
-}
-
-void StatisticsImage::getMean(cv::Mat &in) {
-	in = cv::Mat(size_, CV_32F, 0.0f);
-
-	for (int i = 0; i < size_.width * size_.height; i++) {
-		((float*) in.data)[i] = data_[2*i];
-	}
-}
-
-class StatisticsImageNSamples {
-private:
-	std::vector<cv::Mat> samples_;
-	cv::Size size_;
-	int i_;
-	int n_;
-
-public:
-	StatisticsImageNSamples(cv::Size size, int n) {
-		size_ = size;
-		samples_ = std::vector<cv::Mat>(n);
-		i_ = 0;
-		n_ = n;
-	}
-
-	void update(const cv::Mat &in);
-	void getStdDev(cv::Mat &out);
-	void getVariance(cv::Mat &out);
-	void getMean(cv::Mat &out);
-};
-
-void StatisticsImageNSamples::update(const cv::Mat &in) {
-	DCHECK(in.type() == CV_32F);
-	DCHECK(in.size() == size_);
-
-	i_ = (i_ + 1) % n_;
-	in.copyTo(samples_[i_]);
-}
-
-void StatisticsImageNSamples::getStdDev(cv::Mat &out) {
-	cv::Mat var;
-	getVariance(var);
-	cv::sqrt(var, out);
-}
-
-void StatisticsImageNSamples::getVariance(cv::Mat &out) {
-	// Welford's Method
-	cv::Mat mat_m(size_, CV_32F, 0.0f);
-	cv::Mat mat_S(size_, CV_32F, 0.0f);
-
-	float n = 0.0f;
-	for (int i_sample = (i_ + 1) % n_; i_sample != i_; i_sample = (i_sample + 1) % n_) {
-		if (samples_[i_sample].size() != size_) continue;
-		n += 1.0f;
-		for (int i = 0; i < size_.width * size_.height; i++) {
-			float &x = ((float*) samples_[i_sample].data)[i];
-			float &m = ((float*) mat_m.data)[i];
-			float &S = ((float*) mat_S.data)[i];
-			float m_prev = m;
-
-			if (!isValidDepth(x)) continue;
-
-			m = m + (x - m) / n;
-			S = S + (x - m) * (x - m_prev);
-		}
-	}
-
-	mat_S.copyTo(out);
-}
-
-void StatisticsImageNSamples::getMean(cv::Mat &in) {}
-
-namespace {
-    constexpr char const *const defaultImageViewVertexShader =
-        R"(#version 330
-        uniform vec2 scaleFactor;
-        uniform vec2 position;
-        in vec2 vertex;
-        out vec2 uv;
-        void main() {
-            uv = vertex;
-            vec2 scaledVertex = (vertex * scaleFactor) + position;
-            gl_Position  = vec4(2.0*scaledVertex.x - 1.0,
-                                1.0 - 2.0*scaledVertex.y,
-                                0.0, 1.0);
-        })";
-
-    constexpr char const *const defaultImageViewFragmentShader =
-        R"(#version 330
-        uniform sampler2D image;
-        out vec4 color;
-        in vec2 uv;
-        void main() {
-            color = texture(image, uv);
-        })";
-
-
-	class GLTexture {
-		public:
-		GLTexture() {
-			glGenTextures(1, &glid_);
-			glBindTexture(GL_TEXTURE_2D, glid_);
-			cv::Mat m(cv::Size(100,100), CV_8UC3);
-			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, m.cols, m.rows, 0, GL_RGB, GL_UNSIGNED_BYTE, m.data);
-			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
-			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
-			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
-			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
-		}
-		~GLTexture() {
-			glDeleteTextures(1, &glid_);
-		}
-
-		void update(cv::Mat &m) {
-			if (m.rows == 0) return;
-			glBindTexture(GL_TEXTURE_2D, glid_);
-			// TODO Allow for other formats
-			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, m.cols, m.rows, 0, GL_BGR, GL_UNSIGNED_BYTE, m.data);
-			auto err = glGetError();
-			if (err != 0) LOG(ERROR) << "OpenGL Texture error: " << err;
-		}
-
-		unsigned int texture() const { return glid_; }
-
-		private:
-		unsigned int glid_;
-	};
-
-	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 Eigen::Affine3f create_rotation_matrix(float ax, float ay, float az) {
-  Eigen::Affine3f rx =
-      Eigen::Affine3f(Eigen::AngleAxisf(ax, Eigen::Vector3f(1, 0, 0)));
-  Eigen::Affine3f ry =
-      Eigen::Affine3f(Eigen::AngleAxisf(ay, Eigen::Vector3f(0, 1, 0)));
-  Eigen::Affine3f rz =
-      Eigen::Affine3f(Eigen::AngleAxisf(az, Eigen::Vector3f(0, 0, 1)));
-  return ry * rz * rx;
-}
-
-
-class FTLApplication : public nanogui::Screen {
-	public:
-	explicit FTLApplication(ftl::Configurable *root, ftl::net::Universe *net, ftl::ctrl::Master *controller) : nanogui::Screen(Eigen::Vector2i(1024, 768), "FT-Lab Remote Presence") {
-		using namespace nanogui;
-		net_ = net;
-		ctrl_ = controller;
-
-		status_ = "FT-Lab Remote Presence System";
-
-		setSize(Vector2i(1280,720));
-
-		Theme *toolbuttheme = new Theme(*theme());
-		toolbuttheme->mBorderDark = nanogui::Color(0,0);
-		toolbuttheme->mBorderLight = nanogui::Color(0,0);
-		toolbuttheme->mButtonGradientBotFocused = nanogui::Color(60,255);
-		toolbuttheme->mButtonGradientBotUnfocused = nanogui::Color(0,0);
-		toolbuttheme->mButtonGradientTopFocused = nanogui::Color(60,255);
-		toolbuttheme->mButtonGradientTopUnfocused = nanogui::Color(0,0);
-		toolbuttheme->mButtonGradientTopPushed = nanogui::Color(60,180);
-		toolbuttheme->mButtonGradientBotPushed = nanogui::Color(60,180);
-
-		Theme *windowtheme = new Theme(*theme());
-		windowtheme->mWindowFillFocused = nanogui::Color(220, 200);
-		windowtheme->mWindowFillUnfocused = nanogui::Color(220, 200);
-		windowtheme->mWindowHeaderGradientBot = nanogui::Color(60,230);
-		windowtheme->mWindowHeaderGradientTop = nanogui::Color(60,230);
-		windowtheme->mTextColor = nanogui::Color(20,255);
-		windowtheme->mWindowCornerRadius = 2;
-		windowtheme->mButtonGradientBotFocused = nanogui::Color(210,255);
-		windowtheme->mButtonGradientBotUnfocused = nanogui::Color(190,255);
-		windowtheme->mButtonGradientTopFocused = nanogui::Color(230,255);
-		windowtheme->mButtonGradientTopUnfocused = nanogui::Color(230,255);
-		windowtheme->mButtonGradientTopPushed = nanogui::Color(170,255);
-		windowtheme->mButtonGradientBotPushed = nanogui::Color(210,255);
-		windowtheme->mBorderDark = nanogui::Color(150,255);
-		windowtheme->mBorderMedium = nanogui::Color(165,255);
-		windowtheme->mBorderLight = nanogui::Color(230,255);
-		windowtheme->mButtonFontSize = 16;
-		windowtheme->mTextColorShadow = nanogui::Color(0,0);
-		windowtheme->mWindowTitleUnfocused = windowtheme->mWindowTitleFocused;
-		windowtheme->mWindowTitleFocused = nanogui::Color(240,255);
-		windowtheme->mIconScale = 0.85f;
-
-		auto toolbar = new Window(this, "");
-		toolbar->setPosition(Vector2i(0,0));
-		toolbar->setFixedWidth(50);
-		toolbar->setFixedHeight(height());
-		//toolbar->setLayout(new BoxLayout(Orientation::Vertical,
-        //                               Alignment::Middle, 0, 10));
-
-		setResizeCallback([this,toolbar](Vector2i s) {
-			toolbar->setFixedHeight(s[1]);
-		});
-
-		auto innertool = new Widget(toolbar);
-		innertool->setLayout(new BoxLayout(Orientation::Vertical,
-                                       Alignment::Middle, 0, 10));
-		innertool->setPosition(Vector2i(5,10));
-
-		// Padding widget
-		//auto w = new Widget(innertool);
-		//w->setHeight(10);
-
-		auto button = new ToolButton(innertool, ENTYPO_ICON_HOME);
-		button->setIconExtraScale(1.5f);
-		button->setTheme(toolbuttheme);
-		button->setTooltip("Home");
-		button->setFixedSize(Vector2i(40,40));
-		button->setCallback([this]() {
-			//swindow_->setVisible(true);
-		});
-
-		button = new ToolButton(innertool, ENTYPO_ICON_EDIT);
-		button->setIconExtraScale(1.5f);
-		button->setTheme(toolbuttheme);
-		button->setTooltip("Edit Scene");
-		button->setFixedSize(Vector2i(40,40));
-		button->setCallback([this]() {
-			//swindow_->setVisible(true);
-		});
-
-		button = new ToolButton(innertool, ENTYPO_ICON_CAMERA);
-		button->setIconExtraScale(1.5f);
-		button->setTheme(toolbuttheme);
-		button->setTooltip("Camera Sources");
-		button->setFixedSize(Vector2i(40,40));
-		button->setCallback([this]() {
-			swindow_->setVisible(true);
-		});
-
-		button = new ToolButton(innertool, ENTYPO_ICON_SIGNAL);
-		button->setIconExtraScale(1.5f);
-		button->setTheme(toolbuttheme);
-		button->setTooltip("Connections");
-		button->setFixedSize(Vector2i(40,40));
-		button->setCallback([this]() {
-			cwindow_->setVisible(true);
-		});
-
-		button = new ToolButton(toolbar, ENTYPO_ICON_COG);
-		button->setIconExtraScale(1.5f);
-		button->setTheme(toolbuttheme);
-		button->setTooltip("Settings");
-		button->setFixedSize(Vector2i(40,40));
-		button->setPosition(Vector2i(5,height()-50));
-
-		//configwindow_ = new ConfigWindow(parent, ctrl_);
-		cwindow_ = new ftl::gui::ControlWindow(this, controller);
-		swindow_ = new ftl::gui::SourceWindow(this, controller);
-
-		cwindow_->setPosition(Eigen::Vector2i(80, 20));
-		swindow_->setPosition(Eigen::Vector2i(80, 400));
-		cwindow_->setVisible(false);
-		swindow_->setVisible(false);
-		cwindow_->setTheme(windowtheme);
-		swindow_->setTheme(windowtheme);
-
-		//src_ = nullptr;
-		eye_ = Eigen::Vector3f(0.0f, 0.0f, 0.0f);
-		neye_ = Eigen::Vector4f(0.0f, 0.0f, 0.0f, 0.0f);
-		orientation_ = Eigen::Vector3f(0.0f, 0.0f, 0.0f);
-		up_ = Eigen::Vector3f(0,1.0f,0);
-		//lookPoint_ = Eigen::Vector3f(0.0f,0.0f,-4.0f);
-		lerpSpeed_ = 0.999f;
-		depth_ = false;
-		ftime_ = (float)glfwGetTime();
-
-		mShader.init("RGBDShader", defaultImageViewVertexShader,
-                 defaultImageViewFragmentShader);
-
-		MatrixXu indices(3, 2);
-		indices.col(0) << 0, 1, 2;
-		indices.col(1) << 2, 3, 1;
-
-		MatrixXf vertices(2, 4);
-		vertices.col(0) << 0, 0;
-		vertices.col(1) << 1, 0;
-		vertices.col(2) << 0, 1;
-		vertices.col(3) << 1, 1;
-
-		mShader.bind();
-		mShader.uploadIndices(indices);
-		mShader.uploadAttrib("vertex", vertices);
-
-		setVisible(true);
-		performLayout();
-	}
-
-	~FTLApplication() {
-		mShader.free();
-	}
-
-	bool mouseMotionEvent(const Eigen::Vector2i &p, const Eigen::Vector2i &rel, int button, int modifiers) {
-		if (Screen::mouseMotionEvent(p, rel, button, modifiers)) {
-			return true;
-		} else {
-			if (button == 1) {
-				orientation_[0] += ((float)rel[1] * 0.2f * delta_);
-				//orientation_[2] += std::cos(orientation_[1])*((float)rel[1] * 0.2f * delta_);
-				orientation_[1] -= (float)rel[0] * 0.2f * delta_;
-			}
-		}
-	}
-
-	bool mouseButtonEvent(const nanogui::Vector2i &p, int button, bool down, int modifiers) {
-		if (Screen::mouseButtonEvent(p, button, down, modifiers)) {
-			return true;
-		} else {
-			auto src_ = swindow_->getSource();
-			if (src_ && src_->isReady() && down) {
-				Eigen::Vector2f screenSize = size().cast<float>();
-				auto mScale = (screenSize.cwiseQuotient(imageSize).minCoeff());
-				Eigen::Vector2f scaleFactor = mScale * imageSize.cwiseQuotient(screenSize);
-				Eigen::Vector2f positionInScreen(0.0f, 0.0f);
-				auto mOffset = (screenSize - (screenSize.cwiseProduct(scaleFactor))) / 2;
-				Eigen::Vector2f positionAfterOffset = positionInScreen + mOffset;
-
-				float sx = ((float)p[0] - positionAfterOffset[0]) / mScale;
-				float sy = ((float)p[1] - positionAfterOffset[1]) / mScale;
-
-				Eigen::Vector4f camPos;
-
-				try {
-					camPos = src_->point(sx,sy).cast<float>();
-				} catch(...) {
-					return true;
-				}
-				
-				camPos *= -1.0f;
-				Eigen::Vector4f worldPos =  src_->getPose().cast<float>() * camPos;
-				//lookPoint_ = Eigen::Vector3f(worldPos[0],worldPos[1],worldPos[2]);
-				LOG(INFO) << "Depth at click = " << -camPos[2];
-				return true;
-			}
-		return false;
-		}
-	}
-
-	bool keyboardEvent(int key, int scancode, int action, int modifiers) {
-		using namespace Eigen;
-		if (Screen::keyboardEvent(key, scancode, action, modifiers)) {
-			return true;
-		} else {
-			LOG(INFO) << "Key press " << key << " - " << action << " - " << modifiers;
-			if (key == 263 || key == 262) {
-				float mag = (modifiers & 0x1) ? 0.01f : 0.1f;
-				float scalar = (key == 263) ? -mag : mag;
-				Eigen::Affine3f r = create_rotation_matrix(orientation_[0], orientation_[1], orientation_[2]);
-				neye_ += r.matrix()*Vector4f(scalar,0.0,0.0,1.0);
-				return true;
-			} else if (key == 264 || key == 265) {
-				float mag = (modifiers & 0x1) ? 0.01f : 0.1f;
-				float scalar = (key == 264) ? -mag : mag;
-				Eigen::Affine3f r = create_rotation_matrix(orientation_[0], orientation_[1], orientation_[2]);
-				neye_ += r.matrix()*Vector4f(0.0,0.0,scalar,1.0);
-				return true;
-			} else if (key == 266 || key == 267) {
-				float mag = (modifiers & 0x1) ? 0.01f : 0.1f;
-				float scalar = (key == 266) ? -mag : mag;
-				Eigen::Affine3f r = create_rotation_matrix(orientation_[0], orientation_[1], orientation_[2]);
-				neye_ += r.matrix()*Vector4f(0.0,scalar,0.0,1.0);
-				return true;
-			} else if (action == 1 && key == 'H') {
-				swindow_->setVisible(false);
-				cwindow_->setVisible(false);
-			} else if (action == 1 && key == 32) {
-				ctrl_->pause();
-			}
-			return false;
-		}
-	}
-
-	StatisticsImageNSamples *stats_ = nullptr;
-
-	virtual void draw(NVGcontext *ctx) {
-		using namespace Eigen;
-
-		auto src_ = swindow_->getSource();
-		imageSize = {0, 0};
-
-		float now = (float)glfwGetTime();
-		delta_ = now - ftime_;
-		ftime_ = now;
-
-		if (src_) {
-			cv::Mat rgb, depth;
-
-			// Lerp the Eye
-			eye_[0] += (neye_[0] - eye_[0]) * lerpSpeed_ * delta_;
-			eye_[1] += (neye_[1] - eye_[1]) * lerpSpeed_ * delta_;
-			eye_[2] += (neye_[2] - eye_[2]) * lerpSpeed_ * delta_;
-
-			Eigen::Affine3f r = create_rotation_matrix(orientation_[0], orientation_[1], orientation_[2]);
-			Eigen::Translation3f trans(eye_);
-			Eigen::Affine3f t(trans);
-			Eigen::Matrix4f viewPose = (t * r).matrix();
-
-			src_->setPose(viewPose.cast<double>());
-			src_->grab();
-			src_->getFrames(rgb, depth);
-
-			if (!stats_ && depth.rows > 0) {
-				stats_ = new StatisticsImageNSamples(depth.size(), 25);
-			}
-			
-			if (stats_ && depth.rows > 0) { stats_->update(depth); }
-
-			cv::Mat tmp;
-
-			using ftl::gui::SourceWindow;
-			switch(swindow_->getMode()) {
-				case SourceWindow::Mode::depth:
-					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);
-					texture_.update(tmp);
-					mImageID = texture_.texture();
-					break;
-				
-				case SourceWindow::Mode::stddev:
-					if (depth.rows == 0) { break; }
-					imageSize = Vector2f(depth.cols, depth.rows);
-					stats_->getStdDev(tmp);
-					tmp.convertTo(tmp, CV_8U, 50.0);
-					applyColorMap(tmp, tmp, cv::COLORMAP_HOT);
-					texture_.update(tmp);
-					mImageID = texture_.texture();
-					break;
-
-				default:
-					if (rgb.rows == 0) { break; }
-					imageSize = Vector2f(rgb.cols,rgb.rows);
-					texture_.update(rgb);
-					mImageID = texture_.texture();
-			}
-		}
-
-		Vector2f screenSize = size().cast<float>();
-
-		if (imageSize[0] > 0) {
-			auto mScale = (screenSize.cwiseQuotient(imageSize).minCoeff());
-			Vector2f scaleFactor = mScale * imageSize.cwiseQuotient(screenSize);
-			Vector2f positionInScreen(0.0f, 0.0f);
-			auto mOffset = (screenSize - (screenSize.cwiseProduct(scaleFactor))) / 2;
-			Vector2f positionAfterOffset = positionInScreen + mOffset;
-			Vector2f imagePosition = positionAfterOffset.cwiseQuotient(screenSize);
-			//glEnable(GL_SCISSOR_TEST);
-			//float r = screen->pixelRatio();
-			/* glScissor(positionInScreen.x() * r,
-					(screenSize.y() - positionInScreen.y() - size().y()) * r,
-					size().x() * r, size().y() * r);*/
-			mShader.bind();
-			glActiveTexture(GL_TEXTURE0);
-			glBindTexture(GL_TEXTURE_2D, mImageID);
-			mShader.setUniform("image", 0);
-			mShader.setUniform("scaleFactor", scaleFactor);
-			mShader.setUniform("position", imagePosition);
-			mShader.drawIndexed(GL_TRIANGLES, 0, 2);
-			//glDisable(GL_SCISSOR_TEST);
-		}
-
-		nvgTextAlign(ctx, NVG_ALIGN_RIGHT);
-		nvgText(ctx, screenSize[0]-10, screenSize[1]-20, status_.c_str(), NULL);
-
-		/* Draw the user interface */
-		screen()->performLayout(ctx);
-		Screen::draw(ctx);
-	}
-
-	private:
-	ftl::gui::SourceWindow *swindow_;
-	ftl::gui::ControlWindow *cwindow_;
-	//std::vector<SourceViews> sources_;
-	ftl::net::Universe *net_;
-	nanogui::GLShader mShader;
-    GLuint mImageID;
-	//Source *src_;
-	GLTexture texture_;
-	Eigen::Vector3f eye_;
-	Eigen::Vector4f neye_;
-	Eigen::Vector3f orientation_;
-	Eigen::Vector3f up_;
-	//Eigen::Vector3f lookPoint_;
-	float lerpSpeed_;
-	bool depth_;
-	float ftime_;
-	float delta_;
-	Eigen::Vector2f imageSize;
-	ftl::ctrl::Master *ctrl_;
-	std::string status_;
-};
 
 int main(int argc, char **argv) {
 	auto root = ftl::configure(argc, argv, "gui_default");
@@ -620,7 +34,7 @@ int main(int argc, char **argv) {
 		nanogui::init();
 
 		/* scoped variables */ {
-			nanogui::ref<FTLApplication> app = new FTLApplication(root, net, controller);
+			nanogui::ref<ftl::gui::Screen> app = new ftl::gui::Screen(root, net, controller);
 			app->drawAll();
 			app->setVisible(true);
 			nanogui::mainloop();
diff --git a/applications/gui/src/media_panel.cpp b/applications/gui/src/media_panel.cpp
new file mode 100644
index 000000000..757259aef
--- /dev/null
+++ b/applications/gui/src/media_panel.cpp
@@ -0,0 +1,139 @@
+#include "media_panel.hpp"
+#include "screen.hpp"
+#include "camera.hpp"
+
+#include <nanogui/layout.h>
+#include <nanogui/button.h>
+#include <nanogui/popupbutton.h>
+#include <nanogui/entypo.h>
+
+#ifdef HAVE_LIBARCHIVE
+#include "ftl/rgbd/snapshot.hpp"
+#endif
+
+using ftl::gui::MediaPanel;
+
+MediaPanel::MediaPanel(ftl::gui::Screen *screen) : nanogui::Window(screen, ""), screen_(screen) {
+    using namespace nanogui;
+
+    paused_ = false;
+
+    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]));
+
+	Theme *mediatheme = new Theme(*theme());
+	mediatheme->mIconScale = 1.2f;
+	mediatheme->mWindowDropShadowSize = 0;
+	mediatheme->mWindowFillFocused = nanogui::Color(45, 150);
+	mediatheme->mWindowFillUnfocused = nanogui::Color(45, 80);
+	mediatheme->mButtonGradientTopUnfocused = nanogui::Color(0,0);
+	mediatheme->mButtonGradientBotUnfocused = nanogui::Color(0,0);
+	mediatheme->mButtonGradientTopFocused = nanogui::Color(80,230);
+	mediatheme->mButtonGradientBotFocused = nanogui::Color(80,230);
+	mediatheme->mIconColor = nanogui::Color(255,255);
+	mediatheme->mBorderDark = nanogui::Color(0,0);
+	mediatheme->mBorderMedium = nanogui::Color(0,0);
+	mediatheme->mBorderLight = nanogui::Color(0,0);
+	mediatheme->mDropShadow = nanogui::Color(0,0);
+	mediatheme->mButtonFontSize = 30;
+
+	setTheme(mediatheme);
+
+    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 = new Button(this, "", ENTYPO_ICON_CONTROLLER_STOP);
+    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_RECORD);
+
+ #ifdef HAVE_LIBARCHIVE
+	auto button_snapshot = new Button(this, "", ENTYPO_ICON_IMAGES);
+    button_snapshot->setTooltip("Screen capture");
+	button_snapshot->setCallback([this] {
+        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.addCameraRGBD(
+					"0", // TODO
+					rgb,
+					depth,
+					cam->source()->getPose(),
+					cam->source()->parameters()
+				)) {
+				LOG(ERROR) << "Snapshot failed";
+			}
+		}
+		catch(std::runtime_error) {
+			LOG(ERROR) << "Snapshot failed (file error)";
+		}
+	});
+#endif
+
+    auto popbutton = new PopupButton(this, "", ENTYPO_ICON_LAYERS);
+    popbutton->setSide(Popup::Side::Right);
+	popbutton->setChevronIcon(ENTYPO_ICON_CHEVRON_SMALL_RIGHT);
+    Popup *popup = popbutton->popup();
+    popup->setLayout(new GroupLayout());
+    popup->setAnchorHeight(100);
+
+    button = new Button(popup, "Left");
+    button->setFlags(Button::RadioButton);
+    button->setPushed(true);
+    button->setCallback([this]() {
+        ftl::gui::Camera *cam = screen_->activeCamera();
+        if (cam) {
+            cam->setChannel(ftl::rgbd::kChanLeft);
+        }
+    });
+
+    button = new Button(popup, "Depth");
+    button->setFlags(Button::RadioButton);
+    button->setCallback([this]() {
+        ftl::gui::Camera *cam = screen_->activeCamera();
+        if (cam) {
+            cam->setChannel(ftl::rgbd::kChanDepth);
+        }
+    });
+
+    button = new Button(popup, "Deviation");
+    button->setFlags(Button::RadioButton);
+    button->setCallback([this]() {
+        ftl::gui::Camera *cam = screen_->activeCamera();
+        if (cam) {
+            cam->setChannel(ftl::rgbd::kChanDeviation);
+        }
+    });
+}
+
+MediaPanel::~MediaPanel() {
+
+}
diff --git a/applications/gui/src/media_panel.hpp b/applications/gui/src/media_panel.hpp
new file mode 100644
index 000000000..0f17bd340
--- /dev/null
+++ b/applications/gui/src/media_panel.hpp
@@ -0,0 +1,24 @@
+#ifndef _FTL_GUI_MEDIAPANEL_HPP_
+#define _FTL_GUI_MEDIAPANEL_HPP_
+
+#include <nanogui/window.h>
+
+namespace ftl {
+namespace gui {
+
+class Screen;
+
+class MediaPanel : public nanogui::Window {
+    public:
+    MediaPanel(ftl::gui::Screen *);
+    ~MediaPanel();
+
+    private:
+    ftl::gui::Screen *screen_;
+    bool paused_;
+};
+
+}
+}
+
+#endif  // _FTL_GUI_MEDIAPANEL_HPP_
diff --git a/applications/gui/src/pose_window.cpp b/applications/gui/src/pose_window.cpp
index 4c3df6e9b..d1afdb739 100644
--- a/applications/gui/src/pose_window.cpp
+++ b/applications/gui/src/pose_window.cpp
@@ -1,4 +1,6 @@
 #include "pose_window.hpp"
+#include "screen.hpp"
+#include "camera.hpp"
 
 #include <nanogui/combobox.h>
 #include <nanogui/label.h>
@@ -6,10 +8,21 @@
 #include <nanogui/button.h>
 
 using ftl::gui::PoseWindow;
+using ftl::gui::Screen;
 using std::string;
 
-PoseWindow::PoseWindow(nanogui::Widget *parent, ftl::ctrl::Master *ctrl, const std::string &src)
-		: nanogui::Window(parent, "Pose Adjust"), ctrl_(ctrl), src_(src) {
+static Eigen::Affine3d create_rotation_matrix(float ax, float ay, float az) {
+  Eigen::Affine3d rx =
+      Eigen::Affine3d(Eigen::AngleAxisd(ax, Eigen::Vector3d(1, 0, 0)));
+  Eigen::Affine3d ry =
+      Eigen::Affine3d(Eigen::AngleAxisd(ay, Eigen::Vector3d(0, 1, 0)));
+  Eigen::Affine3d rz =
+      Eigen::Affine3d(Eigen::AngleAxisd(az, Eigen::Vector3d(0, 0, 1)));
+  return ry * rz * rx;
+}
+
+PoseWindow::PoseWindow(ftl::gui::Screen *screen, const std::string &src)
+		: nanogui::Window(screen, "Pose Adjust"), screen_(screen), src_(src) {
 	using namespace nanogui;
 
 	//setLayout(new nanogui::GroupLayout());
@@ -19,7 +32,7 @@ PoseWindow::PoseWindow(nanogui::Widget *parent, ftl::ctrl::Master *ctrl, const s
 	pose_param_ = kPoseTranslation;
 	pose_precision_ = 0.1;
 
-	pose_ = ctrl_->getPose(src_);
+	pose_ = screen_->control()->getPose(src_);
 
 	//Widget *tools = new Widget(this);
 	//    tools->setLayout(new BoxLayout(Orientation::Horizontal,
@@ -29,16 +42,16 @@ PoseWindow::PoseWindow(nanogui::Widget *parent, ftl::ctrl::Master *ctrl, const s
 	grouping->setLayout(new GroupLayout());
 
 	new Label(grouping, "Select source","sans-bold");
-	available_ = ctrl->getNet()->findAll<string>("list_streams");
+	available_ = screen_->net()->findAll<string>("list_streams");
 	auto select = new ComboBox(grouping, available_);
 	select->setSelectedIndex(std::distance(available_.begin(), std::find(available_.begin(), available_.end(), src_)));
 	select->setCallback([this,select](int ix) {
 		src_ = available_[ix];
-		pose_ = ctrl_->getPose(src_);
+		pose_ = screen_->control()->getPose(src_);
 	});
 
-	ctrl->getNet()->onConnect([this,select](ftl::net::Peer *p) {
-		available_ = ctrl_->getNet()->findAll<string>("list_streams");
+	screen_->net()->onConnect([this,select](ftl::net::Peer *p) {
+		available_ = screen_->control()->getNet()->findAll<string>("list_streams");
 		select->setItems(available_);
 	});
 
@@ -52,7 +65,9 @@ PoseWindow::PoseWindow(nanogui::Widget *parent, ftl::ctrl::Master *ctrl, const s
 	button_opt->setTooltip("Virtual view to this pose");
 	//button_opt->setFlags(Button::ToggleButton);
 	//button_opt->setPushed(false);
-	button_opt->setCallback([this]() {  });
+	button_opt->setCallback([this]() {
+		screen_->activeCamera()->setPose(pose_);
+	});
 
 	button_opt = new Button(tools, "", ENTYPO_ICON_LINK);
 	button_opt->setTooltip("Link virtual current pose to this pose");
@@ -68,11 +83,11 @@ PoseWindow::PoseWindow(nanogui::Widget *parent, ftl::ctrl::Master *ctrl, const s
 	button_rgb->setTooltip("Adjust camera location");
 	button_rgb->setFlags(Button::RadioButton);
 	button_rgb->setPushed(true);
-	button_rgb->setChangeCallback([this](bool state) { pose_param_ = kPoseTranslation; });
+	button_rgb->setCallback([this]() { pose_param_ = kPoseTranslation; });
 
 	auto button_depth = new Button(tools, "Rotation");
 	button_depth->setFlags(Button::RadioButton);
-	button_depth->setChangeCallback([this](bool state) { pose_param_ = kPoseRotation; });
+	button_depth->setCallback([this]() { pose_param_ = kPoseRotation; });
 
 	auto button_stddev = new Button(tools, "Raw");
 	button_stddev->setTooltip("Edit the numbers directly");
@@ -110,49 +125,80 @@ PoseWindow::PoseWindow(nanogui::Widget *parent, ftl::ctrl::Master *ctrl, const s
 
 	button = new Button(tools, "Up");
 	button->setCallback([this]() {
-		Eigen::Affine3d transform(Eigen::Translation3d(0.0,-pose_precision_,0.0));
-		Eigen::Matrix4d matrix = transform.matrix();
-		pose_ *= matrix;
-		ctrl_->setPose(src_, pose_);
+		if (pose_param_ ==  kPoseTranslation) {
+			Eigen::Affine3d transform(Eigen::Translation3d(0.0,-pose_precision_,0.0));
+			Eigen::Matrix4d matrix = transform.matrix();
+			pose_ *= matrix;
+		} else if (pose_param_ == kPoseRotation) {
+			Eigen::Affine3d r = create_rotation_matrix(pose_precision_, 0.0, 0.0);
+			pose_ = r.matrix() * pose_;
+		}
+		
+		screen_->control()->setPose(src_, pose_);
 	});
 	button = new Button(tools, "", ENTYPO_ICON_CHEVRON_UP);
 	button->setCallback([this]() {
-		Eigen::Affine3d transform(Eigen::Translation3d(0.0,0.0,-pose_precision_));
-		Eigen::Matrix4d matrix = transform.matrix();
-		pose_ *= matrix;
-		ctrl_->setPose(src_, pose_);
+		if (pose_param_ == kPoseTranslation) {
+			Eigen::Affine3d transform(Eigen::Translation3d(0.0,0.0,-pose_precision_));
+			Eigen::Matrix4d matrix = transform.matrix();
+			pose_ *= matrix;
+		} else if (pose_param_ == kPoseRotation) {
+			Eigen::Affine3d r = create_rotation_matrix(0.0, 0.0, pose_precision_);
+			pose_ = r.matrix() * pose_;
+		}
+		screen_->control()->setPose(src_, pose_);
 	});
 	button = new Button(tools, "Down");
 	button->setCallback([this]() {
-		Eigen::Affine3d transform(Eigen::Translation3d(0.0,pose_precision_,0.0));
-		Eigen::Matrix4d matrix = transform.matrix();
-		pose_ *= matrix;
-		ctrl_->setPose(src_, pose_);
+		if (pose_param_ == kPoseTranslation) {
+			Eigen::Affine3d transform(Eigen::Translation3d(0.0,pose_precision_,0.0));
+			Eigen::Matrix4d matrix = transform.matrix();
+			pose_ *= matrix;
+		} else if (pose_param_ == kPoseRotation) {
+			Eigen::Affine3d r = create_rotation_matrix(-pose_precision_, 0.0, 0.0);
+			pose_ = r.matrix() * pose_;
+		}
+		screen_->control()->setPose(src_, pose_);
 	});
 
 	button = new Button(tools, "", ENTYPO_ICON_CHEVRON_LEFT);
 	button->setCallback([this]() {
-		Eigen::Affine3d transform(Eigen::Translation3d(-pose_precision_,0.0,0.0));
-		Eigen::Matrix4d matrix = transform.matrix();
-		pose_ *= matrix;
-		ctrl_->setPose(src_, pose_);
+		if (pose_param_ == kPoseTranslation) {
+			Eigen::Affine3d transform(Eigen::Translation3d(-pose_precision_,0.0,0.0));
+			Eigen::Matrix4d matrix = transform.matrix();
+			pose_ *= matrix;
+		} else if (pose_param_ == kPoseRotation) {
+			Eigen::Affine3d r = create_rotation_matrix(0.0, pose_precision_, 0.0);
+			pose_ = r.matrix() * pose_;
+		}
+		screen_->control()->setPose(src_, pose_);
 	});
 	new Widget(tools);
 	button = new Button(tools, "", ENTYPO_ICON_CHEVRON_RIGHT);
 	button->setCallback([this]() {
-		Eigen::Affine3d transform(Eigen::Translation3d(pose_precision_,0.0,0.0));
-		Eigen::Matrix4d matrix = transform.matrix();
-		pose_ *= matrix;
-		ctrl_->setPose(src_, pose_);
+		if (pose_param_ == kPoseTranslation) {
+			Eigen::Affine3d transform(Eigen::Translation3d(pose_precision_,0.0,0.0));
+			Eigen::Matrix4d matrix = transform.matrix();
+			pose_ *= matrix;
+		} else if (pose_param_ == kPoseRotation) {
+			Eigen::Affine3d r = create_rotation_matrix(0.0, -pose_precision_, 0.0);
+			pose_ = r.matrix() * pose_;
+		}
+		screen_->control()->setPose(src_, pose_);
 	});
 
 	new Widget(tools);
 	button = new Button(tools, "", ENTYPO_ICON_CHEVRON_DOWN);
 	button->setCallback([this]() {
-		Eigen::Affine3d transform(Eigen::Translation3d(0.0,0.0,pose_precision_));
-		Eigen::Matrix4d matrix = transform.matrix();
-		pose_ *= matrix;
-		ctrl_->setPose(src_, pose_);
+		if (pose_param_ == kPoseTranslation) {
+			Eigen::Affine3d transform(Eigen::Translation3d(0.0,0.0,pose_precision_));
+			Eigen::Matrix4d matrix = transform.matrix();
+			pose_ *= matrix;
+		} else if (pose_param_ == kPoseRotation) {
+			Eigen::Affine3d r = create_rotation_matrix(0.0, 0.0, -pose_precision_);
+			pose_ = r.matrix() * pose_;
+		}
+		screen_->control()->setPose(src_, pose_);
 	});
 }
 
diff --git a/applications/gui/src/pose_window.hpp b/applications/gui/src/pose_window.hpp
index cfe03adff..bbd041414 100644
--- a/applications/gui/src/pose_window.hpp
+++ b/applications/gui/src/pose_window.hpp
@@ -8,16 +8,17 @@
 namespace ftl {
 namespace gui {
 
+class Screen;
+
 /**
  * Manage connected nodes and add new connections.
  */
 class PoseWindow : public nanogui::Window {
 	public:
-	PoseWindow(nanogui::Widget *parent, ftl::ctrl::Master *ctrl, const std::string &src);
+	PoseWindow(ftl::gui::Screen *screen, const std::string &src);
 	~PoseWindow();
 
 	private:
-	ftl::ctrl::Master *ctrl_;
 	std::vector<std::string> available_;
 	std::string src_;
 
@@ -30,6 +31,8 @@ class PoseWindow : public nanogui::Window {
 	poseparameter_t pose_param_;
 	float pose_precision_;
 	Eigen::Matrix4d pose_;
+	ftl::gui::Screen *screen_;
+	bool poselink_;
 };
 
 }
diff --git a/applications/gui/src/screen.cpp b/applications/gui/src/screen.cpp
new file mode 100644
index 000000000..4175e5084
--- /dev/null
+++ b/applications/gui/src/screen.cpp
@@ -0,0 +1,308 @@
+#include "screen.hpp"
+
+#include <nanogui/opengl.h>
+#include <nanogui/glutil.h>
+#include <nanogui/screen.h>
+#include <nanogui/window.h>
+#include <nanogui/layout.h>
+#include <nanogui/imageview.h>
+#include <nanogui/combobox.h>
+#include <nanogui/label.h>
+#include <nanogui/toolbutton.h>
+
+#include <opencv2/opencv.hpp>
+
+#include <loguru.hpp>
+
+#include "ctrl_window.hpp"
+#include "src_window.hpp"
+#include "camera.hpp"
+#include "media_panel.hpp"
+
+using ftl::gui::Screen;
+using ftl::gui::Camera;
+using std::string;
+using ftl::rgbd::Source;
+using ftl::rgbd::isValidDepth;
+
+namespace {
+    constexpr char const *const defaultImageViewVertexShader =
+        R"(#version 330
+        uniform vec2 scaleFactor;
+        uniform vec2 position;
+        in vec2 vertex;
+        out vec2 uv;
+        void main() {
+            uv = vertex;
+            vec2 scaledVertex = (vertex * scaleFactor) + position;
+            gl_Position  = vec4(2.0*scaledVertex.x - 1.0,
+                                1.0 - 2.0*scaledVertex.y,
+                                0.0, 1.0);
+        })";
+
+    constexpr char const *const defaultImageViewFragmentShader =
+        R"(#version 330
+        uniform sampler2D image;
+        out vec4 color;
+        in vec2 uv;
+        void main() {
+            color = texture(image, uv);
+        })";
+}
+
+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") {
+	using namespace nanogui;
+	net_ = pnet;
+	ctrl_ = controller;
+	root_ = proot;
+	camera_ = nullptr;
+
+	status_ = "FT-Lab Remote Presence System";
+
+	setSize(Vector2i(1280,720));
+
+	Theme *toolbuttheme = new Theme(*theme());
+	toolbuttheme->mBorderDark = nanogui::Color(0,0);
+	toolbuttheme->mBorderLight = nanogui::Color(0,0);
+	toolbuttheme->mButtonGradientBotFocused = nanogui::Color(60,255);
+	toolbuttheme->mButtonGradientBotUnfocused = nanogui::Color(0,0);
+	toolbuttheme->mButtonGradientTopFocused = nanogui::Color(60,255);
+	toolbuttheme->mButtonGradientTopUnfocused = nanogui::Color(0,0);
+	toolbuttheme->mButtonGradientTopPushed = nanogui::Color(60,180);
+	toolbuttheme->mButtonGradientBotPushed = nanogui::Color(60,180);
+
+	windowtheme = new Theme(*theme());
+	windowtheme->mWindowFillFocused = nanogui::Color(220, 200);
+	windowtheme->mWindowFillUnfocused = nanogui::Color(220, 200);
+	windowtheme->mWindowHeaderGradientBot = nanogui::Color(60,230);
+	windowtheme->mWindowHeaderGradientTop = nanogui::Color(60,230);
+	windowtheme->mTextColor = nanogui::Color(20,255);
+	windowtheme->mWindowCornerRadius = 2;
+	windowtheme->mButtonGradientBotFocused = nanogui::Color(210,255);
+	windowtheme->mButtonGradientBotUnfocused = nanogui::Color(190,255);
+	windowtheme->mButtonGradientTopFocused = nanogui::Color(230,255);
+	windowtheme->mButtonGradientTopUnfocused = nanogui::Color(230,255);
+	windowtheme->mButtonGradientTopPushed = nanogui::Color(170,255);
+	windowtheme->mButtonGradientBotPushed = nanogui::Color(210,255);
+	windowtheme->mBorderDark = nanogui::Color(150,255);
+	windowtheme->mBorderMedium = nanogui::Color(165,255);
+	windowtheme->mBorderLight = nanogui::Color(230,255);
+	windowtheme->mButtonFontSize = 16;
+	windowtheme->mTextColorShadow = nanogui::Color(0,0);
+	windowtheme->mWindowTitleUnfocused = windowtheme->mWindowTitleFocused;
+	windowtheme->mWindowTitleFocused = nanogui::Color(240,255);
+	windowtheme->mIconScale = 0.85f;
+
+	auto toolbar = new Window(this, "");
+	toolbar->setPosition(Vector2i(0,0));
+	toolbar->setFixedWidth(50);
+	toolbar->setFixedHeight(height());
+	//toolbar->setLayout(new BoxLayout(Orientation::Vertical,
+	//                               Alignment::Middle, 0, 10));
+
+	setResizeCallback([this,toolbar](Vector2i s) {
+		toolbar->setFixedHeight(s[1]);
+		mwindow_->setPosition(Vector2i(s[0] / 2 - mwindow_->width()/2, s[1] - 30 - mwindow_->height()));
+	});
+
+	auto innertool = new Widget(toolbar);
+	innertool->setLayout(new BoxLayout(Orientation::Vertical,
+									Alignment::Middle, 0, 10));
+	innertool->setPosition(Vector2i(5,10));
+
+	// Padding widget
+	//auto w = new Widget(innertool);
+	//w->setHeight(10);
+
+	auto button = new ToolButton(innertool, ENTYPO_ICON_HOME);
+	button->setIconExtraScale(1.5f);
+	button->setTheme(toolbuttheme);
+	button->setTooltip("Home");
+	button->setFixedSize(Vector2i(40,40));
+	button->setCallback([this]() {
+		//swindow_->setVisible(true);
+	});
+
+	/*button = new ToolButton(innertool, ENTYPO_ICON_PLUS);
+	button->setIconExtraScale(1.5f);
+	button->setTheme(toolbuttheme);
+	button->setTooltip("Add new");
+	button->setFixedSize(Vector2i(40,40));
+	button->setCallback([this]() {
+		//swindow_->setVisible(true);
+	});*/
+
+	button = new ToolButton(innertool, ENTYPO_ICON_PLUS);
+	button->setIconExtraScale(1.5f);
+	button->setTheme(toolbuttheme);
+	button->setTooltip("Camera Sources");
+	button->setFixedSize(Vector2i(40,40));
+	button->setCallback([this]() {
+		swindow_->setVisible(true);
+	});
+
+	button = new ToolButton(innertool, ENTYPO_ICON_TOOLS);
+	button->setIconExtraScale(1.5f);
+	button->setTheme(toolbuttheme);
+	button->setTooltip("Connections");
+	button->setFixedSize(Vector2i(40,40));
+	button->setCallback([this]() {
+		cwindow_->setVisible(true);
+	});
+
+	button = new ToolButton(toolbar, ENTYPO_ICON_COG);
+	button->setIconExtraScale(1.5f);
+	button->setTheme(toolbuttheme);
+	button->setTooltip("Settings");
+	button->setFixedSize(Vector2i(40,40));
+	button->setPosition(Vector2i(5,height()-50));
+
+	//configwindow_ = new ConfigWindow(parent, ctrl_);
+	cwindow_ = new ftl::gui::ControlWindow(this, controller);
+	swindow_ = new ftl::gui::SourceWindow(this);
+	mwindow_ = new ftl::gui::MediaPanel(this);
+	mwindow_->setVisible(false);
+
+	cwindow_->setPosition(Eigen::Vector2i(80, 20));
+	swindow_->setPosition(Eigen::Vector2i(80, 400));
+	cwindow_->setVisible(false);
+	swindow_->setVisible(false);
+	cwindow_->setTheme(windowtheme);
+	swindow_->setTheme(windowtheme);
+
+	mShader.init("RGBDShader", defaultImageViewVertexShader,
+				defaultImageViewFragmentShader);
+
+	MatrixXu indices(3, 2);
+	indices.col(0) << 0, 1, 2;
+	indices.col(1) << 2, 3, 1;
+
+	MatrixXf vertices(2, 4);
+	vertices.col(0) << 0, 0;
+	vertices.col(1) << 1, 0;
+	vertices.col(2) << 0, 1;
+	vertices.col(3) << 1, 1;
+
+	mShader.bind();
+	mShader.uploadIndices(indices);
+	mShader.uploadAttrib("vertex", vertices);
+
+	setVisible(true);
+	performLayout();
+}
+
+ftl::gui::Screen::~Screen() {
+	mShader.free();
+}
+
+void ftl::gui::Screen::setActiveCamera(ftl::gui::Camera *cam) {
+	camera_ = cam;
+
+	if (cam) {
+		status_ = cam->source()->getURI();
+		mwindow_->setVisible(true);
+	} else {
+		mwindow_->setVisible(false);
+		status_ = "No camera...";
+	}
+}
+
+bool ftl::gui::Screen::mouseMotionEvent(const Eigen::Vector2i &p, const Eigen::Vector2i &rel, int button, int modifiers) {
+	if (nanogui::Screen::mouseMotionEvent(p, rel, button, modifiers)) {
+		return true;
+	} else {
+		if (camera_) camera_->mouseMovement(rel[0], rel[1], button);
+	}
+}
+
+bool ftl::gui::Screen::mouseButtonEvent(const nanogui::Vector2i &p, int button, bool down, int modifiers) {
+	if (nanogui::Screen::mouseButtonEvent(p, button, down, modifiers)) {
+		return true;
+	} else {
+		if (camera_ && down) {
+			Eigen::Vector2f screenSize = size().cast<float>();
+			auto mScale = (screenSize.cwiseQuotient(imageSize).minCoeff());
+			Eigen::Vector2f scaleFactor = mScale * imageSize.cwiseQuotient(screenSize);
+			Eigen::Vector2f positionInScreen(0.0f, 0.0f);
+			auto mOffset = (screenSize - (screenSize.cwiseProduct(scaleFactor))) / 2;
+			Eigen::Vector2f positionAfterOffset = positionInScreen + mOffset;
+
+			float sx = ((float)p[0] - positionAfterOffset[0]) / mScale;
+			float sy = ((float)p[1] - positionAfterOffset[1]) / mScale;
+
+			Eigen::Vector4f camPos;
+
+			try {
+				camPos = camera_->source()->point(sx,sy).cast<float>();
+			} catch(...) {
+				return true;
+			}
+			
+			camPos *= -1.0f;
+			Eigen::Vector4f worldPos =  camera_->source()->getPose().cast<float>() * camPos;
+			//lookPoint_ = Eigen::Vector3f(worldPos[0],worldPos[1],worldPos[2]);
+			LOG(INFO) << "Depth at click = " << -camPos[2];
+			return true;
+		}
+	return false;
+	}
+}
+
+bool ftl::gui::Screen::keyboardEvent(int key, int scancode, int action, int modifiers) {
+	using namespace Eigen;
+	if (nanogui::Screen::keyboardEvent(key, scancode, action, modifiers)) {
+		return true;
+	} else {
+		LOG(INFO) << "Key press " << key << " - " << action << " - " << modifiers;
+
+		if (key >= 262 && key <= 267) {
+			if (camera_) camera_->keyMovement(key, modifiers);
+			return true;
+		} else if (action == 1 && key == 32) {
+			ctrl_->pause();
+			return true;
+		}
+		return false;
+	}
+}
+
+void ftl::gui::Screen::draw(NVGcontext *ctx) {
+	using namespace Eigen;
+
+	Vector2f screenSize = size().cast<float>();
+
+	if (camera_) {
+		imageSize = {camera_->width(), camera_->height()};
+
+		mImageID = camera_->captureFrame().texture();
+
+		if (imageSize[0] > 0) {
+			auto mScale = (screenSize.cwiseQuotient(imageSize).minCoeff());
+			Vector2f scaleFactor = mScale * imageSize.cwiseQuotient(screenSize);
+			Vector2f positionInScreen(0.0f, 0.0f);
+			auto mOffset = (screenSize - (screenSize.cwiseProduct(scaleFactor))) / 2;
+			Vector2f positionAfterOffset = positionInScreen + mOffset;
+			Vector2f imagePosition = positionAfterOffset.cwiseQuotient(screenSize);
+			//glEnable(GL_SCISSOR_TEST);
+			//float r = screen->pixelRatio();
+			/* glScissor(positionInScreen.x() * r,
+					(screenSize.y() - positionInScreen.y() - size().y()) * r,
+					size().x() * r, size().y() * r);*/
+			mShader.bind();
+			glActiveTexture(GL_TEXTURE0);
+			glBindTexture(GL_TEXTURE_2D, mImageID);
+			mShader.setUniform("image", 0);
+			mShader.setUniform("scaleFactor", scaleFactor);
+			mShader.setUniform("position", imagePosition);
+			mShader.drawIndexed(GL_TRIANGLES, 0, 2);
+			//glDisable(GL_SCISSOR_TEST);
+		}
+	}
+
+	nvgTextAlign(ctx, NVG_ALIGN_RIGHT);
+	nvgText(ctx, screenSize[0]-10, screenSize[1]-20, status_.c_str(), NULL);
+
+	/* Draw the user interface */
+	screen()->performLayout(ctx);
+	nanogui::Screen::draw(ctx);
+}
\ No newline at end of file
diff --git a/applications/gui/src/screen.hpp b/applications/gui/src/screen.hpp
new file mode 100644
index 000000000..7746aa49e
--- /dev/null
+++ b/applications/gui/src/screen.hpp
@@ -0,0 +1,74 @@
+#ifndef _FTL_GUI_SCREEN_HPP_
+#define _FTL_GUI_SCREEN_HPP_
+
+#include <nanogui/screen.h>
+#include <nanogui/glutil.h>
+#include <ftl/master.hpp>
+#include <ftl/net/universe.hpp>
+#include <ftl/configuration.hpp>
+
+#include "ctrl_window.hpp"
+#include "src_window.hpp"
+#include "gltexture.hpp"
+
+class StatisticsImageNSamples;
+
+namespace ftl {
+namespace gui {
+
+class Camera;
+class MediaPanel;
+
+class Screen : public nanogui::Screen {
+	public:
+	explicit Screen(ftl::Configurable *root, ftl::net::Universe *net, ftl::ctrl::Master *controller);
+	~Screen();
+
+	bool mouseMotionEvent(const Eigen::Vector2i &p, const Eigen::Vector2i &rel, int button, int modifiers);
+	bool mouseButtonEvent(const nanogui::Vector2i &p, int button, bool down, int modifiers);
+	bool keyboardEvent(int key, int scancode, int action, int modifiers);
+
+	void setActivePose(const Eigen::Matrix4d &p);
+
+	virtual void draw(NVGcontext *ctx);
+
+	ftl::Configurable *root() { return root_; }
+	ftl::net::Universe *net() { return net_; }
+	ftl::ctrl::Master *control() { return ctrl_; }
+
+	void setActiveCamera(ftl::gui::Camera*);
+	ftl::gui::Camera *activeCamera() { return camera_; }
+
+	nanogui::Theme *windowtheme;
+	nanogui::Theme *specialtheme;
+
+	private:
+	ftl::gui::SourceWindow *swindow_;
+	ftl::gui::ControlWindow *cwindow_;
+	ftl::gui::MediaPanel *mwindow_;
+	//std::vector<SourceViews> sources_;
+	ftl::net::Universe *net_;
+	nanogui::GLShader mShader;
+    GLuint mImageID;
+	//Source *src_;
+	GLTexture texture_;
+	Eigen::Vector3f eye_;
+	Eigen::Vector4f neye_;
+	Eigen::Vector3f orientation_;
+	Eigen::Vector3f up_;
+	//Eigen::Vector3f lookPoint_;
+	float lerpSpeed_;
+	bool depth_;
+	float ftime_;
+	float delta_;
+	Eigen::Vector2f imageSize;
+	ftl::ctrl::Master *ctrl_;
+	ftl::Configurable *root_;
+	std::string status_;
+	ftl::gui::Camera *camera_;
+};
+
+}
+}
+
+#endif  // _FTL_GUI_SCREEN_HPP_
diff --git a/applications/gui/src/src_window.cpp b/applications/gui/src/src_window.cpp
index b5e70e898..8a0f93da2 100644
--- a/applications/gui/src/src_window.cpp
+++ b/applications/gui/src/src_window.cpp
@@ -1,6 +1,7 @@
 #include "src_window.hpp"
 
-#include "pose_window.hpp"
+#include "screen.hpp"
+#include "camera.hpp"
 
 #include <nanogui/imageview.h>
 #include <nanogui/textbox.h>
@@ -17,92 +18,46 @@
 #endif
 
 using ftl::gui::SourceWindow;
+using ftl::gui::Screen;
 using ftl::rgbd::Source;
 using std::string;
+using ftl::config::json_t;
 
-class GLTexture {
-	public:
-	GLTexture() {
-		glGenTextures(1, &glid_);
-        glBindTexture(GL_TEXTURE_2D, glid_);
-		cv::Mat m(cv::Size(100,100), CV_8UC3);
-		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, m.cols, m.rows, 0, GL_RGB, GL_UNSIGNED_BYTE, m.data);
-		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
-        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
-        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
-        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
-	}
-	~GLTexture() {
-		glDeleteTextures(1, &glid_);
-	}
-
-	void update(cv::Mat &m) {
-		if (m.rows == 0) return;
-		glBindTexture(GL_TEXTURE_2D, glid_);
-		// TODO Allow for other formats
-		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, m.cols, m.rows, 0, GL_BGR, GL_UNSIGNED_BYTE, m.data);
-		auto err = glGetError();
-		if (err != 0) LOG(ERROR) << "OpenGL Texture error: " << err;
-	}
-
-	unsigned int texture() const { return glid_; }
-
-	private:
-	unsigned int glid_;
-};
-
-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;
-}
-
-SourceWindow::SourceWindow(nanogui::Widget *parent, ftl::ctrl::Master *ctrl)
-		: nanogui::Window(parent, "Source View"), ctrl_(ctrl) {
+SourceWindow::SourceWindow(ftl::gui::Screen *screen)
+		: nanogui::Window(screen, ""), screen_(screen) {
 	setLayout(new nanogui::GroupLayout());
 
 	using namespace nanogui;
 	
-	mode_ = Mode::rgb;
-	src_ = ftl::create<Source>(ctrl->getRoot(), "source", ctrl->getNet());
+	//if (!screen->root()->get<json_t>("sources")) {
+	//	screen->root()->getConfig()["sources"] = json_t::array();
+	//}
+
+	//src_ = ftl::create<Source>(ctrl->getRoot(), "source", ctrl->getNet());
 
 	//Widget *tools = new Widget(this);
 	//    tools->setLayout(new BoxLayout(Orientation::Horizontal,
 	//                                   Alignment::Middle, 0, 6));
 
 	new Label(this, "Select source","sans-bold");
-	available_ = ctrl->getNet()->findAll<string>("list_streams");
+	available_ = screen_->control()->getNet()->findAll<string>("list_streams");
 	auto select = new ComboBox(this, available_);
 	select->setCallback([this,select](int ix) {
+		//src_->set("uri", available_[ix]);
+		// TODO(Nick) Check camera exists first
+		screen_->setActiveCamera(cameras_[available_[ix]]);
 		LOG(INFO) << "Change source: " << ix;
-		src_->set("uri", available_[ix]);
 	});
 
-	ctrl->getNet()->onConnect([this,select](ftl::net::Peer *p) {
-		available_ = ctrl_->getNet()->findAll<string>("list_streams");
+	_updateCameras();
+
+	screen->net()->onConnect([this,select](ftl::net::Peer *p) {
+		available_ = screen_->net()->findAll<string>("list_streams");
 		select->setItems(available_);
+		_updateCameras();
 	});
 
-	new Label(this, "Source Options","sans-bold");
+	/*new Label(this, "Source Options","sans-bold");
 
 	auto tools = new Widget(this);
     tools->setLayout(new BoxLayout(Orientation::Horizontal,
@@ -123,11 +78,11 @@ SourceWindow::SourceWindow(nanogui::Widget *parent, ftl::ctrl::Master *ctrl)
 	button_stddev->setFlags(Button::RadioButton);
 	button_stddev->setChangeCallback([this](bool state) { mode_ = Mode::stddev; });
 
-	auto button_pose = new Button(this, "Adjust Pose", ENTYPO_ICON_COMPASS);
-	button_pose->setCallback([this]() {
-		auto posewin = new PoseWindow(screen(), ctrl_, src_->getURI());
-		posewin->setTheme(theme());
-	});
+	//auto button_pose = new Button(this, "Adjust Pose", ENTYPO_ICON_COMPASS);
+	//button_pose->setCallback([this]() {
+	//	auto posewin = new PoseWindow(screen_, screen_->control(), src_->getURI());
+	//	posewin->setTheme(theme());
+	//});
 
 #ifdef HAVE_LIBARCHIVE
 	auto button_snapshot = new Button(this, "Snapshot", ENTYPO_ICON_IMAGES);
@@ -159,7 +114,24 @@ SourceWindow::SourceWindow(nanogui::Widget *parent, ftl::ctrl::Master *ctrl)
 	//cam.view = imageView;
 	//imageView->setGridThreshold(20);
 	//imageView->setSource(src_);
-	//image_ = imageView;
+	//image_ = imageView;*/
+}
+
+void SourceWindow::_updateCameras() {
+	for (auto s : available_) {
+		if (cameras_.find(s) == cameras_.end()) {
+			json_t srcjson;
+			srcjson["uri"] = s;
+			screen_->root()->getConfig()["sources"].push_back(srcjson);
+			std::vector<ftl::rgbd::Source*> srcs = ftl::createArray<ftl::rgbd::Source>(screen_->root(), "sources", screen_->net());
+			auto *src = srcs[srcs.size()-1];
+
+			auto *cam = new ftl::gui::Camera(screen_, src);
+			cameras_[s] = cam;
+		} else {
+			LOG(INFO) << "Camera already exists: " << s;
+		}
+	}
 }
 
 SourceWindow::~SourceWindow() {
diff --git a/applications/gui/src/src_window.hpp b/applications/gui/src/src_window.hpp
index e7aa19213..77f529c8c 100644
--- a/applications/gui/src/src_window.hpp
+++ b/applications/gui/src/src_window.hpp
@@ -6,6 +6,7 @@
 #include <ftl/uuid.hpp>
 #include <ftl/rgbd/source.hpp>
 #include <vector>
+#include <map>
 #include <string>
 
 class VirtualCameraView;
@@ -13,26 +14,23 @@ class VirtualCameraView;
 namespace ftl {
 namespace gui {
 
-/**
- * Manage connected nodes and add new connections.
- */
+class Screen;
+class Camera;
+
 class SourceWindow : public nanogui::Window {
 	public:
-	enum class Mode { rgb, depth, stddev };
-	SourceWindow(nanogui::Widget *parent, ftl::ctrl::Master *ctrl);
+	SourceWindow(ftl::gui::Screen *screen);
 	~SourceWindow();
 
-	ftl::rgbd::Source *getSource() const { return src_; }
-	bool getDepth() const { return mode_ == Mode::depth; }
-	Mode getMode() { return mode_; }
+	const std::vector<ftl::gui::Camera*> &getCameras();
 
 	private:
-	ftl::ctrl::Master *ctrl_;
-	ftl::rgbd::Source *src_;
-	Mode mode_;
-	VirtualCameraView *image_;
+	ftl::gui::Screen *screen_;
+	std::map<std::string, ftl::gui::Camera*> cameras_; 
 	std::vector<std::string> available_;
 
+	void _updateCameras();
+
 };
 
 }
diff --git a/applications/reconstruct/src/registration.cpp b/applications/reconstruct/src/registration.cpp
index ad8c1e8cd..5b828242e 100644
--- a/applications/reconstruct/src/registration.cpp
+++ b/applications/reconstruct/src/registration.cpp
@@ -551,7 +551,7 @@ bool ChessboardRegistrationChain::findTransformations(vector<Matrix4f> &data) {
 						<< getSource(edge.second)->getID() << " to source"
 						<< getSource(edge.first)->getID();
 			
-			nlohmann::json conf(config_);
+			nlohmann::json conf(getConfig());
 			conf["targetsource"] = getSource(edge.first)->getID();
 			conf["chain"] = false;
 
diff --git a/applications/reconstruct/src/scene_rep_hash_sdf.cu b/applications/reconstruct/src/scene_rep_hash_sdf.cu
index 521a34601..c29e66b16 100644
--- a/applications/reconstruct/src/scene_rep_hash_sdf.cu
+++ b/applications/reconstruct/src/scene_rep_hash_sdf.cu
@@ -623,7 +623,7 @@ __global__ void starveVoxelsKernel(HashData hashData) {
 
 	//is typically exectued only every n'th frame
 	int weight = hashData.d_SDFBlocks[entry.ptr + threadIdx.x].weight;
-	weight = max(0, weight-1);	
+	weight = max(0, weight-2);	
 	hashData.d_SDFBlocks[entry.ptr + threadIdx.x].weight = weight;  //CHECK Remove to totally clear previous frame (Nick)
 }
 
diff --git a/components/common/cpp/include/ftl/configurable.hpp b/components/common/cpp/include/ftl/configurable.hpp
index 7999bcfa8..03197ce1e 100644
--- a/components/common/cpp/include/ftl/configurable.hpp
+++ b/components/common/cpp/include/ftl/configurable.hpp
@@ -50,7 +50,7 @@ class Configurable {
 	/**
 	 * Return raw JSON entity for this Configurable.
 	 */
-	nlohmann::json &getConfig() { return config_; }
+	nlohmann::json &getConfig() { return *config_; }
 
 	/**
 	 * Get a configuration property from the json object. Returns an optional
@@ -77,7 +77,7 @@ class Configurable {
 	 */
 	template <typename T>
 	void set(const std::string &name, T value) {
-		config_[name] = value;
+		(*config_)[name] = value;
 		_trigger(name);
 	}
 
@@ -96,8 +96,10 @@ class Configurable {
 	 */
 	void on(const std::string &prop, std::function<void(const config::Event&)>);
 
+	void patchPtr(nlohmann::json &newcfg) { config_ = &newcfg; }
+
 	protected:
-	nlohmann::json &config_;
+	nlohmann::json *config_;  // FIXME(Nick) Should be a reference but this can be invalidated...
 
 	private:
 	std::map<std::string, std::list<std::function<void(const config::Event&)>>> observers_; 
@@ -120,16 +122,17 @@ void Configurable::set<const std::string&>(const std::string &name, const std::s
 
 template <typename T>
 std::optional<T> ftl::Configurable::get(const std::string &name) {
-	if (!config_[name].is_null()) {
+	if (!config_->is_object() && !config_->is_null()) LOG(FATAL) << "Config is not an object";
+	if (!(*config_)[name].is_null()) {
 		try {
-			return config_[name].get<T>();
+			return (*config_)[name].get<T>();
 		} catch (...) {
 			return {};
 		}
-	} else if (config_["$ref"].is_string()) {
+	} else if ((*config_)["$ref"].is_string()) {
 		// TODO(Nick) Add # if missing
 		// TODO(Nick) Cache result of ref loopkup
-		std::string res_uri = config_["$ref"].get<std::string>()+"/"+name;
+		std::string res_uri = (*config_)["$ref"].get<std::string>()+"/"+name;
 		auto &r = ftl::config::resolve(res_uri);
 
 		DLOG(2) << "GET: " << res_uri << " = " << r;
@@ -137,7 +140,7 @@ std::optional<T> ftl::Configurable::get(const std::string &name) {
 		try {
 			return r.get<T>();
 		} catch (...) {
-			LOG(ERROR) << "Missing: " << config_["$id"].get<std::string>()+"/"+name;
+			LOG(ERROR) << "Missing: " << (*config_)["$id"].get<std::string>()+"/"+name;
 			return {};
 		}
 	} else {
diff --git a/components/common/cpp/include/ftl/configuration.hpp b/components/common/cpp/include/ftl/configuration.hpp
index db6cb1438..3333ed576 100644
--- a/components/common/cpp/include/ftl/configuration.hpp
+++ b/components/common/cpp/include/ftl/configuration.hpp
@@ -125,11 +125,14 @@ T *ftl::config::create(json_t &link, ARGS ...args) {
 
     ftl::Configurable *cfg = ftl::config::find(link["$id"].get<std::string>());
     if (!cfg) {
-       // try {
+        try {
             cfg = new T(link, args...);
-        //} catch(...) {
-       //     LOG(FATAL) << "Could not construct " << link;
-        //}
+        } catch(...) {
+            LOG(FATAL) << "Could not construct " << link;
+        }
+    } else {
+        // Make sure configurable has newest object pointer
+        cfg->patchPtr(link);
     }
 
     try {
diff --git a/components/common/cpp/include/nlohmann/json.hpp b/components/common/cpp/include/nlohmann/json.hpp
index 68b715f04..93a5e9a2a 100644
--- a/components/common/cpp/include/nlohmann/json.hpp
+++ b/components/common/cpp/include/nlohmann/json.hpp
@@ -1,3 +1,10 @@
+/*
+ * Modified by: Nicolas Pope
+ * Changes:
+ *  * Allow // comments
+ *  * Extend exception messages to include location info 
+ */
+
 /*
     __ _____ _____ _____
  __|  |   __|     |   | |  JSON for Modern C++
@@ -15592,7 +15599,7 @@ class basic_json
             return m_value.object->operator[](key);
         }
 
-        JSON_THROW(type_error::create(305, "cannot use operator[] with a string argument with " + std::string(type_name())));
+        JSON_THROW(type_error::create(305, "cannot use operator[] with a string argument with " + std::string(type_name()) + std::string(" [key = ") + key + std::string("]")));
     }
 
     /*!
diff --git a/components/common/cpp/src/configurable.cpp b/components/common/cpp/src/configurable.cpp
index 87f2a454f..7cea5adeb 100644
--- a/components/common/cpp/src/configurable.cpp
+++ b/components/common/cpp/src/configurable.cpp
@@ -9,12 +9,15 @@ using ftl::config::json_t;
 
 extern nlohmann::json null_json;
 
-Configurable::Configurable() : config_(null_json) {
+Configurable::Configurable() : config_(&null_json) {
 	ftl::config::registerConfigurable(this);
 }
 
-Configurable::Configurable(nlohmann::json &config) : config_(config) {
-	//if (config["uri"].is_string()) __changeURI(config["uri"].get<std::string>(), this);
+Configurable::Configurable(nlohmann::json &config) : config_(&config) {
+	if (!config.is_object()) {
+		LOG(FATAL) << "Configurable json is not an object: " << config;
+	}
+
 	ftl::config::registerConfigurable(this);
 }
 
diff --git a/components/net/cpp/include/ftl/net/universe.hpp b/components/net/cpp/include/ftl/net/universe.hpp
index 0c71db7fe..6afdd6107 100644
--- a/components/net/cpp/include/ftl/net/universe.hpp
+++ b/components/net/cpp/include/ftl/net/universe.hpp
@@ -204,7 +204,7 @@ class Universe : public ftl::Configurable {
 	bool active_;
 	ftl::UUID this_peer;
 	std::shared_mutex net_mutex_;
-	std::shared_mutex handler_mutex_;
+	std::recursive_mutex handler_mutex_;
 	fd_set sfderror_;
 	fd_set sfdread_;
 	std::vector<ftl::net::Listener*> listeners_;
diff --git a/components/net/cpp/src/universe.cpp b/components/net/cpp/src/universe.cpp
index 76b3e546a..488a2acde 100644
--- a/components/net/cpp/src/universe.cpp
+++ b/components/net/cpp/src/universe.cpp
@@ -436,7 +436,7 @@ void Universe::removeCallback(callback_t cbid) {
 }
 
 void Universe::_notifyConnect(Peer *p) {
-	SHARED_LOCK(handler_mutex_,lk);
+	UNIQUE_LOCK(handler_mutex_,lk);
 	peer_ids_[p->id()] = p;
 
 	for (auto &i : on_connect_) {
@@ -451,7 +451,7 @@ void Universe::_notifyConnect(Peer *p) {
 void Universe::_notifyDisconnect(Peer *p) {
 	// In all cases, should already be locked outside this function call
 	//unique_lock<mutex> lk(net_mutex_);
-	SHARED_LOCK(handler_mutex_,lk);
+	UNIQUE_LOCK(handler_mutex_,lk);
 	for (auto &i : on_disconnect_) {
 		try {
 			i.h(p);
diff --git a/components/rgbd-sources/include/ftl/rgbd/source.hpp b/components/rgbd-sources/include/ftl/rgbd/source.hpp
index d90385f50..c43525ae4 100644
--- a/components/rgbd-sources/include/ftl/rgbd/source.hpp
+++ b/components/rgbd-sources/include/ftl/rgbd/source.hpp
@@ -32,6 +32,16 @@ enum capability_t {
 	kCapDisparity	// Raw disparity is available
 };
 
+typedef unsigned int channel_t;
+
+static const unsigned int kChanLeft = 1;
+static const unsigned int kChanDepth = 2;
+static const unsigned int kChanRight = 3;
+static const unsigned int kChanDisparity = 4;
+static const unsigned int kChanDeviation = 5;
+
+static const unsigned int kChanOverlay1 = 100;
+
 /**
  * RGBD Generic data source configurable entity. This class hides the
  * internal implementation of an RGBD source by providing accessor functions
@@ -150,6 +160,14 @@ class Source : public ftl::Configurable {
 	 */
 	void reset();
 
+	void pause(bool);
+	bool isPaused() { return paused_; }
+
+	void bullet(bool);
+	bool isBullet() { return bullet_; }
+
+	bool thumbnail(cv::Mat &t);
+
 	ftl::net::Universe *getNet() const { return net_; }
 
 	std::string getURI() { return value("uri", std::string("")); }
@@ -166,6 +184,8 @@ class Source : public ftl::Configurable {
 	Eigen::Matrix4d pose_;
 	ftl::net::Universe *net_;
 	std::shared_mutex mutex_;
+	bool paused_;
+	bool bullet_;
 
 	detail::Source *_createImplementation();
 	detail::Source *_createFileImpl(const ftl::URI &uri);
diff --git a/components/rgbd-sources/src/net.cpp b/components/rgbd-sources/src/net.cpp
index b71d18e08..330fc907e 100644
--- a/components/rgbd-sources/src/net.cpp
+++ b/components/rgbd-sources/src/net.cpp
@@ -93,29 +93,11 @@ NetSource::~NetSource() {
 	host_->getNet()->removeCallback(h_);
 }
 
-void NetSource::_recv(const vector<unsigned char> &jpg, const vector<unsigned char> &d) {
-	cv::Mat tmp_rgb, tmp_depth;
-
-	// Decode in temporary buffers to prevent long locks
-	cv::imdecode(jpg, cv::IMREAD_COLOR, &tmp_rgb);
-	cv::imdecode(d, cv::IMREAD_UNCHANGED, &tmp_depth);
-
-	// Lock host to prevent grab
-	UNIQUE_LOCK(host_->mutex(),lk);
-	rgb_ = tmp_rgb;
-	tmp_depth.convertTo(depth_, CV_32FC1, 1.0f/(16.0f*100.0f));
-	N_--;
-	//lk.unlock();
-}
-
 void NetSource::_recvChunk(int frame, int chunk, bool delta, const vector<unsigned char> &jpg, const vector<unsigned char> &d) {
 	cv::Mat tmp_rgb, tmp_depth;
 
 	if (!active_) return;
 
-	//LOG(INFO) << "Received chunk " << (int)chunk;
-
-	//try {
 	// Decode in temporary buffers to prevent long locks
 	cv::imdecode(jpg, cv::IMREAD_COLOR, &tmp_rgb);
 	cv::imdecode(d, cv::IMREAD_UNCHANGED, &tmp_depth);
@@ -132,20 +114,11 @@ void NetSource::_recvChunk(int frame, int chunk, bool delta, const vector<unsign
 	
 	cv::Rect roi(cx,cy,chunk_width_,chunk_height_);
 	cv::Mat chunkRGB = rgb_(roi);
-	//cv::Mat ichunkDepth = idepth_(roi);
 	cv::Mat chunkDepth = depth_(roi);
 
 	tmp_rgb.copyTo(chunkRGB);
-	//tmp_depth.convertTo(tmp_depth, CV_16UC1);
-	//if (delta) ichunkDepth = tmp_depth - ichunkDepth;
-	//tmp_depth.copyTo(ichunkDepth);
 	tmp_depth.convertTo(chunkDepth, CV_32FC1, 1.0f/(16.0f*10.0f));
 	if (chunk == 0) N_--;
-	//lk.unlock();
-	//} catch(...) {
-	//	LOG(ERROR) << "Decode exception";
-	//	return;
-	//}
 }
 
 void NetSource::setPose(const Eigen::Matrix4d &pose) {
@@ -167,7 +140,6 @@ void NetSource::_updateURI() {
 	active_ = false;
 	auto uri = host_->get<string>("uri");
 
-	// TODO(Nick) If URI changes then must unbind + rebind.
 	if (uri_.size() > 0) {
 		host_->getNet()->unbind(uri_);
 	}
-- 
GitLab