diff --git a/applications/gui/src/main.cpp b/applications/gui/src/main.cpp
index ea45a23ce6c883e49f9f87340f6458723a6c893d..d070e3941a2622fdbf5dd43840647a0edf29668f 100644
--- a/applications/gui/src/main.cpp
+++ b/applications/gui/src/main.cpp
@@ -28,6 +28,113 @@ using ftl::rgbd::Source;
 	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_);
+	// Knuth's method 
+
+	n_++;
+	for (int i = 0; i < size_.width * size_.height; i++) {
+		float x = ((float*) in.data)[i];
+		if (x > 30.0f || x < 0.001) { 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 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 &in) {
+	// Knuth'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_) {
+		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 (x > 30.0 || x < 0.001) continue;
+
+			m = m + (x - m) / n;
+			S = S + (x - m) * (x - m_prev);
+		}
+	}
+
+	mat_S.copyTo(in);
+	cv::sqrt(in, in);
+}
+
+void StatisticsImageNSamples::getMean(cv::Mat &in) {}
 
 namespace {
     constexpr char const *const defaultImageViewVertexShader =
@@ -112,6 +219,17 @@ namespace {
 }
 
 
+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 GUI") {
@@ -123,10 +241,11 @@ class FTLApplication : public nanogui::Screen {
 
 		//src_ = nullptr;
 		eye_ = Eigen::Vector3f(0.0f, 0.0f, 0.0f);
-		centre_ = Eigen::Vector3f(0.0f, 0.0f, -4.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.4f;
+		//lookPoint_ = Eigen::Vector3f(0.0f,0.0f,-4.0f);
+		lerpSpeed_ = 0.999f;
 		depth_ = false;
 		ftime_ = (float)glfwGetTime();
 
@@ -155,6 +274,18 @@ class FTLApplication : public nanogui::Screen {
 		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;
@@ -181,7 +312,7 @@ class FTLApplication : public nanogui::Screen {
 				
 				camPos *= -1.0f;
 				Eigen::Vector4f worldPos =  src_->getPose() * camPos;
-				lookPoint_ = Eigen::Vector3f(worldPos[0],worldPos[1],worldPos[2]);
+				//lookPoint_ = Eigen::Vector3f(worldPos[0],worldPos[1],worldPos[2]);
 				LOG(INFO) << "Depth at click = " << -camPos[2];
 				return true;
 			}
@@ -190,18 +321,25 @@ class FTLApplication : public nanogui::Screen {
 	}
 
 	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;
+			//LOG(INFO) << "Key press " << key << " - " << action;
 			if (key == 263 || key == 262) {
-				// TODO Should rotate around lookAt object, but requires correct depth
-				Eigen::Quaternion<float> q;  q = Eigen::AngleAxis<float>((key == 262) ? 0.01f : -0.01f, up_);
-				eye_ = (q * (eye_ - centre_)) + centre_;
+				float scalar = (key == 263) ? -0.1f : 0.1f;
+				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 scalar = (key == 264) ? 0.99f : 1.01f;
-				eye_ = ((eye_ - centre_) * scalar) + centre_;
+				float scalar = (key == 264) ? -0.1f : 0.1f;
+				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 scalar = (key == 266) ? -0.1f : 0.1f;
+				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(!swindow_->visible());
@@ -211,6 +349,8 @@ class FTLApplication : public nanogui::Screen {
 		}
 	}
 
+	StatisticsImageNSamples *stats_ = nullptr;
+
 	virtual void draw(NVGcontext *ctx) {
 		using namespace Eigen;
 
@@ -218,33 +358,60 @@ class FTLApplication : public nanogui::Screen {
 		imageSize = {0, 0};
 
 		float now = (float)glfwGetTime();
-		float delta = now - ftime_;
+		delta_ = now - ftime_;
 		ftime_ = now;
 
 		if (src_) {
 			cv::Mat rgb, depth;
-			centre_ += (lookPoint_ - centre_) * (lerpSpeed_ * delta);
-			Eigen::Matrix4f viewPose = lookAt<float>(eye_,centre_,up_).inverse();
+
+			// 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);
 			src_->grab();
 			src_->getFrames(rgb, depth);
 
-			if (swindow_->getDepth()) {
-				if (depth.rows > 0) {
+			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);
-					cv::Mat idepth;
-					depth.convertTo(idepth, CV_8U, 255.0f / 10.0f);  // TODO(nick)
-    				applyColorMap(idepth, idepth, cv::COLORMAP_JET);
-					texture_.update(idepth);
+					depth.convertTo(tmp, CV_8U, 255.0f / 10.0f);
+					applyColorMap(tmp, tmp, cv::COLORMAP_JET);
+					texture_.update(tmp);
 					mImageID = texture_.texture();
-				}
-			} else {
-				if (rgb.rows > 0) {
+					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();
-				}
 			}
 		}
 
@@ -288,12 +455,14 @@ class FTLApplication : public nanogui::Screen {
 	//Source *src_;
 	GLTexture texture_;
 	Eigen::Vector3f eye_;
-	Eigen::Vector3f centre_;
+	Eigen::Vector4f neye_;
+	Eigen::Vector3f orientation_;
 	Eigen::Vector3f up_;
-	Eigen::Vector3f lookPoint_;
+	//Eigen::Vector3f lookPoint_;
 	float lerpSpeed_;
 	bool depth_;
 	float ftime_;
+	float delta_;
 	Eigen::Vector2f imageSize;
 };
 
diff --git a/applications/gui/src/src_window.cpp b/applications/gui/src/src_window.cpp
index 997aaaa645852a7a9c893a9c555074fff6cc22a4..ad61ccccb14555418106b9e408dc48e3daeccee2 100644
--- a/applications/gui/src/src_window.cpp
+++ b/applications/gui/src/src_window.cpp
@@ -1,6 +1,8 @@
 #include "src_window.hpp"
 
 #include <nanogui/imageview.h>
+#include <nanogui/textbox.h>
+#include <nanogui/slider.h>
 #include <nanogui/combobox.h>
 #include <nanogui/label.h>
 #include <nanogui/opengl.h>
@@ -77,37 +79,43 @@ SourceWindow::SourceWindow(nanogui::Widget *parent, ftl::ctrl::Master *ctrl)
 	setLayout(new nanogui::GroupLayout());
 
 	using namespace nanogui;
-
-	depth_ = false;
-    src_ = ftl::create<Source>(ctrl->getRoot(), "source", ctrl->getNet());
+	
+	mode_ = Mode::rgb;
+	src_ = ftl::create<Source>(ctrl->getRoot(), "source", ctrl->getNet());
 
 	//Widget *tools = new Widget(this);
-    //    tools->setLayout(new BoxLayout(Orientation::Horizontal,
-    //                                   Alignment::Middle, 0, 6));
+	//    tools->setLayout(new BoxLayout(Orientation::Horizontal,
+	//                                   Alignment::Middle, 0, 6));
 
-    new Label(this, "Select source","sans-bold");
-    available_ = ctrl->getNet()->findAll<string>("list_streams");
-    auto select = new ComboBox(this, available_);
-    select->setCallback([this,select](int ix) {
-        LOG(INFO) << "Change source: " << ix;
-        src_->set("uri", available_[ix]);
-    });
+	new Label(this, "Select source","sans-bold");
+	available_ = ctrl->getNet()->findAll<string>("list_streams");
+	auto select = new ComboBox(this, available_);
+	select->setCallback([this,select](int 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");
-		 select->setItems(available_);
+		available_ = ctrl_->getNet()->findAll<string>("list_streams");
+		select->setItems(available_);
 	});
 
-	auto depth = new Button(this, "Depth");
-	depth->setFlags(Button::ToggleButton);
-	depth->setChangeCallback([this](bool state) {
-		//image_->setDepth(state);
-		depth_ = state;
-	});
+	auto button_rgb = new Button(this, "RGB (left)");
+	button_rgb->setFlags(Button::RadioButton);
+	button_rgb->setPushed(true);
+	button_rgb->setChangeCallback([this](bool state) { mode_ = Mode::rgb; });
+
+	auto button_depth = new Button(this, "Depth");
+	button_depth->setFlags(Button::RadioButton);
+	button_depth->setChangeCallback([this](bool state) { mode_ = Mode::depth; });
+
+	auto button_stddev = new Button(this, "Standard Deviation (25 frames)");
+	button_stddev->setFlags(Button::RadioButton);
+	button_stddev->setChangeCallback([this](bool state) { mode_ = Mode::stddev; });
 
 #ifdef HAVE_LIBARCHIVE
-	auto snapshot = new Button(this, "Snapshot");
-	snapshot->setCallback([this] {
+	auto button_snapshot = new Button(this, "Snapshot");
+	button_snapshot->setCallback([this] {
 		try {
 			char timestamp[18];
 			std::time_t t=std::time(NULL);
diff --git a/applications/gui/src/src_window.hpp b/applications/gui/src/src_window.hpp
index 6258267c62fcec81c688a89a9361c4c58d166938..e7aa192133eebd7ec306c41be1d84d30a8861538 100644
--- a/applications/gui/src/src_window.hpp
+++ b/applications/gui/src/src_window.hpp
@@ -18,16 +18,18 @@ namespace gui {
  */
 class SourceWindow : public nanogui::Window {
 	public:
+	enum class Mode { rgb, depth, stddev };
 	SourceWindow(nanogui::Widget *parent, ftl::ctrl::Master *ctrl);
 	~SourceWindow();
 
 	ftl::rgbd::Source *getSource() const { return src_; }
-	bool getDepth() const { return depth_; }
+	bool getDepth() const { return mode_ == Mode::depth; }
+	Mode getMode() { return mode_; }
 
 	private:
 	ftl::ctrl::Master *ctrl_;
 	ftl::rgbd::Source *src_;
-	bool depth_;
+	Mode mode_;
 	VirtualCameraView *image_;
 	std::vector<std::string> available_;