diff --git a/Doxyfile b/Doxyfile
index 47cb13e46efd68d84723dc80e9d378cdde662d4c..42eb33cc5ec62e06256976f4fccad1db9c757fae 100644
--- a/Doxyfile
+++ b/Doxyfile
@@ -875,7 +875,8 @@ RECURSIVE              = YES
 
 EXCLUDE                = "components/common/cpp/include/nlohmann/json.hpp" \
 						 "components/common/cpp/include/loguru.hpp" \
-						 "components/common/cpp/include/ctpl_stl.h"
+						 "components/common/cpp/include/ctpl_stl.h" \
+						 "build/"
 
 # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
 # directories that are symbolic links (a Unix file system feature) are excluded
diff --git a/applications/gui/src/config_window.cpp b/applications/gui/src/config_window.cpp
index 322f817ae264386ab3a54bfddc72c7d5f588fc36..b3c5ef874961d773f12981c0eee93fd8fd67fa3c 100644
--- a/applications/gui/src/config_window.cpp
+++ b/applications/gui/src/config_window.cpp
@@ -6,6 +6,7 @@
 #include <nanogui/entypo.h>
 #include <nanogui/formhelper.h>
 #include <nanogui/vscrollpanel.h>
+#include <nanogui/opengl.h>
 
 #include <vector>
 #include <string>
@@ -15,6 +16,67 @@ using std::string;
 using std::vector;
 using ftl::config::json_t;
 
+class SearchBox : public nanogui::TextBox {
+private:
+	std::vector<std::string> configurables_;
+	Widget *buttons_;
+	std::string previous;
+
+	void _setVisible(const std::string &str) {
+		// Check whether the search string has changed to prevent
+		// unnecessary searching.
+		if (str != previous) {
+			for (int i = configurables_.size()-1; i >= 0; --i) {
+				if (configurables_[i].find(mValueTemp) != std::string::npos) {
+					buttons_->childAt(i)->setVisible(true);
+				} else {
+					buttons_->childAt(i)->setVisible(false);
+				}
+			}
+			previous = str;
+		}
+	}
+
+public:
+	SearchBox(Widget *parent, std::vector<std::string> &configurables) : nanogui::TextBox(parent, ""), configurables_(configurables) {
+		setAlignment(TextBox::Alignment::Left);
+		setEditable(true);
+		setPlaceholder("Search");
+	}
+
+	~SearchBox() {
+	}
+
+	bool keyboardEvent(int key, int scancode, int action, int modifier) {
+		TextBox::keyboardEvent(key, scancode, action, modifier);
+		_setVisible(mValueTemp);
+		return true;
+	}
+
+	void setButtons(Widget *buttons) {
+		buttons_ = buttons;
+	}
+};
+
+static std::string titleForURI(const ftl::URI &uri) {
+	auto *cfg = ftl::config::find(uri.getBaseURI());
+	if (cfg && cfg->get<std::string>("title")) {
+		return *cfg->get<std::string>("title");
+	} else if (uri.getPath().size() > 0) {
+		return uri.getPathSegment(-1);
+	} else {
+		return uri.getHost();
+	}
+}
+
+static std::string genPadding(const std::string &str, size_t count) {
+	std::string res = "";
+	for (size_t i=0; i<count; ++i) {
+		res += str;
+	}
+	return res;
+}
+
 ConfigWindow::ConfigWindow(nanogui::Widget *parent, ftl::ctrl::Master *ctrl)
 		: nanogui::Window(parent, "Settings"), ctrl_(ctrl) {
 	using namespace nanogui;
@@ -23,17 +85,52 @@ ConfigWindow::ConfigWindow(nanogui::Widget *parent, ftl::ctrl::Master *ctrl)
 	setPosition(Vector2i(parent->width()/2.0f - 100.0f, parent->height()/2.0f - 100.0f));
 	//setModal(true);
 
-	configurables_ = ftl::config::list();
+	auto configurables = ftl::config::list();
+	const auto size = configurables.size();
 
 	new Label(this, "Select Configurable","sans-bold");
 
+	auto searchBox = new SearchBox(this, configurables);
+
 	auto vscroll = new VScrollPanel(this);
 	vscroll->setFixedHeight(300);
-	Widget *buttons = new Widget(vscroll);
+	auto buttons = new Widget(vscroll);
 	buttons->setLayout(new BoxLayout(Orientation::Vertical, Alignment::Fill));
 
-	for (auto c : configurables_) {
-		auto itembutton = new Button(buttons, c);
+	searchBox->setButtons(buttons);
+
+	std::vector<std::string> configurable_titles(size);
+	for (int i = 0; i < size; ++i) {
+		ftl::URI uri(configurables[i]);
+		std::string label = uri.getFragment();
+
+		size_t pos = label.find_last_of("/");
+		if (pos != std::string::npos) label = label.substr(pos+1);
+
+		std::string parentName = configurables[i];
+		size_t pos2 = parentName.find_last_of("/");
+		if (pos2 != std::string::npos) parentName = parentName.substr(0,pos2);
+
+		// FIXME: Does not indicated parent indentation ... needs sorting?
+
+		if (i > 0 && parentName == configurables[i-1]) {
+			ftl::URI uri(configurables[i-1]);
+			configurable_titles[i-1] = std::string("[") + titleForURI(uri) + std::string("] ") + uri.getFragment();
+
+			auto *prev = dynamic_cast<Button*>(buttons->childAt(buttons->childCount()-1));
+			prev->setCaption(configurable_titles[i-1]);
+			prev->setBackgroundColor(nanogui::Color(0.3f,0.3f,0.3f,1.0f));
+			prev->setTextColor(nanogui::Color(1.0f,1.0f,1.0f,1.0f));
+			prev->setIconPosition(Button::IconPosition::Left);
+			prev->setIcon(ENTYPO_ICON_FOLDER);
+		}
+
+		configurable_titles[i] = label;
+
+		auto itembutton = new nanogui::Button(buttons, configurable_titles[i]);
+		std::string c = configurables[i];
+		itembutton->setTooltip(c);
+		itembutton->setBackgroundColor(nanogui::Color(0.9f,0.9f,0.9f,0.9f));
 		itembutton->setCallback([this,c]() {
 			LOG(INFO) << "Change configurable: " << c;
 			_buildForm(c);
@@ -136,3 +233,4 @@ void ConfigWindow::_buildForm(const std::string &suri) {
 bool ConfigWindow::exists(const std::string &uri) {
 	return ftl::config::find(uri) != nullptr;
 }
+
diff --git a/applications/gui/src/config_window.hpp b/applications/gui/src/config_window.hpp
index a0fe74155fe5320f983871d8c9237c7571625670..a7acd117116553f3e902bdc6640d4f67e71b1f30 100644
--- a/applications/gui/src/config_window.hpp
+++ b/applications/gui/src/config_window.hpp
@@ -2,10 +2,10 @@
 #define _FTL_GUI_CFGWINDOW_HPP_
 
 #include <nanogui/window.h>
+#include <nanogui/formhelper.h>
+
 #include <ftl/master.hpp>
 #include <ftl/uuid.hpp>
-
-#include <nanogui/formhelper.h>
 #include <ftl/net_configurable.hpp>
 
 namespace ftl {
@@ -21,7 +21,6 @@ class ConfigWindow : public nanogui::Window {
 
 	private:
 	ftl::ctrl::Master *ctrl_;
-	std::vector<std::string> configurables_;
 	
 	void _buildForm(const std::string &uri);
 	void _addElements(nanogui::FormHelper *form, const std::string &suri);
diff --git a/applications/player/src/main.cpp b/applications/player/src/main.cpp
index 2741cac2ef832f3cc60073f7320d443ea9620c9d..6fd7c54ce648a683d46d2f08171e2ad5de040ee7 100644
--- a/applications/player/src/main.cpp
+++ b/applications/player/src/main.cpp
@@ -11,22 +11,35 @@
 
 #include <Eigen/Eigen>
 
+const static std::string help[] = {
+	"Esc", "close",
+	"0-9", "change source",
+	"D",   "toggle depth",
+	"S",   "save screenshot",
+};
+
+std::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 std::string(timestamp);
+}
+
 using ftl::codecs::codec_t;
 using ftl::codecs::Channel;
 
-static ftl::codecs::Decoder *decoder;
-
+static std::map<Channel, ftl::codecs::Decoder*> decoders;
 
-static void createDecoder(const ftl::codecs::Packet &pkt) {
-	if (decoder) {
-		if (!decoder->accepts(pkt)) {
-			ftl::codecs::free(decoder);
+static void createDecoder(const Channel channel, const ftl::codecs::Packet &pkt) {
+	if (decoders[channel]) {
+		if (!decoders[channel]->accepts(pkt)) {
+			ftl::codecs::free(decoders[channel]);
 		} else {
 			return;
 		}
 	}
 
-	decoder = ftl::codecs::allocateDecoder(pkt);
+	decoders[channel] = ftl::codecs::allocateDecoder(pkt);
 }
 
 static void visualizeDepthMap(	const cv::Mat &depth, cv::Mat &out,
@@ -56,28 +69,34 @@ static std::string nameForCodec(ftl::codecs::codec_t c) {
 }
 
 int main(int argc, char **argv) {
-    std::string filename(argv[1]);
-    LOG(INFO) << "Playing: " << filename;
+	std::string filename(argv[1]);
+	LOG(INFO) << "Playing: " << filename;
 
 	auto root = ftl::configure(argc, argv, "player_default");
 
 	std::ifstream f;
-    f.open(filename);
-    if (!f.is_open()) LOG(ERROR) << "Could not open file";
+	f.open(filename);
+	if (!f.is_open()) LOG(ERROR) << "Could not open file";
 
-    ftl::codecs::Reader r(f);
-    if (!r.begin()) LOG(ERROR) << "Bad ftl file";
+	ftl::codecs::Reader r(f);
+	if (!r.begin()) LOG(ERROR) << "Bad ftl file";
 
-    LOG(INFO) << "Playing...";
+	LOG(INFO) << "Playing...";
 
-    int current_stream = 0;
-    int current_channel = 0;
+	int current_stream = 0;
+	int current_channel = 0;
 
 	int stream_mask = 0;
+
+	static volatile bool screenshot;
+	static int64_t screenshot_ts;
+	static cv::Mat screenshot_color;
+	static cv::Mat screenshot_depth;
+
 	std::vector<std::bitset<128>> channel_mask;
 
 	ftl::timer::add(ftl::timer::kTimerMain, [&current_stream,&current_channel,&r,&stream_mask,&channel_mask](int64_t ts) {
-		bool res = r.read(ts, [&current_stream,&current_channel,&r,&stream_mask,&channel_mask](const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) {
+		bool res = r.read(ts, [ts, &current_stream,&current_channel,&r,&stream_mask,&channel_mask](const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) {
 			if (!(stream_mask & (1 << spkt.streamID))) {
 				stream_mask |= 1 << spkt.streamID;
 				LOG(INFO) << " - Stream found (" << (int)spkt.streamID << ")";
@@ -95,56 +114,95 @@ int main(int argc, char **argv) {
 				LOG(INFO) << "     - Blocks = " << (int)pkt.block_total;
 			}
 
-			if (spkt.streamID == current_stream) {
+			if (spkt.streamID != current_stream) { return; }
 
-				if (pkt.codec == codec_t::POSE) {
-					Eigen::Matrix4d p = Eigen::Map<Eigen::Matrix4d>((double*)pkt.data.data());
-					LOG(INFO) << "Have pose: " << p;
-					return;
-				}
+			if (pkt.codec == codec_t::POSE) {
+				Eigen::Matrix4d p = Eigen::Map<Eigen::Matrix4d>((double*)pkt.data.data());
+				LOG(INFO) << "Have pose: " << p;
+				return;
+			}
 
-				if (pkt.codec == codec_t::CALIBRATION) {
-					ftl::rgbd::Camera *camera = (ftl::rgbd::Camera*)pkt.data.data();
-					LOG(INFO) << "Have calibration: " << camera->fx;
-					return;
-				}
-				
-				if (spkt.channel != static_cast<ftl::codecs::Channel>(current_channel)) return;
+			if (pkt.codec == codec_t::CALIBRATION) {
+				ftl::rgbd::Camera *camera = (ftl::rgbd::Camera*)pkt.data.data();
+				LOG(INFO) << "Have calibration: " << camera->fx;
+				return;
+			}
+			
+			auto channel = static_cast<ftl::codecs::Channel>(current_channel);
+			if (pkt.codec == ftl::codecs::codec_t::MSGPACK) { return; }
+			//LOG(INFO) << "Reading packet: (" << (int)spkt.streamID << "," << (int)spkt.channel << ") " << (int)pkt.codec << ", " << (int)pkt.definition;
+
+			cv::cuda::GpuMat gframe(cv::Size(ftl::codecs::getWidth(pkt.definition),ftl::codecs::getHeight(pkt.definition)), (spkt.channel == Channel::Depth) ? CV_32F : CV_8UC3);
+			cv::Mat frame;
+			createDecoder(spkt.channel, pkt);
+
+			try {
+				decoders[spkt.channel]->decode(pkt, gframe);
+				gframe.download(frame);
+			} catch (std::exception &e) {
+				LOG(INFO) << "Decoder exception: " << e.what();
+			}
 
-				//LOG(INFO) << "Reading packet: (" << (int)spkt.streamID << "," << (int)spkt.channel << ") " << (int)pkt.codec << ", " << (int)pkt.definition;
+			if (screenshot) {
+				if (!screenshot_depth.empty() && !screenshot_color.empty()) {
+					std::string fname = time_now_string();
+					LOG(INFO) << "Screenshot saved: " << fname;
+					cv::imwrite(fname + "-depth.tiff", screenshot_depth);
+					cv::imwrite(fname + "-color.tiff", screenshot_color);
+					screenshot = false;
+					screenshot_color = cv::Mat();
+					screenshot_depth = cv::Mat();
+					screenshot_ts = 0;
+				}
+				else {
+					if (screenshot_ts != ts) {
+						screenshot_ts = ts;
+						screenshot_color = cv::Mat();
+						screenshot_depth = cv::Mat();
+					}
+					if (spkt.channel == Channel::Colour) { 
+						frame.copyTo(screenshot_color);
+					}
+					else if (spkt.channel == Channel::Depth) { 
+						frame.copyTo(screenshot_depth);
+					}
+				}
+			}
 
-				cv::cuda::GpuMat gframe(cv::Size(ftl::codecs::getWidth(pkt.definition),ftl::codecs::getHeight(pkt.definition)), (spkt.channel == Channel::Depth) ? CV_32F : CV_8UC3);
-				cv::Mat frame;
-				createDecoder(pkt);
+			if (spkt.channel != channel) return;
 
-				try {
-					decoder->decode(pkt, gframe);
-					gframe.download(frame);
-				} catch (std::exception &e) {
-					LOG(INFO) << "Decoder exception: " << e.what();
+			if (!frame.empty()) {
+				if (spkt.channel == Channel::Depth) {
+					visualizeDepthMap(frame, frame, 8.0f);
 				}
+				double time = (double)(spkt.timestamp - r.getStartTime()) / 1000.0;
+				cv::putText(frame, std::string("Time: ") + std::to_string(time) + std::string("s"), cv::Point(10,20), cv::FONT_HERSHEY_PLAIN, 1, cv::Scalar(0,0,255));
 
-				if (!frame.empty()) {
-					if (spkt.channel == Channel::Depth) {
-						visualizeDepthMap(frame, frame, 8.0f);
-					}
-					double time = (double)(spkt.timestamp - r.getStartTime()) / 1000.0;
-					cv::putText(frame, std::string("Time: ") + std::to_string(time) + std::string("s"), cv::Point(10,20), cv::FONT_HERSHEY_PLAIN, 1, cv::Scalar(0,0,255));
-					cv::imshow("Player", frame);
-				} else {
-					frame.create(cv::Size(600,600), CV_8UC3);
-					cv::imshow("Player", frame);
+				// hotkey help text
+				for (int i = 0; i < std::size(help); i += 2) {
+					cv::putText(frame, help[i], cv::Point(10, 40+(i/2)*14), cv::FONT_HERSHEY_PLAIN, 1, cv::Scalar(64,64,255));
+					cv::putText(frame, help[i+1], cv::Point(50, 40+(i/2)*14), cv::FONT_HERSHEY_PLAIN, 1, cv::Scalar(64,64,255));
 				}
-				int key = cv::waitKey(1);
-				if (key >= 48 && key <= 57) {
-					current_stream = key - 48;
-				} else if (key == 'd') {
-					current_channel = (current_channel == 0) ? 1 : 0;
-				} else if (key == 'r') {
-					current_channel = (current_channel == 0) ? 2 : 0;
-				} else if (key == 27) {
-					ftl::timer::stop(false);
+
+				cv::imshow("Player", frame);
+			} else {
+				frame.create(cv::Size(600,600), CV_8UC3);
+				cv::imshow("Player", frame);
+			}
+			int key = cv::waitKey(1);
+			if (key >= 48 && key <= 57) {
+				int new_stream = key - 48;
+				if ((0 <= new_stream) && (new_stream < channel_mask.size())) {
+					current_stream = new_stream;
 				}
+			} else if (key == 'd') {
+				current_channel = (current_channel == 0) ? 1 : 0;
+			} else if (key == 'r') {
+				current_channel = (current_channel == 0) ? 2 : 0;
+			} else if (key == 27) {
+				ftl::timer::stop(false);
+			} else if (key == 115) {
+				screenshot = true;
 			}
 		});
 		if (!res) ftl::timer::stop(false);
@@ -153,7 +211,7 @@ int main(int argc, char **argv) {
 
 	ftl::timer::start(true);
 
-    r.end();
+	r.end();
 
 	ftl::running = false;
 	return 0;
diff --git a/applications/reconstruct/src/main.cpp b/applications/reconstruct/src/main.cpp
index c3dc37e325bd873bec4e318bafa5ac72a993863b..5cce7a5a236699a58e74a1044dfef35a008bf72e 100644
--- a/applications/reconstruct/src/main.cpp
+++ b/applications/reconstruct/src/main.cpp
@@ -227,6 +227,13 @@ static void run(ftl::Configurable *root) {
 	});
 	stream->add(vs);
 
+	for (auto c : ftl::config::getChildren(root->getID())) {
+		LOG(INFO) << "Tagging configurable: " << c->getID();
+		auto tags = c->value<std::vector<std::string>>("tags", nlohmann::json::array({}));
+		tags.push_back("reconstruction");
+		c->set("tags", tags);
+	}
+
 	// ---- Recording code -----------------------------------------------------
 	std::ofstream fileout;
 	ftl::codecs::Writer writer(fileout);
diff --git a/components/common/cpp/include/ftl/configuration.hpp b/components/common/cpp/include/ftl/configuration.hpp
index 18aaf89f9433911dd71ec3d3519ff2125317e78e..ed7eee95d0e0692242ca5dafd22ef142890561f5 100644
--- a/components/common/cpp/include/ftl/configuration.hpp
+++ b/components/common/cpp/include/ftl/configuration.hpp
@@ -83,6 +83,13 @@ const std::vector<Configurable *> &findByTag(const std::string &tag);
 
 std::vector<std::string> list();
 
+/**
+ * Recursively get all children of a configurable. The given configurable is
+ * also included in the vector, unless it is null,
+ * in which case an empty vector is returned.
+ */
+const std::vector<Configurable *> getChildren(const std::string &uri);
+
 /**
  * Adds a Configurable instance to the database of instances so that it can
  * then be resolved using find().
diff --git a/components/common/cpp/src/configuration.cpp b/components/common/cpp/src/configuration.cpp
index 9fd9d3081f18781ce0474438a3e1829d3b082b33..c53f50c78decd870c86228de2634588bdb313374 100644
--- a/components/common/cpp/src/configuration.cpp
+++ b/components/common/cpp/src/configuration.cpp
@@ -212,6 +212,17 @@ std::vector<std::string> ftl::config::list() {
 	return r;
 }
 
+const std::vector<Configurable *> ftl::config::getChildren(const string &uri) {
+	std::vector<Configurable *> children;
+	for (const auto &[curi, c] : config_instance) {
+		auto mismatch = std::mismatch(uri.begin(), uri.end(), curi.begin());
+		if (mismatch.first == uri.end()) {
+			children.push_back(c);
+		}
+	}
+	return children;
+}
+
 void ftl::config::registerConfigurable(ftl::Configurable *cfg) {
 	auto uri = cfg->get<string>("$id");
 	if (!uri) {
diff --git a/components/operators/src/colours.cpp b/components/operators/src/colours.cpp
index 955bfed58b1ca26c55734eef648eaaec6286409b..267bcb8ca7b9dfa7f748fa3d2d3482bae5c5abe0 100644
--- a/components/operators/src/colours.cpp
+++ b/components/operators/src/colours.cpp
@@ -54,7 +54,7 @@ bool ColourChannels::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgb
 		if (in.hasChannel(Channel::Right)) {
 			auto &right = in.get<cv::cuda::GpuMat>(Channel::Right);
 			if (depth.size() != right.size()) {
-				cv::cuda::resize(right, rbuf_, depth.size(), 0.0, 0.0, cv::INTER_CUBIC, cvstream);
+				cv::cuda::resize(right, rbuf_, depth.size(), 0.0, 0.0, cv::INTER_LINEAR, cvstream);
 				cv::cuda::swap(right, rbuf_);
 			}
 		}
diff --git a/components/operators/src/depth.cpp b/components/operators/src/depth.cpp
index 3d332d6ae397c479e3864967cb47759678dbf7b9..7122513b1e6e90ea98533972391481dffc2f3e7c 100644
--- a/components/operators/src/depth.cpp
+++ b/components/operators/src/depth.cpp
@@ -23,8 +23,8 @@ void DepthChannel::_createPipeline() {
 	if (pipe_ != nullptr) return;
 
 	pipe_ = ftl::config::create<ftl::operators::Graph>(config(), "depth");
-	depth_size_ = cv::Size(	pipe_->value("width", 1280),
-							pipe_->value("height", 720));
+	depth_size_ = cv::Size(	config()->value("width", 1280),
+							config()->value("height", 720));
 
 	pipe_->append<ftl::operators::ColourChannels>("colour");  // Convert BGR to BGRA
 	pipe_->append<ftl::operators::FixstarsSGM>("algorithm");
@@ -52,7 +52,7 @@ bool DepthChannel::apply(ftl::rgbd::FrameSet &in, ftl::rgbd::FrameSet &out, cuda
 			cv::cuda::GpuMat& left = f.get<cv::cuda::GpuMat>(Channel::Left);
 			cv::cuda::GpuMat& right = f.get<cv::cuda::GpuMat>(Channel::Right);
 			cv::cuda::GpuMat& depth = f.create<cv::cuda::GpuMat>(Channel::Depth);
-			depth.create(left.size(), CV_32FC1);
+			depth.create(depth_size_, CV_32FC1);
 
 			if (left.empty() || right.empty()) continue;
 
diff --git a/components/rgbd-sources/src/sources/stereovideo/local.cpp b/components/rgbd-sources/src/sources/stereovideo/local.cpp
index 03b80ff52f60c95d374cfee289c0b3880bb766bd..f4e11d6debca52b9049fba491c06a9cbd266fb0d 100644
--- a/components/rgbd-sources/src/sources/stereovideo/local.cpp
+++ b/components/rgbd-sources/src/sources/stereovideo/local.cpp
@@ -71,14 +71,19 @@ LocalSource::LocalSource(nlohmann::json &config)
 		stereo_ = true;
 	}
 
+	dwidth_ = value("depth_width", width_);
+	dheight_ = value("depth_height", height_);
+
 	// 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);
+	left_hm_ = cv::cuda::HostMem(dheight_, dwidth_, CV_8UC3);
+	right_hm_ = cv::cuda::HostMem(dheight_, dwidth_, CV_8UC3);
+	hres_hm_ = cv::cuda::HostMem(height_, width_, CV_8UC3);
 }
 
 LocalSource::LocalSource(nlohmann::json &config, const string &vid)
 	:	Configurable(config), timestamp_(0.0) {
-
+	LOG(FATAL) << "Stereo video file sources no longer supported";
+ /*
 	//flip_ = value("flip", false);
 	//flip_v_ = value("flip_vert", false);
 	nostereo_ = value("nostereo", false);
@@ -119,11 +124,16 @@ LocalSource::LocalSource(nlohmann::json &config, const string &vid)
 		stereo_ = false;
 	}
 
+	dwidth_ = value("depth_width", width_);
+	dheight_ = value("depth_height", height_);
+
 	// 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);
+	left_hm_ = cv::cuda::HostMem(dheight_, dwidth_, CV_8UC3);
+	right_hm_ = cv::cuda::HostMem(dheight_, dwidth_, CV_8UC3);
+	hres_hm_ = cv::cuda::HostMem(height_, width_, CV_8UC3);
 
 	//tps_ = 1.0 / value("max_fps", 25.0);
+	*/
 }
 
 /*bool LocalSource::left(cv::Mat &l) {
@@ -225,26 +235,32 @@ bool LocalSource::grab() {
 	return true;
 }
 
-bool LocalSource::get(cv::cuda::GpuMat &l_out, cv::cuda::GpuMat &r_out, Calibrate *c, cv::cuda::Stream &stream) {
-	Mat l, r;
+bool LocalSource::get(cv::cuda::GpuMat &l_out, cv::cuda::GpuMat &r_out, cv::cuda::GpuMat &hres_out, Calibrate *c, cv::cuda::Stream &stream) {
+	Mat l, r ,hres;
 
 	// Use page locked memory
 	l = left_hm_.createMatHeader();
 	r = right_hm_.createMatHeader();
+	hres = hres_hm_.createMatHeader();
+
+	Mat &lfull = (!hasHigherRes()) ? l : hres;
+	Mat &rfull = (!hasHigherRes()) ? r : rtmp_;
 
 	if (!camera_a_) return false;
 
 	if (camera_b_ || !stereo_) {
-		if (!camera_a_->retrieve(l)) {
+		// TODO: Use threads here?
+		if (!camera_a_->retrieve(lfull)) {
 			LOG(ERROR) << "Unable to read frame from camera A";
 			return false;
 		}
-		if (camera_b_ && !camera_b_->retrieve(r)) {
+		if (camera_b_ && !camera_b_->retrieve(rfull)) {
 			LOG(ERROR) << "Unable to read frame from camera B";
 			return false;
 		}
 	} else {
-		Mat frame;
+		LOG(FATAL) << "Stereo video no longer supported";
+		/*Mat frame;
 		if (!camera_a_->retrieve(frame)) {
 			LOG(ERROR) << "Unable to read frame from video";
 			return false;
@@ -257,7 +273,7 @@ bool LocalSource::get(cv::cuda::GpuMat &l_out, cv::cuda::GpuMat &r_out, Calibrat
 		//} 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) {
@@ -283,7 +299,18 @@ bool LocalSource::get(cv::cuda::GpuMat &l_out, cv::cuda::GpuMat &r_out, Calibrat
 		r = tr;
 	}*/
 
-	c->rectifyStereo(l, r);
+	c->rectifyStereo(lfull, rfull);
+
+	// Need to resize
+	if (hasHigherRes()) {
+		// TODO: Use threads?
+		cv::resize(lfull, l, l.size(), 0.0, 0.0, cv::INTER_CUBIC);
+		cv::resize(rfull, r, r.size(), 0.0, 0.0, cv::INTER_CUBIC);
+		hres_out.upload(hres, stream);
+		//LOG(INFO) << "Early Resize: " << l.size() << " from " << lfull.size();
+	} else {
+		hres_out = cv::cuda::GpuMat();
+	}
 
 	l_out.upload(l, stream);
 	r_out.upload(r, stream);
diff --git a/components/rgbd-sources/src/sources/stereovideo/local.hpp b/components/rgbd-sources/src/sources/stereovideo/local.hpp
index 9f21f5cf79b86f7cdee9455052c55a829eaf0da7..bedd974c9a1a65f275b38322a6f8c88794aefd81 100644
--- a/components/rgbd-sources/src/sources/stereovideo/local.hpp
+++ b/components/rgbd-sources/src/sources/stereovideo/local.hpp
@@ -25,10 +25,15 @@ class LocalSource : public Configurable {
 	//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);
+	bool get(cv::cuda::GpuMat &l, cv::cuda::GpuMat &r, cv::cuda::GpuMat &h, Calibrate *c, cv::cuda::Stream &stream);
 
-	unsigned int width() const { return width_; }
-	unsigned int height() const { return height_; }
+	unsigned int width() const { return dwidth_; }
+	unsigned int height() const { return dheight_; }
+
+	unsigned int fullWidth() const { return width_; }
+	unsigned int fullHeight() const { return height_; }
+
+	inline bool hasHigherRes() const { return dwidth_ != width_; }
 	
 	//void setFramerate(float fps);
 	//float getFramerate() const;
@@ -50,9 +55,13 @@ class LocalSource : public Configurable {
 	cv::VideoCapture *camera_b_;
 	unsigned int width_;
 	unsigned int height_;
+	unsigned int dwidth_;
+	unsigned int dheight_;
 
 	cv::cuda::HostMem left_hm_;
 	cv::cuda::HostMem right_hm_;
+	cv::cuda::HostMem hres_hm_;
+	cv::Mat rtmp_;
 };
 
 }
diff --git a/components/rgbd-sources/src/sources/stereovideo/stereovideo.cpp b/components/rgbd-sources/src/sources/stereovideo/stereovideo.cpp
index 78bc1dba34ba5c9b8d07cca5b7a11b75d20fd487..8858726c8672a4286eb5fb98a397858d81d3d069 100644
--- a/components/rgbd-sources/src/sources/stereovideo/stereovideo.cpp
+++ b/components/rgbd-sources/src/sources/stereovideo/stereovideo.cpp
@@ -76,7 +76,10 @@ void StereoVideoSource::init(const string &file) {
 	pipeline_input_->append<ftl::operators::NVOpticalFlow>("optflow");
 	#endif
 
-	pipeline_depth_ = ftl::config::create<ftl::operators::Graph>(host_, "disparity");
+	//depth_size_ = cv::Size(	host_->value("depth_width", 1280),
+	//						host_->value("depth_height", 720));
+
+	/*pipeline_depth_ = ftl::config::create<ftl::operators::Graph>(host_, "disparity");
 	depth_size_ = cv::Size(	pipeline_depth_->value("width", color_size_.width),
 							pipeline_depth_->value("height", color_size_.height));
 
@@ -90,13 +93,13 @@ void StereoVideoSource::init(const string &file) {
 	pipeline_depth_->append<ftl::operators::Normals>("normals");  // Estimate surface normals
 	pipeline_depth_->append<ftl::operators::CrossSupport>("cross");
 	pipeline_depth_->append<ftl::operators::DiscontinuityMask>("discontinuity_mask");
-	pipeline_depth_->append<ftl::operators::AggreMLS>("mls");  // Perform MLS (using smoothing channel)
+	pipeline_depth_->append<ftl::operators::AggreMLS>("mls");  // Perform MLS (using smoothing channel)*/
 
-	calib_ = ftl::create<Calibrate>(host_, "calibration", color_size_, stream_);
+	calib_ = ftl::create<Calibrate>(host_, "calibration", cv::Size(lsrc_->fullWidth(), lsrc_->fullHeight()), stream_);
 	if (!calib_->isCalibrated()) LOG(WARNING) << "Cameras are not calibrated!";
 
 	// Generate camera parameters from camera matrix
-	cv::Mat K = calib_->getCameraMatrixLeft(depth_size_);
+	cv::Mat K = calib_->getCameraMatrixLeft(color_size_);
 	params_ = {
 		K.at<double>(0,0),	// Fx
 		K.at<double>(1,1),	// Fy
@@ -154,9 +157,9 @@ ftl::rgbd::Camera StereoVideoSource::parameters(Channel chan) {
 	cv::Mat K;
 	
 	if (chan == Channel::Right) {
-		K = calib_->getCameraMatrixRight(depth_size_);
+		K = calib_->getCameraMatrixRight(color_size_);
 	} else {
-		K = calib_->getCameraMatrixLeft(depth_size_);
+		K = calib_->getCameraMatrixLeft(color_size_);
 	}
 
 	// TODO: remove hardcoded values (min/max)
@@ -165,8 +168,8 @@ ftl::rgbd::Camera StereoVideoSource::parameters(Channel chan) {
 		K.at<double>(1,1),	// Fy
 		-K.at<double>(0,2),	// Cx
 		-K.at<double>(1,2),	// Cy
-		(unsigned int) depth_size_.width,
-		(unsigned int) depth_size_.height,
+		(unsigned int) color_size_.width,
+		(unsigned int) color_size_.height,
 		0.0f,	// 0m min
 		15.0f,	// 15m max
 		1.0 / calib_->getQ().at<double>(3,2), // Baseline
@@ -187,7 +190,12 @@ bool StereoVideoSource::retrieve() {
 	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_);
+	cv::cuda::GpuMat dummy;
+	auto &hres = (lsrc_->hasHigherRes()) ? frame.create<cv::cuda::GpuMat>(Channel::ColourHighRes) : dummy;
+
+	lsrc_->get(left, right, hres, calib_, stream2_);
+
+	//LOG(INFO) << "Channel size: " << hres.size();
 
 	pipeline_input_->apply(frame, frame, host_, cv::cuda::StreamAccessor::getStream(stream2_));
 	stream2_.waitForCompletion();
diff --git a/components/rgbd-sources/src/streamer.cpp b/components/rgbd-sources/src/streamer.cpp
index 9525a9fc918294d65cfbfec1922372dfd9978f21..75165ffd1d49dc89a268f9a1c3bb92575b644980 100644
--- a/components/rgbd-sources/src/streamer.cpp
+++ b/components/rgbd-sources/src/streamer.cpp
@@ -535,8 +535,8 @@ void Streamer::_process(ftl::rgbd::FrameSet &fs) {
 				}
 				
 
-				// TODO: Use ColourHighQuality if available
-				if (fs.frames[j].getPackets(Channel::Colour).size() == 0) {
+				auto colChan = (fs.frames[j].hasChannel(Channel::ColourHighRes)) ? Channel::ColourHighRes : Channel::Colour;
+				if (fs.frames[j].getPackets(colChan).size() == 0) {
 					if (!src->hq_encoder_c1) src->hq_encoder_c1 = ftl::codecs::allocateEncoder(
 						definition_t::HD1080, hq_devices_, hq_codec_);
 					auto *enc = src->hq_encoder_c1;
@@ -544,14 +544,14 @@ void Streamer::_process(ftl::rgbd::FrameSet &fs) {
 					if (enc) {
 						// TODO: Stagger the reset between nodes... random phasing
 						if (insert_iframes_ && fs.timestamp % (10*ftl::timer::getInterval()) == 0) enc->reset();
-						enc->encode(fs.frames[j].get<cv::cuda::GpuMat>(Channel::Colour), src->hq_bitrate, [this,src,hasChan2](const ftl::codecs::Packet &blk){
+						enc->encode(fs.frames[j].get<cv::cuda::GpuMat>(colChan), src->hq_bitrate, [this,src,hasChan2](const ftl::codecs::Packet &blk){
 							_transmitPacket(src, blk, Channel::Colour, hasChan2, Quality::High);
 						});
 					} else {
 						LOG(ERROR) << "Insufficient encoder resources";
 					}
 				} else {
-					const auto &packets = fs.frames[j].getPackets(Channel::Colour);
+					const auto &packets = fs.frames[j].getPackets(colChan);
 					// FIXME: Adjust block number and total to match number of packets
 					// Also requires the receiver to decode in block number order.
 					LOG(INFO) << "Send existing encoding: " << packets.size();
diff --git a/python/ftl/ftlstream.py b/python/ftl/ftlstream.py
index 65514f0ba1109700b338cf30e91b2d046754cc9b..dfbc858c3649c187b12b206478b4e23ca3a23c2b 100644
--- a/python/ftl/ftlstream.py
+++ b/python/ftl/ftlstream.py
@@ -246,21 +246,43 @@ class FTLStreamReader:
             warn("frame expected, no image received from decoder")
 
         if ftl.is_float_channel(self._sp.channel):
-            # TODO: only supports 8 bits per pixel format and 16 bits
-            #       ()"old format")
-            #
-            # NVPipe: (2 * width), high bits in left, low in right
+            if (p.flags & ftl.PacketFlags.MappedDepth):
+                # New format
 
-            high = img[:,(img.shape[1]//2):,0].astype(np.uint32) << 8
-            low = img[:,:(img.shape[1]//2),0].astype(np.uint32)
-            img = (high|low).astype(np.float)/1000.0
+                # hardcoded constants maxdepth and P
+                maxdepth = 16
+                P = (2.0 * 256.0) / 16384.0
 
+                # use only 8 bits of 10
+                img = (img >> 2).astype(np.float) / 255
+
+                L = img[:,:,0]
+                Ha = img[:,:,1]
+                Hb = img[:,:,2]
+
+                m = np.floor(4.0 * (L/P) - 0.5).astype(np.int) % 4
+                L0 = L - ((L-(P / 8.0)) % P) + (P / 4.0) * m.astype(np.float) - (P/8.0)
+
+                s = np.zeros(img.shape[:2], dtype=np.float)
+                np.copyto(s, (P/2.0) * Ha, where=m == 0)
+                np.copyto(s, (P/2.0) * Hb, where=m == 1)
+                np.copyto(s, (P/2.0) * (1.0 - Ha), where=m == 2)
+                np.copyto(s, (P/2.0) * (1.0 - Hb), where=m == 3)
+
+                img = (L0+s) * maxdepth
+
+            else:
+                # NvPipe format
+                high = img[:,(img.shape[1]//2):,0].astype(np.uint32) << 8
+                low = img[:,:(img.shape[1]//2),0].astype(np.uint32)
+                img = (high|low).astype(np.float)/1000.0
+            '''
             try:
                 img[img < self._calibration[sp.streamID].min_depth] = 0.0
                 img[img > self._calibration[sp.streamID].max_depth] = 0.0
             except KeyError:
                 warn("no calibration for received frame")
-
+            '''
             self._frame = img
 
         else:
diff --git a/python/ftl/ftltypes.py b/python/ftl/ftltypes.py
index 69eb872489e92bb460ae3c57365b85993a79f387..6c22004140b4e71e6a0ffbf4bc900e09b8a0ad28 100644
--- a/python/ftl/ftltypes.py
+++ b/python/ftl/ftltypes.py
@@ -13,6 +13,10 @@ Packet = namedtuple("Packet", ["codec", "definition", "block_total",
 StreamPacket = namedtuple("StreamPacket", ["timestamp", "streamID",
                                            "channel_count", "channel"])
 
+class PacketFlags:
+    RGB = 0x00000001
+    MappedDepth = 0x00000002
+
 # components/codecs/include/ftl/codecs/channels.hpp
 class Channel(IntEnum):
     None_           = -1
diff --git a/python/ftl/libde265.py b/python/ftl/libde265.py
index c57404eaa00477e8c487d608caecea82a8e57bfa..36a00439be431b2af3ee3b66024d4202d2d7bdca 100644
--- a/python/ftl/libde265.py
+++ b/python/ftl/libde265.py
@@ -1,24 +1,28 @@
-'''
-Python wrapper for libde265. Only decoding is (partly) implemented.
+'''!
+Python wrapper for libde265. Only decoding is implemented.
 
 Requirements:
  * libde265 library (libde265.so.0)
  * numpy
- * opencv or skimage
+ * opencv (recommended) or skimage
 
 '''
 
 try:
     import cv2 as cv
     def _resize(img, size):
-        return cv.resize(img, dsize=tuple(reversed(size)), interpolation=cv.INTER_CUBIC)
+        dst = np.zeros(size, dtype=img.dtype)
+        cv.resize(img, tuple(reversed(size)), dst, interpolation=cv.INTER_LINEAR)
+        return dst
 
 except ImportError:
+    # seems to be much slower than OpenCV resize()
+
     from skimage.transform import resize as resize_skimage
     def _resize(img, size):
-        # skimage resize() return dtype float64, convert back to uint8
+        # skimage resize() return dtype float64, convert back to original type
         # order: 0 nn, 1 bilinear, 3 bicubic
-        return (resize_skimage(img, size, order=3, mode="constant", cval=0) * 255).astype(np.uint8)
+        return (resize_skimage(img, size, order=2, mode="constant", cval=0) *  np.iinfo(img.dtype).max).astype(img.dtype)
 
 from warnings import warn
 
@@ -39,6 +43,10 @@ if _threads is None:
 
 _threads = 1
 
+################################################################################
+# interface and definitions from libde256 api
+################################################################################
+
 # error codes copied from header (de265.h)
 
 class _libde265error(IntEnum):
@@ -155,6 +163,8 @@ libde265.de265_get_image_plane.restype = ctypes.POINTER(ctypes.c_char)
 libde265.de265_get_number_of_input_bytes_pending.argtypes = [ctypes.c_void_p]
 libde265.de265_get_number_of_input_bytes_pending.restype = ctypes.c_int
 
+################################################################################
+
 class libde265Error(Exception):
     def __init__(self, code):
         super(libde265Error, self).__init__(
@@ -168,7 +178,7 @@ class Decoder:
         self._more = ctypes.c_int()
         self._out_stride = ctypes.c_int()
         self._ctx = libde265.de265_new_decoder()
-        self._supress_warnings = False
+        self._disable_warnings = False
 
         err = libde265.de265_start_worker_threads(self._ctx, threads)
 
@@ -182,28 +192,29 @@ class Decoder:
         size = (libde265.de265_get_image_height(de265_image, 0),
                 libde265.de265_get_image_width(de265_image, 0))
 
-        res = np.zeros((*size, 3), dtype=np.uint8)
+        res = np.zeros((*size, 3), dtype=np.uint16)
 
         # libde265: always 420 (???)
-        # chroma_format = libde265.de265_get_chroma_format(de265_image)
+        chroma_format = libde265.de265_get_chroma_format(de265_image)
+        if chroma_format != de265_chroma.de265_chroma_420:
+            raise NotImplementedError("Unsupported chroma format %s" % str(chroma_format))
 
         for c in range(0, 3):
             size_channel = (libde265.de265_get_image_height(de265_image, c),
                             libde265.de265_get_image_width(de265_image, c))
 
             if size_channel[0] > size[0] or size_channel[1] > size[1]:
-                # Is this possible?
-                print(size, size_channel)
                 raise Exception("Channel larger than first channel")
 
             bpp = libde265.de265_get_bits_per_pixel(de265_image, c)
-            if bpp != 8:
-                raise NotImplementedError("%i-bit format not implemented (TODO)" % bpp)
+            if bpp == 8:
+                dtype = np.uint8
+            else:
+                dtype = np.uint16
 
             img_ptr = libde265.de265_get_image_plane(de265_image, c, self._out_stride)
 
-			# libde: how is 10 bits per pixel returned
-            ch = np.frombuffer(img_ptr[:size_channel[0] * size_channel[1]], dtype=np.uint8)
+            ch = np.frombuffer(img_ptr[:size_channel[0] * self._out_stride.value], dtype=dtype)
             ch.shape = size_channel
 
             res[:,:,c] = _resize(ch, size)
@@ -211,7 +222,7 @@ class Decoder:
         return res
 
     def _warning(self):
-        if self._supress_warnings:
+        if self._disable_warnings:
             return
 
         code = libde265.de265_get_warning(self._ctx)