diff --git a/applications/reconstruct/src/main.cpp b/applications/reconstruct/src/main.cpp
index 18ed5db3cefd62b2414552b68b4dccf22b015fd1..619e586555d2fff34b2d1b3a0b9137aeb6346f81 100644
--- a/applications/reconstruct/src/main.cpp
+++ b/applications/reconstruct/src/main.cpp
@@ -46,10 +46,10 @@
 
 using ftl::net::Universe;
 using ftl::rgbd::Display;
-using ftl::config;
 using std::string;
 using std::vector;
 using ftl::rgbd::RGBDSource;
+using ftl::config::json_t;
 
 using json = nlohmann::json;
 using std::this_thread::sleep_for;
@@ -229,22 +229,22 @@ struct Cameras {
 };
 
 template <template<class> class Container>
-std::map<string, Eigen::Matrix4f> runRegistration(ftl::net::Universe &net, Container<Cameras> &inputs) {
+std::map<string, Eigen::Matrix4f> runRegistration(ftl::Configurable *root, ftl::net::Universe &net, Container<Cameras> &inputs) {
 	std::map<string, Eigen::Matrix4f> registration;
 	
 	// NOTE: uses config["registration"]
 	
-	if (!config["registration"].is_object()) {
+	if (!root->getConfig()["registration"].is_object()) {
 		LOG(FATAL) << "Configuration missing \"registration\" entry!";
 		return registration;
 	}
 	
-	int iter = (int) config["registration"]["calibration"]["iterations"];
-	int delay = (int) config["registration"]["calibration"]["delay"];
-	float max_error = (int) config["registration"]["calibration"]["max_error"];
-	string ref_uri = (string) config["registration"]["reference-source"];
-	cv::Size pattern_size(	(int) config["registration"]["calibration"]["patternsize"][0],
-							(int) config["registration"]["calibration"]["patternsize"][1]);
+	int iter = (int) root->getConfig()["registration"]["calibration"]["iterations"];
+	int delay = (int) root->getConfig()["registration"]["calibration"]["delay"];
+	float max_error = (int) root->getConfig()["registration"]["calibration"]["max_error"];
+	string ref_uri = (string) root->getConfig()["registration"]["reference-source"];
+	cv::Size pattern_size(	(int) root->getConfig()["registration"]["calibration"]["patternsize"][0],
+							(int) root->getConfig()["registration"]["calibration"]["patternsize"][1]);
 	
 	// config["registration"] done
 	
@@ -313,23 +313,25 @@ std::map<string, Eigen::Matrix4f> runRegistration(ftl::net::Universe &net, Conta
 }
 #endif
 
-static void run() {
-	Universe net(config["net"]);
+static void run(ftl::Configurable *root) {
+	Universe *net = ftl::create<Universe>(root, "net");
 	
 	// Make sure connections are complete
-	sleep_for(milliseconds(500));
+	//sleep_for(milliseconds(500));
+	net->waitConnections();
 	
-	if (!config["sources"].is_array()) {
+	std::vector<Cameras> inputs;
+	auto sources = root->get<vector<json_t>>("sources");
+
+	if (!sources) {
 		LOG(ERROR) << "No sources configured!";
 		return;
 	}
-	
-	std::vector<Cameras> inputs;
 	//std::vector<Display> displays;
 
 	// TODO Allow for non-net source types
-	for (auto &src : config["sources"]) {		
-		RGBDSource *in = ftl::rgbd::RGBDSource::create(src, &net); //new ftl::rgbd::NetSource(src, &net);
+	for (auto &src : *sources) {		
+		RGBDSource *in = ftl::rgbd::RGBDSource::create(src, net); //new ftl::rgbd::NetSource(src, &net);
 		if (!in) {
 			LOG(ERROR) << "Unrecognised source: " << src;
 		} else {
@@ -355,8 +357,8 @@ static void run() {
 	// load point cloud transformations
 	
 	std::map<string, Eigen::Matrix4f> registration;
-	if (config["registration"]["calibration"]["run"]) {
-		registration = runRegistration(net, inputs);
+	if (root->getConfig()["registration"]["calibration"]["run"]) {
+		registration = runRegistration(root, *net, inputs);
 	}
 	else {
 		LOG(INFO) << "LOAD REG";
@@ -367,7 +369,7 @@ static void run() {
 	// (registration includes every camera)
 	
 	bool valid_registration = true;
-	string ref_input = config["registration"]["reference-source"];
+	string ref_input = root->getConfig()["registration"]["reference-source"];
 	
 	// check every camera is included in registration
 	for (auto &input : inputs) {
@@ -417,28 +419,28 @@ static void run() {
 	for (auto &input : inputs) { LOG(INFO) << "    " + (string) input.source->getConfig()["uri"]; }
 	
 	//vector<PointCloud<PointXYZRGB>::Ptr> clouds(inputs.size());
-	ftl::rgbd::Display display(config["display"]); // todo
-	ftl::rgbd::VirtualSource *virt = new ftl::rgbd::VirtualSource(config["virtual"], &net);
-	ftl::voxhash::SceneRep scene(config["voxelhash"]);
-	virt->setScene(&scene);
-	display.setSource(virt);
+	ftl::rgbd::Display *display = ftl::create<ftl::rgbd::Display>(root, "display");
+	ftl::rgbd::VirtualSource *virt = ftl::create<ftl::rgbd::VirtualSource>(root, "virtual", net);
+	ftl::voxhash::SceneRep *scene = ftl::create<ftl::voxhash::SceneRep>(root, "voxelhash");
+	virt->setScene(scene);
+	display->setSource(virt);
 
 
 	unsigned char frameCount = 0;
 	bool paused = false;
 
 	// Keyboard camera controls
-	display.onKey([&paused](int key) {
+	display->onKey([&paused](int key) {
 		if (key == 32) paused = !paused;
 	});
 
 	int active = inputs.size();
-	while (active > 0 && display.active()) {
+	while (active > 0 && display->active()) {
 		active = 0;
 
 		if (!paused) {
 			//net.broadcast("grab");  // To sync cameras
-			scene.nextFrame();
+			scene->nextFrame();
 		
 			for (size_t i = 0; i < inputs.size(); i++) {
 				// Get the RGB-Depth frame from input
@@ -460,7 +462,7 @@ static void run() {
 				// Send to GPU and merge view into scene
 				inputs[i].gpu.updateParams(inputs[i].params);
 				inputs[i].gpu.updateData(depth, rgba);
-				scene.integrate(inputs[i].source->getPose(), inputs[i].gpu, inputs[i].params, nullptr);
+				scene->integrate(inputs[i].source->getPose(), inputs[i].gpu, inputs[i].params, nullptr);
 			}
 		} else {
 			active = 1;
@@ -468,11 +470,10 @@ static void run() {
 
 		frameCount++;
 
-		display.update();
+		display->update();
 	}
 }
 
 int main(int argc, char **argv) {
-	ftl::configure(argc, argv, "reconstruction");
-	run();
+	run(ftl::configure(argc, argv, "reconstruction_default"));
 }
diff --git a/applications/vision/src/main.cpp b/applications/vision/src/main.cpp
index 99c6937725e5d418802f59fa0b4134ab0f406da0..e9bcc690f11440596727a53f9b6f97e02e86261e 100644
--- a/applications/vision/src/main.cpp
+++ b/applications/vision/src/main.cpp
@@ -50,7 +50,6 @@ using std::mutex;
 using std::unique_lock;
 using cv::Mat;
 using json = nlohmann::json;
-using ftl::config;
 
 namespace ftl {
 void disparityToDepth(const cv::Mat &disparity, cv::Mat &depth, const cv::Mat &q) {
@@ -78,44 +77,51 @@ void disparityToDepth(const cv::Mat &disparity, cv::Mat &depth, const cv::Mat &q
 };
 
 
-static void run(const string &file) {
-	ctpl::thread_pool pool(2);
-	Universe net(config["net"]);
+static void run(ftl::Configurable *root) {
+	Universe *net = ftl::create<Universe>(root, "net");
 	LOG(INFO) << "Net started.";
 
+	auto paths = root->get<vector<string>>("paths");
+	string file = "";
+	if (paths && (*paths).size() > 0) file = (*paths)[0];
+
 	StereoVideoSource *source = nullptr;
-	source = new StereoVideoSource(config, file);
+	source = ftl::create<StereoVideoSource>(root, "source", file);
 	
-	Display display(config["display"], "local");
+	Display *display = ftl::create<Display>(root, "display", "local");
 	
-	Streamer stream(config["stream"], &net);
-	stream.add(source);
-	stream.run();
+	Streamer *stream = ftl::create<Streamer>(root, "stream", net);
+	stream->add(source);
+	stream->run();
 
-	while (display.active()) {
+	while (display->active()) {
 		cv::Mat rgb, depth;
 		source->getRGBD(rgb, depth);
-		if (!rgb.empty()) display.render(rgb, depth, source->getParameters());
-		display.wait(10);
+		if (!rgb.empty()) display->render(rgb, depth, source->getParameters());
+		display->wait(10);
 	}
 
-	stream.stop();
+	stream->stop();
 
 	LOG(INFO) << "Finished.";
+	delete stream;
+	delete display;
+	delete source;
+	delete net;
 }
 
 int main(int argc, char **argv) {
 	std::cout << "FTL Vision Node " << FTL_VERSION_LONG << std::endl;
-	auto paths = ftl::configure(argc, argv, "vision");
+	auto root = ftl::configure(argc, argv, "vision_default");
 	
-	config["paths"] = paths;
+	//config["ftl://vision/default"]["paths"] = paths;
 
 	// Choose normal or middlebury modes
-	if (config["middlebury"]["dataset"] == "") {
+	//if (config["middlebury"]["dataset"] == "") {
 		std::cout << "Loading..." << std::endl;
-		run((paths.size() > 0) ? paths[0] : "");
-	} else {
-		ftl::middlebury::test(config);
-	}
+		run(root);
+	//} else {
+	//	ftl::middlebury::test(config);
+	//}
 }
 
diff --git a/components/common/cpp/CMakeLists.txt b/components/common/cpp/CMakeLists.txt
index dac189bb61df6d19f1a8d0d8b1ed299c608c6054..56a74f2664127f3ad18390d1fbfc47d581d4e95b 100644
--- a/components/common/cpp/CMakeLists.txt
+++ b/components/common/cpp/CMakeLists.txt
@@ -1,10 +1,13 @@
 set(COMMONSRC
 	src/config.cpp
+	src/uri.cpp
 	src/configuration.cpp
 	src/configurable.cpp
 	src/opencv_to_pcl.cpp
 )
 
+check_function_exists(uriParseSingleUriA HAVE_URIPARSESINGLE)
+
 add_library(ftlcommon ${COMMONSRC})
 
 target_compile_options(ftlcommon PUBLIC $<$<COMPILE_LANGUAGE:CXX>:-fPIC>)
@@ -14,7 +17,7 @@ target_include_directories(ftlcommon PUBLIC
 	$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
 	$<INSTALL_INTERFACE:include>
 	PRIVATE src)
-target_link_libraries(ftlcommon glog::glog ${OpenCV_LIBS} ${PCL_LIBRARIES})
+target_link_libraries(ftlcommon glog::glog ${OpenCV_LIBS} ${PCL_LIBRARIES} ${URIPARSER_LIBRARIES})
 
 add_subdirectory(test)
 
diff --git a/components/common/cpp/include/ftl/configurable.hpp b/components/common/cpp/include/ftl/configurable.hpp
index 7008c9b1edddf412c6d05130522977186f9d9cee..c1f903b61df4004e458c54f243fd0014c69ff96f 100644
--- a/components/common/cpp/include/ftl/configurable.hpp
+++ b/components/common/cpp/include/ftl/configurable.hpp
@@ -27,27 +27,17 @@ namespace ftl {
  */
 class Configurable {
 	public:
-	Configurable() {}
+	Configurable();
 	explicit Configurable(nlohmann::json &config) : config_(config) {
 		if (config["uri"].is_string()) __changeURI(config["uri"].get<std::string>(), this);
 	}
+	virtual ~Configurable() {}
 
 	/**
 	 * Force the JSON object to have specific properties with a specific type.
 	 * If not, emit errors and terminate the application.
 	 */
-	void required(const char *f, const std::vector<std::tuple<std::string, std::string, std::string>> &r) {
-		bool diderror = false;
-		for (auto i : r) {
-			auto [name, desc, type] = i;
-			if (config_[name].type_name() != type) {
-				LOG(ERROR) << "Missing required option in \"" << f << "\": \"" << name << "\" - " << desc;
-				LOG(ERROR) << "    Got type " << config_[name].type_name() << " but expected " << type;
-				diderror = true;
-			}
-		}
-		if (diderror) LOG(FATAL) << "Cannot continue without required option";
-	}
+	void required(const char *f, const std::vector<std::tuple<std::string, std::string, std::string>> &r);
 
 	nlohmann::json &getConfig() { return config_; }
 
@@ -57,12 +47,15 @@ class Configurable {
 	 * the requested type.
 	 */
 	template <typename T>
-	std::optional<T> get(const std::string &name) {
-		try {
-			return config_[name].get<T>();
-		} catch (...) {
-			return {};
-		}
+	std::optional<T> get(const std::string &name);
+
+	/**
+	 * Get a configuration property, but return a default if not found.
+	 */
+	template <typename T>
+	T value(const std::string &name, T def) {
+		auto r = get<T>(name);
+		return (r) ? *r : def;
 	}
 
 	/**
@@ -102,4 +95,33 @@ void Configurable::set<const std::string&>(const std::string &name, const std::s
 
 }
 
+#include <ftl/configuration.hpp>
+
+template <typename T>
+std::optional<T> ftl::Configurable::get(const std::string &name) {
+	if (!config_[name].is_null()) {
+		try {
+			return config_[name].get<T>();
+		} catch (...) {
+			return {};
+		}
+	} 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;
+		auto &r = ftl::config::resolve(res_uri);
+
+		LOG(INFO) << "GET Resolve: " << res_uri << " = " << r;
+
+		try {
+			return r.get<T>();
+		} catch (...) {
+			LOG(ERROR) << "Missing: " << config_["$id"].get<std::string>()+"/"+name;
+			return {};
+		}
+	} else {
+		return {};
+	}
+}
+
 #endif  // _FTL_CONFIGURABLE_HPP_
diff --git a/components/common/cpp/include/ftl/configuration.hpp b/components/common/cpp/include/ftl/configuration.hpp
index 685c9b4ba30e08268d4a48c2b50ad852e84cf7b8..9f8c6e0fda3033f0102b2ac1de95e7cb3afe3790 100644
--- a/components/common/cpp/include/ftl/configuration.hpp
+++ b/components/common/cpp/include/ftl/configuration.hpp
@@ -1,26 +1,125 @@
+#pragma once
 #ifndef _FTL_COMMON_CONFIGURATION_HPP_
 #define _FTL_COMMON_CONFIGURATION_HPP_
 
 #include <nlohmann/json.hpp>
+//#include <ftl/configurable.hpp>
 #include <string>
 #include <vector>
 #include <optional>
 
 namespace ftl {
 
-extern nlohmann::json config;
+class Configurable;
 
 bool is_directory(const std::string &path);
 bool is_file(const std::string &path);
 bool create_directory(const std::string &path);
-
 bool is_video(const std::string &file);
 
+namespace config {
+
+typedef nlohmann::json json_t;
+
 std::optional<std::string> locateFile(const std::string &name);
 
-std::vector<std::string> configure(int argc, char **argv, const std::string &app);
+Configurable *configure(int argc, char **argv, const std::string &root);
+
+/**
+ * Resolve a JSON schema reference, but do not wait for a remote reference
+ * if it is not available. A null entity is returned if not resolved.
+ */
+json_t &resolve(const std::string &);
+
+/**
+ * Resolve a reference object, or if not a reference object it simply returns
+ * the original object. A reference object with additional properties other
+ * than $ref will result in a new merged object.
+ */
+json_t &resolve(json_t &ref);
+
+/**
+ * Resolve a JSON schema reference and block until such a reference can be
+ * resolved correctly.
+ */
+json_t &resolveWait(const std::string &);
+
+/**
+ * Using a JSON schema reference, find an existing instance of a Configurable
+ * object for that reference. Or return nullptr if not found.
+ */
+Configurable *find(const std::string &uri);
+
+/**
+ * Adds a Configurable instance to the database of instances so that it can
+ * then be resolved using find().
+ */
+void registerConfigurable(Configurable *cfg);
+
+template <typename T, typename... ARGS>
+T *create(json_t &link, ARGS ...args);
+
+template <typename T, typename... ARGS>
+T *create(ftl::Configurable *parent, const std::string &name, ARGS ...args);
+
+void set(const std::string &uri, const nlohmann::json &);
+
+}  // namespace config
+
+// Deprecated
+using config::create;
+using config::locateFile;
+using config::configure;
+
+}  // namespace ftl
+
+#include <ftl/configurable.hpp>
+
+template <typename T, typename... ARGS>
+T *ftl::config::create(json_t &link, ARGS ...args) {
+    auto &r = link; // = ftl::config::resolve(link);
+
+    if (!r["$id"].is_string()) {
+        LOG(FATAL) << "Entity does not have $id or parent: " << r;
+        return nullptr;
+    }
+
+    ftl::Configurable *cfg = ftl::config::find(r["$id"].get<std::string>());
+    if (!cfg) {
+       // try {
+            cfg = new T(r, args...);
+        //} catch(...) {
+       //     LOG(FATAL) << "Could not construct " << link;
+        //}
+    }
+
+    try {
+        return dynamic_cast<T*>(cfg);
+    } catch(...) {
+        LOG(FATAL) << "Configuration URI object is of wrong type: " << link;
+        return nullptr;
+    }
+}
+
+template <typename T, typename... ARGS>
+T *ftl::config::create(ftl::Configurable *parent, const std::string &name, ARGS ...args) {
+    nlohmann::json &entity = ftl::config::resolve(parent->getConfig()[name]);
+
+    if (entity.is_object()) {
+        if (!entity["$id"].is_string()) {
+			// TODO(Nick) Check for # in URI
+            entity["$id"] = *parent->get<std::string>("$id") + std::string("/") + name;
+        }
+    } /*else {
+		nlohmann::json &res = resolve(entity);
+		if (!res["uri"].is_string()) {
+            res["uri"] = *parent->get<std::string>("uri") + std::string("/") + name;
+			LOG(WARNING) << "Creating false URI!!! - " << res["uri"].get<std::string>();
+        }
+	}*/
 
-};
+    return create<T>(entity, args...);
+}
 
 #endif  // _FTL_COMMON_CONFIGURATION_HPP_
 
diff --git a/components/net/cpp/include/ftl/uri.hpp b/components/common/cpp/include/ftl/uri.hpp
similarity index 84%
rename from components/net/cpp/include/ftl/uri.hpp
rename to components/common/cpp/include/ftl/uri.hpp
index 1931c8196c5e73acea16390c2dee8613a5324f2d..be6b7fd3e52f27d9a0769e180a994bd781c7e85e 100644
--- a/components/net/cpp/include/ftl/uri.hpp
+++ b/components/common/cpp/include/ftl/uri.hpp
@@ -39,9 +39,17 @@ namespace ftl {
 		scheme_t getProtocol() const { return m_proto; };
 		scheme_t getScheme() const { return m_proto; };
 		const std::string &getPath() const { return m_path; };
+		const std::string &getFragment() const { return m_frag; }
 		std::string getQuery() const;
 		const std::string &getBaseURI() const { return m_base; };
-		const std::string &getPathSegment(int n) const { return m_pathseg[n]; };
+
+		/**
+		 * Get the URI without query parameters, and limit path to length N.
+		 * If N is negative then it is taken from full path length.
+		 */
+		std::string getBaseURI(int n);
+
+		std::string getPathSegment(int n) const;
 
 		void setAttribute(const std::string &key, const std::string &value);
 		void setAttribute(const std::string &key, int value);
@@ -60,10 +68,12 @@ namespace ftl {
 		bool m_valid;
 		std::string m_host;
 		std::string m_path;
+		std::string m_frag;
 		std::string m_base;
 		std::vector<std::string> m_pathseg;
 		int m_port;
 		scheme_t m_proto;
+		std::string m_protostr;
 		// std::string m_query;
 		std::map<std::string, std::string> m_qmap;
 	};
diff --git a/components/common/cpp/include/nlohmann/json.hpp b/components/common/cpp/include/nlohmann/json.hpp
index c9af0bed36d6852de735b8d0b5d034614aef0e54..68b715f045b97286b0d4849c67a9a747cac8f88b 100644
--- a/components/common/cpp/include/nlohmann/json.hpp
+++ b/components/common/cpp/include/nlohmann/json.hpp
@@ -3977,12 +3977,24 @@ scan_number_done:
             return token_type::parse_error;
         }
 
+		bool incomment = false;
+
         // read next character and ignore whitespace
         do
         {
             get();
-        }
-        while (current == ' ' or current == '\t' or current == '\n' or current == '\r');
+
+			// Nick: Add support for inline comments
+			if (!incomment && current == '/') {
+				get();
+				if (current != '/') {
+					error_message = "missing / for inline comment";
+					return token_type::parse_error;
+				}
+				incomment = true;
+			} else if (incomment && (current == '\n' || current == '\r')) incomment = false;
+        }
+        while (incomment or current == ' ' or current == '\t' or current == '\n' or current == '\r');
 
         switch (current)
         {
diff --git a/components/common/cpp/src/configurable.cpp b/components/common/cpp/src/configurable.cpp
index c4520b12e9957f8d8cd11118865b935f0cfa2975..d770ae6e7003a21bc4a9d15d9c523cb0f0339f84 100644
--- a/components/common/cpp/src/configurable.cpp
+++ b/components/common/cpp/src/configurable.cpp
@@ -5,6 +5,25 @@ using std::string;
 using std::map;
 using std::list;
 using std::function;
+using ftl::config::json_t;
+
+extern nlohmann::json null_json;
+
+Configurable::Configurable() : config_(null_json) {}
+
+void Configurable::required(const char *f, const std::vector<std::tuple<std::string, std::string, std::string>> &r) {
+	bool diderror = false;
+	for (auto i : r) {
+		auto [name, desc, type] = i;
+		auto ent = get<json_t>(name);
+		if (!ent || (*ent).type_name() != type) {
+			LOG(ERROR) << "Missing required option in \"" << f << "\": \"" << name << "\" - " << desc;
+			//LOG(ERROR) << "    Got type " << (*ent).type_name() << " but expected " << type;
+			diderror = true;
+		}
+	}
+	if (diderror) LOG(FATAL) << "Cannot continue without required option";
+}
 
 void Configurable::_trigger(const string &name) {
 	auto ix = observers_.find(name);
diff --git a/components/common/cpp/src/configuration.cpp b/components/common/cpp/src/configuration.cpp
index a302d606bc32d8d2cd1ee8cc18b66387beab54bf..07fad52d1021b8146d63722f36b157262b199148 100644
--- a/components/common/cpp/src/configuration.cpp
+++ b/components/common/cpp/src/configuration.cpp
@@ -11,13 +11,15 @@
 
 #include <nlohmann/json.hpp>
 #include <ftl/configuration.hpp>
+#include <ftl/configurable.hpp>
+#include <ftl/uri.hpp>
 
 #include <fstream>
 #include <string>
 #include <map>
 #include <iostream>
 
-using nlohmann::json;
+using ftl::config::json_t;
 using std::ifstream;
 using std::string;
 using std::map;
@@ -25,13 +27,20 @@ using std::vector;
 using std::optional;
 using ftl::is_file;
 using ftl::is_directory;
+using ftl::Configurable;
 
 // Store loaded configuration
 namespace ftl {
-json config;
+namespace config {
+json_t config;
+//json_t root_config;
 };
+};
+
+using ftl::config::config;
+//using ftl::config::root_config;
 
-using ftl::config;
+static Configurable *rootCFG = nullptr;
 
 bool ftl::is_directory(const std::string &path) {
 #ifdef WIN32
@@ -85,11 +94,14 @@ bool ftl::create_directory(const std::string &path) {
 #endif
 }
 
-optional<string> ftl::locateFile(const string &name) {
-	auto paths = config["paths"];
+optional<string> ftl::config::locateFile(const string &name) {
+	if (is_file(name)) return name;
+
+	auto paths = rootCFG->getConfig()["paths"];
 	
-	if (!paths.is_null()) {
-		for (string p : paths) {
+	if (paths.is_array()) {
+		vector<string> vpaths = paths.get<vector<string>>();
+		for (string p : vpaths) {
 			if (is_directory(p)) {
 				if (is_file(p+"/"+name)) {
 					return p+"/"+name;
@@ -116,11 +128,11 @@ static bool mergeConfig(const string path) {
 	//i.open(path);
 	if (i.is_open()) {
 		try {
-			json t;
+			nlohmann::json t;
 			i >> t;
 			config.merge_patch(t);
 			return true;
-		} catch (json::parse_error& e) {
+		} catch (nlohmann::json::parse_error& e) {
 			LOG(ERROR) << "Parse error in loading config: "  << e.what();
 			return false;
 		} catch (...) {
@@ -131,11 +143,128 @@ static bool mergeConfig(const string path) {
 	}
 }
 
+static std::map<std::string, json_t*> config_index;
+static std::map<std::string, ftl::Configurable*> config_instance;
+
+/*
+ * Recursively URI index the JSON structure.
+ */
+static void _indexConfig(json_t &cfg) {
+	if (cfg.is_object()) {
+		auto id = cfg["$id"];
+		if (id.is_string()) {
+			LOG(INFO) << "Indexing: " << id.get<string>();
+			config_index[id.get<string>()] = &cfg;
+		}
+
+		for (auto i : cfg.items()) {
+			if (i.value().is_structured()) {
+				_indexConfig(cfg[i.key()]);
+			}
+		}
+	} // TODO(Nick) Arrays....
+}
+
+ftl::Configurable *ftl::config::find(const std::string &uri) {
+	auto ix = config_instance.find(uri);
+	if (ix == config_instance.end()) return nullptr;
+	else return (*ix).second;
+}
+
+void ftl::config::registerConfigurable(ftl::Configurable *cfg) {
+	auto uri = cfg->get<string>("$id");
+	if (!uri) {
+		LOG(FATAL) << "Configurable object is missing $id property";
+		return;
+	}
+	auto ix = config_instance.find(*uri);
+	if (ix == config_instance.end()) {
+		LOG(FATAL) << "Attempting to create a duplicate object: " << *uri;
+	} else {
+		config_instance[*uri] = cfg;
+	}
+}
+
+json_t null_json;
+
+json_t &ftl::config::resolve(const std::string &puri) {
+	string uri_str = puri;
+
+	// TODO(Nick) Must support alternative root (last $id)
+	if (uri_str.at(0) == '#') {
+		uri_str = config["$id"].get<string>() + uri_str;
+	}
+
+	ftl::URI uri(uri_str);
+	if (uri.isValid()) {
+		std::string u = uri.getBaseURI();
+		LOG(INFO) << "Resolve URI: " << u;
+		auto ix = config_index.find(u);
+		if (ix == config_index.end()) {
+			LOG(FATAL) << "Cannot find resource: " << u;
+		}
+
+		auto ptr = nlohmann::json::json_pointer("/"+uri.getFragment());
+		try {
+			return resolve((*ix).second->at(ptr));
+		} catch(...) {
+			return null_json;
+		}
+	//}
+/*
+
+		int n = 0;
+		while (u.size() != 0) {
+			LOG(INFO) << "Resolve URI: " << u;
+			auto ix = config_index.find(u);
+			if (ix == config_index.end()) {
+				u = uri.getBaseURI(--n);
+				continue;
+			}
+			//LOG(INFO) << "FOUND URI " << *(*ix).second;
+
+			if (n == 0) {
+				return *(*ix).second;
+			} else {
+				// Must apply path segments again...
+				nlohmann::json *c = (*ix).second;
+				while (n < 0) {
+					auto seg = uri.getPathSegment(n++);
+					c = &(*c)[seg];
+					
+					// Recursive resolve...
+					if ((*c).is_string()) {
+						c = &resolve(*c);
+					}
+				}
+
+				// Give it the correct URI
+				if (!(*c)["$id"].is_string()) {
+					(*c)["$id"] = uri.getBaseURI();
+				}
+
+				return *c;
+			}
+		}
+		LOG(FATAL) << "Unresolvable configuration URI: " << uri.getBaseURI();
+		return null_json;*/
+	} else {
+		return null_json;
+	}
+}
+
+json_t &ftl::config::resolve(json_t &j) {
+	if (j.is_object() && j["$ref"].is_string()) {
+		return resolve(j["$ref"].get<string>());
+	} else {
+		return j;
+	}
+}
+
 /**
  * Find and load a JSON configuration file
  */
-static bool findConfiguration(const string &file, const vector<string> &paths,
-		const std::string &app) {
+static bool findConfiguration(const string &file, const vector<string> &paths) {
 	bool f = false;
 	bool found = false;
 	
@@ -169,7 +298,7 @@ static bool findConfiguration(const string &file, const vector<string> &paths,
 	}
 
 	if (found) {
-		config = config[app];
+		_indexConfig(config);
 		return true;
 	} else {
 		return false;
@@ -214,9 +343,10 @@ static map<string, string> read_options(char ***argv, int *argc) {
  * Put command line options into json config. If config element does not exist
  * or is of a different type then report an error.
  */
-static void process_options(const map<string, string> &opts) {
+static void process_options(Configurable *root, const map<string, string> &opts) {
 	for (auto opt : opts) {
 		if (opt.first == "config") continue;
+		if (opt.first == "root") continue;
 
 		if (opt.first == "version") {
 			std::cout << "Future-Tech Lab - v" << FTL_VERSION << std::endl;
@@ -225,21 +355,22 @@ static void process_options(const map<string, string> &opts) {
 		}
 
 		try {
-			auto ptr = json::json_pointer("/"+opt.first);
+			auto ptr = nlohmann::json::json_pointer("/"+opt.first);
 			// TODO(nick) Allow strings without quotes
-			auto v = json::parse(opt.second);
-			if (v.type() != config.at(ptr).type()) {
-				LOG(ERROR) << "Incorrect type for argument " << opt.first;
+			auto v = nlohmann::json::parse(opt.second);
+			std::string type = root->getConfig().at(ptr).type_name();
+			if (type != "null" && v.type_name() != type) {
+				LOG(ERROR) << "Incorrect type for argument " << opt.first << " - expected '" << type << "', got '" << v.type_name() << "'";
 				continue;
 			}
-			config.at(ptr) = v;
+			root->getConfig().at(ptr) = v;
 		} catch(...) {
-			LOG(ERROR) << "Unrecognised option: " << opt.first;
+			LOG(ERROR) << "Unrecognised option: " << *root->get<string>("$id") << "#" << opt.first;
 		}
 	}
 }
 
-vector<string> ftl::configure(int argc, char **argv, const std::string &app) {
+Configurable *ftl::config::configure(int argc, char **argv, const std::string &root) {
 	argc--;
 	argv++;
 
@@ -251,11 +382,22 @@ vector<string> ftl::configure(int argc, char **argv, const std::string &app) {
 		paths.push_back(argv[0]);
 	}
 	
-	if (!findConfiguration(options["config"], paths, app)) {
+	if (!findConfiguration(options["config"], paths)) {
 		LOG(FATAL) << "Could not find any configuration!";
 	}
-	process_options(options);
 
-	return paths;
+	string root_str = (options.find("root") != options.end()) ? nlohmann::json::parse(options["root"]).get<string>() : root;
+
+	Configurable *rootcfg = create<Configurable>(config);
+	if (root_str.size() > 0) {
+		LOG(INFO) << "Setting root to " << root_str;
+		rootcfg = create<Configurable>(rootcfg, root_str);
+	}
+
+	//root_config = rootcfg->getConfig();
+	rootCFG = rootcfg;
+	rootcfg->set("paths", paths);
+	process_options(rootcfg, options);
+	return rootcfg;
 }
 
diff --git a/components/net/cpp/src/uri.cpp b/components/common/cpp/src/uri.cpp
similarity index 72%
rename from components/net/cpp/src/uri.cpp
rename to components/common/cpp/src/uri.cpp
index e23435246d42fc72adc2e18dca2ce199ce486688..2fcbd86d1d3c824627641474720067015fb8debd 100644
--- a/components/net/cpp/src/uri.cpp
+++ b/components/common/cpp/src/uri.cpp
@@ -21,6 +21,7 @@ URI::URI(const URI &c) {
     m_pathseg = c.m_pathseg;
     m_qmap = c.m_qmap;
     m_base = c.m_base;
+	m_frag = c.m_frag;
 }
 
 void URI::_parse(uri_t puri) {
@@ -39,6 +40,7 @@ void URI::_parse(uri_t puri) {
         m_port = -1;
         m_proto = SCHEME_NONE;
         m_path = "";
+		m_frag = "";
     } else {
         m_host = std::string(uri.hostText.first, uri.hostText.afterLast - uri.hostText.first);
         
@@ -50,6 +52,7 @@ void URI::_parse(uri_t puri) {
         else if (prototext == "ws") m_proto = SCHEME_WS;
         else if (prototext == "ipc") m_proto = SCHEME_IPC;
         else m_proto = SCHEME_OTHER;
+        m_protostr = prototext;
 
         std::string porttext = std::string(uri.portText.first, uri.portText.afterLast - uri.portText.first);
         m_port = atoi(porttext.c_str());
@@ -83,10 +86,16 @@ void URI::_parse(uri_t puri) {
 
         uriFreeUriMembersA(&uri);
 
+		auto fraglast = (uri.query.first != NULL) ? uri.query.first : uri.fragment.afterLast;
+		if (uri.fragment.first != NULL && fraglast - uri.fragment.first > 0) {
+			m_frag = std::string(uri.fragment.first, fraglast - uri.fragment.first);
+		}
+
         m_valid = m_proto != SCHEME_NONE && m_host.size() > 0;
 
         if (m_valid) {
             if (m_qmap.size() > 0) m_base = std::string(uri.scheme.first, uri.query.first - uri.scheme.first - 1);
+			else if (uri.fragment.first != NULL) m_base = std::string(uri.scheme.first, uri.fragment.first - uri.scheme.first - 1);
             else m_base = std::string(uri.scheme.first);
         }
     }
@@ -96,6 +105,34 @@ string URI::to_string() const {
     return (m_qmap.size() > 0) ? m_base + "?" + getQuery() : m_base;
 }
 
+string URI::getPathSegment(int n) const {
+	int N = (n < 0) ? m_pathseg.size()+n : n;
+	if (N < 0 || N >= m_pathseg.size()) return "";
+	else return m_pathseg[N];
+}
+
+string URI::getBaseURI(int n) {
+    if (n >= (int)m_pathseg.size()) return m_base;
+    if (n >= 0) {
+        string r = m_protostr + string("://") + m_host + ((m_port != 0) ? string(":") + std::to_string(m_port) : "");
+        for (int i=0; i<n; i++) {
+			r += "/";
+            r += getPathSegment(i);
+        }
+
+        return r;
+    } else if (m_pathseg.size()+n >= 0) {
+        string r = m_protostr + string("://") + m_host + ((m_port != 0) ? string(":") + std::to_string(m_port) : "");
+        int N = m_pathseg.size()+n;
+        for (int i=0; i<N; i++) {
+			r += "/";
+            r += getPathSegment(i);
+        }
+
+        return r;
+    } else return "";
+}
+
 string URI::getQuery() const {
     string q;
     for (auto x : m_qmap) {
diff --git a/components/common/cpp/test/CMakeLists.txt b/components/common/cpp/test/CMakeLists.txt
index 67d8918396271c81fb2befdb5e19030bad29e0c7..c49d192bacfe66df7034612713dbc7e752291cb7 100644
--- a/components/common/cpp/test/CMakeLists.txt
+++ b/components/common/cpp/test/CMakeLists.txt
@@ -2,6 +2,9 @@
 add_executable(configurable_unit
 	./tests.cpp
 	../src/configurable.cpp
+	../src/uri.cpp
+	../src/config.cpp
+	../src/configuration.cpp
 	./configurable_unit.cpp
 )
 target_include_directories(configurable_unit PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../include")
@@ -9,8 +12,18 @@ target_link_libraries(configurable_unit
 	${URIPARSER_LIBRARIES}
 	glog::glog)
 
+### URI ########################################################################
+add_executable(uri_unit
+	./tests.cpp
+	../src/uri.cpp
+	./uri_unit.cpp)
+target_include_directories(uri_unit PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../include")
+target_link_libraries(uri_unit
+	${URIPARSER_LIBRARIES})
+
 
 
 add_test(ConfigurableUnitTest configurable_unit)
+add_test(URIUnitTest uri_unit)
 
 
diff --git a/components/net/cpp/test/uri_unit.cpp b/components/common/cpp/test/uri_unit.cpp
similarity index 52%
rename from components/net/cpp/test/uri_unit.cpp
rename to components/common/cpp/test/uri_unit.cpp
index a9fde4e8080ef98732c15149e6bd824dd1b31723..be94d76c12c6eee26af890b8178def8fd5914b4b 100644
--- a/components/net/cpp/test/uri_unit.cpp
+++ b/components/common/cpp/test/uri_unit.cpp
@@ -25,6 +25,53 @@ SCENARIO( "URI() can parse valid URIs", "[utility]" ) {
 		REQUIRE( uri.getPath() == "/test/case.html" );
 	}
 
+	GIVEN( "a valid fragment" ) {
+		URI uri("http://localhost:8080/test/case.html#frag");
+
+		REQUIRE( uri.isValid() );
+		REQUIRE( uri.getScheme() == URI::SCHEME_HTTP );
+		REQUIRE( uri.getHost() == "localhost" );
+		REQUIRE( (uri.getPort() == 8080) );
+		REQUIRE( uri.getPath() == "/test/case.html" );
+		REQUIRE( uri.getFragment() == "frag");
+	}
+
+	GIVEN( "a multipart valid fragment" ) {
+		URI uri("http://localhost:8080/test/case.html#frag/second");
+
+		REQUIRE( uri.isValid() );
+		REQUIRE( uri.getScheme() == URI::SCHEME_HTTP );
+		REQUIRE( uri.getHost() == "localhost" );
+		REQUIRE( (uri.getPort() == 8080) );
+		REQUIRE( uri.getPath() == "/test/case.html" );
+		REQUIRE( uri.getFragment() == "frag/second");
+		REQUIRE( uri.getBaseURI() == "http://localhost:8080/test/case.html");
+	}
+
+	GIVEN( "an empty fragment" ) {
+		URI uri("http://localhost:8080/test/case.html#");
+
+		REQUIRE( uri.isValid() );
+		REQUIRE( uri.getScheme() == URI::SCHEME_HTTP );
+		REQUIRE( uri.getHost() == "localhost" );
+		REQUIRE( (uri.getPort() == 8080) );
+		REQUIRE( uri.getPath() == "/test/case.html" );
+		REQUIRE( uri.getFragment() == "");
+		REQUIRE( uri.getBaseURI() == "http://localhost:8080/test/case.html");
+	}
+
+	/*GIVEN( "a valid fragment with query" ) {
+		URI uri("http://localhost:8080/test/case.html#frag?q=4");
+
+		REQUIRE( uri.isValid() );
+		REQUIRE( uri.getScheme() == URI::SCHEME_HTTP );
+		REQUIRE( uri.getHost() == "localhost" );
+		REQUIRE( (uri.getPort() == 8080) );
+		REQUIRE( uri.getPath() == "/test/case.html" );
+		REQUIRE( uri.getQuery() == "q=4" );
+		REQUIRE( uri.getFragment() == "frag");
+	}*/
+
 	GIVEN( "a valid scheme with path and query" ) {
 		URI uri("ftl://utu.fi/test/case.html?v=1");
 
@@ -89,3 +136,25 @@ SCENARIO( "URI::getAttribute() from query" ) {
 	}
 }
 
+SCENARIO( "URI::getBaseURI(N)" ) {
+	GIVEN( "an N of 0" ) {
+		URI uri("http://localhost:1000/hello/world");
+		REQUIRE( uri.getBaseURI(0) == "http://localhost:1000" );
+	}
+
+	GIVEN( "an N of -1" ) {
+		URI uri("http://localhost:1000/hello/world");
+		REQUIRE( uri.getBaseURI(-1) == "http://localhost:1000/hello" );
+	}
+
+	GIVEN( "an N of 1" ) {
+		URI uri("http://localhost:1000/hello/world");
+		REQUIRE( uri.getBaseURI(1) == "http://localhost:1000/hello" );
+	}
+
+	GIVEN( "an N of 2" ) {
+		URI uri("http://localhost:1000/hello/world");
+		REQUIRE( uri.getBaseURI(2) == "http://localhost:1000/hello/world" );
+	}
+}
+
diff --git a/components/net/cpp/CMakeLists.txt b/components/net/cpp/CMakeLists.txt
index d1e06de139d3bc5604cc68b95dc3d53b6a2d6937..c7e8bb34119a6b2f70b5a34b6e4b7e4911fe465f 100644
--- a/components/net/cpp/CMakeLists.txt
+++ b/components/net/cpp/CMakeLists.txt
@@ -4,7 +4,6 @@
 
 
 add_library(ftlnet
-	src/uri.cpp
 	src/listener.cpp
 	src/peer.cpp
 	src/dispatcher.cpp
@@ -12,13 +11,11 @@ add_library(ftlnet
 	src/ws_internal.cpp
 )
 
-check_function_exists(uriParseSingleUriA HAVE_URIPARSESINGLE)
-
 target_include_directories(ftlnet PUBLIC
 	$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
 	$<INSTALL_INTERFACE:include>
 	PRIVATE src)
-target_link_libraries(ftlnet ftlcommon Threads::Threads glog::glog ${UUID_LIBRARIES} ${URIPARSER_LIBRARIES})
+target_link_libraries(ftlnet ftlcommon Threads::Threads glog::glog ${UUID_LIBRARIES})
 
 install(TARGETS ftlnet EXPORT ftlnet-config
 	ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
@@ -27,7 +24,7 @@ install(TARGETS ftlnet EXPORT ftlnet-config
 install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
 
 add_executable(net-cli src/main.cpp)
-target_link_libraries(net-cli ftlnet ftlcommon glog::glog ${URIPARSER_LIBRARIES} Threads::Threads ${READLINE_LIBRARY} ${UUID_LIBRARIES})
+target_link_libraries(net-cli ftlnet ftlcommon glog::glog Threads::Threads ${READLINE_LIBRARY} ${UUID_LIBRARIES})
 add_dependencies(net-cli ftlnet)
 
 ADD_SUBDIRECTORY(test)
diff --git a/components/net/cpp/src/universe.cpp b/components/net/cpp/src/universe.cpp
index 290993394cee4b68a9a46ba06add7364a0ff5ee9..55ab9b0a50ca43401e92c09eb69c1e9a395deeaa 100644
--- a/components/net/cpp/src/universe.cpp
+++ b/components/net/cpp/src/universe.cpp
@@ -21,6 +21,7 @@ using ftl::UUID;
 using std::optional;
 using std::unique_lock;
 using std::mutex;
+using ftl::config::json_t;
 
 Universe::Universe() : Configurable(), active_(true), this_peer(ftl::net::this_peer), thread_(Universe::__start, this) {
 	_installBindings();
@@ -28,17 +29,21 @@ Universe::Universe() : Configurable(), active_(true), this_peer(ftl::net::this_p
 
 Universe::Universe(nlohmann::json &config) :
 		Configurable(config), active_(true), this_peer(ftl::net::this_peer), thread_(Universe::__start, this) {
-	if (config["listen"].is_array()) {
-		for (auto &l : config["listen"]) {
-			listen(l);
+
+	auto l = get<json_t>("listen");
+
+	if (l && (*l).is_array()) {
+		for (auto &ll : *l) {
+			listen(ll);
 		}
-	} else if (config["listen"].is_string()) {
-		listen(config["listen"]);
+	} else if (l && (*l).is_string()) {
+		listen((*l).get<string>());
 	}
 	
-	if (config["peers"].is_array()) {
-		for (auto &p : config["peers"]) {
-			connect(p);
+	auto p = get<json_t>("peers");
+	if (p && (*p).is_array()) {
+		for (auto &pp : *p) {
+			connect(pp);
 		}
 	}
 	
diff --git a/components/net/cpp/test/CMakeLists.txt b/components/net/cpp/test/CMakeLists.txt
index e81a89721fb6022c7f2fb7c286a588efd5fc0d92..38c453171754d47786c1d12b0497f560bbbca3fb 100644
--- a/components/net/cpp/test/CMakeLists.txt
+++ b/components/net/cpp/test/CMakeLists.txt
@@ -3,26 +3,16 @@ add_executable(peer_unit
 	./tests.cpp
 	../src/ws_internal.cpp
 	../src/dispatcher.cpp
-	../src/uri.cpp
 	./peer_unit.cpp
 	../../../common/cpp/src/config.cpp
 )
 target_include_directories(peer_unit PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../include" "${CMAKE_CURRENT_SOURCE_DIR}/../../../common/cpp/include")
 target_link_libraries(peer_unit
-	${URIPARSER_LIBRARIES}
+	ftlcommon
 	glog::glog
 	Threads::Threads
 	${UUID_LIBRARIES})
 
-### URI ########################################################################
-add_executable(uri_unit
-	./tests.cpp
-	../src/uri.cpp
-	./uri_unit.cpp)
-target_include_directories(uri_unit PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../include")
-target_link_libraries(uri_unit
-	${URIPARSER_LIBRARIES})
-
 ### P2P Base Unit ##############################################################
 # TODO(nick) Actually make this a unit test
 
@@ -45,16 +35,13 @@ add_dependencies(net_integration ftlnet)
 target_include_directories(net_integration PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}../include")
 target_link_libraries(net_integration
 	ftlnet
-	${URIPARSER_LIBRARIES}
+	ftlcommon
 	glog::glog
 	Threads::Threads
 	${UUID_LIBRARIES})
 
 
 
-
-
-add_test(URIUnitTest uri_unit)
 #add_test(ProtocolUnitTest protocol_unit)
 add_test(PeerUnitTest peer_unit)
 add_test(NetIntegrationTest net_integration)
diff --git a/components/renderers/cpp/include/ftl/display.hpp b/components/renderers/cpp/include/ftl/display.hpp
index 7856c3ee924d009f547009dc48613afced465c2f..2c2e8b8d5b80a1f3355e27462798d0fe6f698f12 100644
--- a/components/renderers/cpp/include/ftl/display.hpp
+++ b/components/renderers/cpp/include/ftl/display.hpp
@@ -6,6 +6,7 @@
 #define _FTL_DISPLAY_HPP_
 
 #include <ftl/config.h>
+#include <ftl/configurable.hpp>
 #include "../../../rgbd-sources/include/ftl/camera_params.hpp"
 
 #include <nlohmann/json.hpp>
@@ -22,7 +23,7 @@ namespace ftl {
 /**
  * Multiple local display options for disparity or point clouds.
  */
-class Display {
+class Display : public ftl::Configurable {
 	private:
 		std::string name_;
 	public:
@@ -48,8 +49,6 @@ class Display {
 	void onKey(std::function<void(int)> h) { key_handlers_.push_back(h); }
 
 	private:
-	nlohmann::json config_;
-
 #if defined HAVE_VIZ
 	cv::viz::Viz3d *window_;
 #endif  // HAVE_VIZ
diff --git a/components/renderers/cpp/src/display.cpp b/components/renderers/cpp/src/display.cpp
index 092872a58608d6514fdff2d1005b16beceec51c5..23288289b01ef181f262f56d6d5cda501cfe146b 100644
--- a/components/renderers/cpp/src/display.cpp
+++ b/components/renderers/cpp/src/display.cpp
@@ -11,7 +11,7 @@ using ftl::Display;
 using cv::Mat;
 using cv::Vec3f;
 
-Display::Display(nlohmann::json &config, std::string name) : config_(config) {
+Display::Display(nlohmann::json &config, std::string name) : ftl::Configurable(config) {
 	name_ = name;
 #if defined HAVE_VIZ
 	window_ = new cv::viz::Viz3d("FTL: " + name);
diff --git a/components/rgbd-sources/include/ftl/rgbd_source.hpp b/components/rgbd-sources/include/ftl/rgbd_source.hpp
index ca7dec6ac18d54716d5a07c1f924b4b097b91548..e6ca35418f80368e5f62adc622203ce09d5962a6 100644
--- a/components/rgbd-sources/include/ftl/rgbd_source.hpp
+++ b/components/rgbd-sources/include/ftl/rgbd_source.hpp
@@ -29,7 +29,7 @@ class RGBDSource : public ftl::Configurable {
 	void getRGBD(cv::Mat &rgb, cv::Mat &depth);
 
 	const CameraParameters &getParameters() { return params_; };
-	std::string getURI() const { return config_["uri"].get<std::string>(); }
+	std::string getURI() const { return config_["$id"].get<std::string>(); }
 
 	virtual void setPose(const Eigen::Matrix4f &pose) { pose_ = pose; };
 	const Eigen::Matrix4f &getPose() { return pose_; };
diff --git a/components/rgbd-sources/src/algorithms/elas.hpp b/components/rgbd-sources/src/algorithms/elas.hpp
index 02f72e3e20b3cf1b94de6fdbc8a55283ba90f3da..29b76cd9ef219013d8a47042a9e2a8529da08ba3 100644
--- a/components/rgbd-sources/src/algorithms/elas.hpp
+++ b/components/rgbd-sources/src/algorithms/elas.hpp
@@ -9,6 +9,7 @@
 #include <opencv2/opencv.hpp>
 #include <elas.h>
 #include <ftl/disparity.hpp>
+#include <ftl/configuration.hpp>
 
 namespace ftl {
 namespace algorithms {
@@ -24,8 +25,8 @@ class ELAS : public ftl::Disparity {
 	void compute(const cv::Mat &l, const cv::Mat &r, cv::Mat &disp);
 
 	/* Factory creator */
-	static inline Disparity *create(nlohmann::json &config) {
-		return new ELAS(config);
+	static inline Disparity *create(ftl::Configurable *p, const std::string &name) {
+		return ftl::create<ELAS>(p, name);
 	}
 
 	private:
diff --git a/components/rgbd-sources/src/algorithms/fixstars_sgm.hpp b/components/rgbd-sources/src/algorithms/fixstars_sgm.hpp
index 4f78280f4e09b6f709c40c0d52285b8f2e09d659..a17ca1bbfdfddb82981ede823c6e878bc1d9c723 100644
--- a/components/rgbd-sources/src/algorithms/fixstars_sgm.hpp
+++ b/components/rgbd-sources/src/algorithms/fixstars_sgm.hpp
@@ -10,6 +10,7 @@
 #include <libsgm.h>
 #include "../disparity.hpp"
 #include <opencv2/cudastereo.hpp>
+#include <ftl/configuration.hpp>
 
 namespace ftl {
 namespace algorithms {
@@ -29,8 +30,8 @@ class FixstarsSGM : public ftl::Disparity {
 	void compute(const cv::Mat &l, const cv::Mat &r, cv::Mat &disp, const cv::Mat &mask_l) override;
 	
 	/* Factory creator */
-	static inline Disparity *create(nlohmann::json &config) {
-		return new FixstarsSGM(config);
+	static inline Disparity *create(ftl::Configurable *p, const std::string &name) {
+		return ftl::create<FixstarsSGM>(p, name);
 	}
 
 	private:
diff --git a/components/rgbd-sources/src/algorithms/nick.hpp b/components/rgbd-sources/src/algorithms/nick.hpp
index ee18f77019ce600d745023084f1fd059258ca9e9..1b5c71ccf07a2757e321845cbd7d423e8e88ff75 100644
--- a/components/rgbd-sources/src/algorithms/nick.hpp
+++ b/components/rgbd-sources/src/algorithms/nick.hpp
@@ -5,6 +5,7 @@
 #include <opencv2/opencv.hpp>
 #include <opencv2/cudastereo.hpp>
 #include "../disparity.hpp"
+#include <ftl/configuration.hpp>
 
 namespace ftl {
 namespace algorithms {
@@ -14,7 +15,9 @@ class NickCuda : public ftl::Disparity {
 	
 	void compute(const cv::Mat &l, const cv::Mat &r, cv::Mat &disp);
 
-	static inline Disparity *create(nlohmann::json &config) { return new NickCuda(config); }
+	static inline Disparity *create(ftl::Configurable *p, const std::string &name) {
+		return ftl::create<NickCuda>(p, name);
+	}
 	
 	private:
 	cv::cuda::GpuMat disp_;
diff --git a/components/rgbd-sources/src/algorithms/opencv_bm.hpp b/components/rgbd-sources/src/algorithms/opencv_bm.hpp
index b890c1b450ebb8263a5f821cdec21c9d707152cb..4a6b5af0c5442acbc45c1662a3f7a7270b84b714 100644
--- a/components/rgbd-sources/src/algorithms/opencv_bm.hpp
+++ b/components/rgbd-sources/src/algorithms/opencv_bm.hpp
@@ -10,6 +10,7 @@
 #include "opencv2/ximgproc.hpp"
 #include <opencv2/calib3d.hpp>
 #include "../disparity.hpp"
+#include <ftl/configuration.hpp>
 
 namespace ftl {
 namespace algorithms {
@@ -23,8 +24,8 @@ class OpenCVBM : public ftl::Disparity {
 	
 	void compute(const cv::Mat &l, const cv::Mat &r, cv::Mat &disp);
 
-	static inline Disparity *create(nlohmann::json &config) {
-		return new OpenCVBM(config);
+	static inline Disparity *create(ftl::Configurable *p, const std::string &name) {
+		return ftl::create<OpenCVBM>(p, name);
 	}
 	
 	private:
diff --git a/components/rgbd-sources/src/algorithms/opencv_cuda_bm.hpp b/components/rgbd-sources/src/algorithms/opencv_cuda_bm.hpp
index 3542c8dd5f3da6f8a8e0f39cf34e7ada4dd05d04..13c88ccd55380e4ef2f0fd74f3a1e668602d29af 100644
--- a/components/rgbd-sources/src/algorithms/opencv_cuda_bm.hpp
+++ b/components/rgbd-sources/src/algorithms/opencv_cuda_bm.hpp
@@ -9,6 +9,7 @@
 #include <opencv2/opencv.hpp>
 #include <opencv2/cudastereo.hpp>
 #include "../disparity.hpp"
+#include <ftl/configuration.hpp>
 
 namespace ftl {
 namespace algorithms {
@@ -22,8 +23,8 @@ class OpenCVCudaBM : public ftl::Disparity {
 	
 	void compute(const cv::Mat &l, const cv::Mat &r, cv::Mat &disp);
 
-	static inline Disparity *create(nlohmann::json &config) {
-		return new OpenCVCudaBM(config);
+	static inline Disparity *create(ftl::Configurable *p, const std::string &name) {
+		return ftl::create<OpenCVCudaBM>(p, name);
 	}
 	
 	private:
diff --git a/components/rgbd-sources/src/algorithms/opencv_cuda_bp.hpp b/components/rgbd-sources/src/algorithms/opencv_cuda_bp.hpp
index 4a096ed30bf753835dd3587b827d5204ce5a4652..3a498552d09ac828863d37c8dff1388e730aef1c 100644
--- a/components/rgbd-sources/src/algorithms/opencv_cuda_bp.hpp
+++ b/components/rgbd-sources/src/algorithms/opencv_cuda_bp.hpp
@@ -9,6 +9,7 @@
 #include <opencv2/opencv.hpp>
 #include <opencv2/cudastereo.hpp>
 #include "../disparity.hpp"
+#include <ftl/configuration.hpp>
 
 namespace ftl {
 namespace algorithms {
@@ -22,8 +23,8 @@ class OpenCVCudaBP : public ftl::Disparity {
 	
 	void compute(const cv::Mat &l, const cv::Mat &r, cv::Mat &disp);
 
-	static inline Disparity *create(nlohmann::json &config) {
-		return new OpenCVCudaBP(config);
+	static inline Disparity *create(ftl::Configurable *p, const std::string &name) {
+		return ftl::create<OpenCVCudaBP>(p, name);
 	}
 	
 	private:
diff --git a/components/rgbd-sources/src/algorithms/opencv_sgbm.hpp b/components/rgbd-sources/src/algorithms/opencv_sgbm.hpp
index 052b331aebd73cf5042223f92b0b489c5c5d1319..4e6437b0fa75093a4ef2a1d0f3fb1dc8130ae16d 100644
--- a/components/rgbd-sources/src/algorithms/opencv_sgbm.hpp
+++ b/components/rgbd-sources/src/algorithms/opencv_sgbm.hpp
@@ -10,6 +10,7 @@
 #include "opencv2/ximgproc.hpp"
 #include <opencv2/calib3d.hpp>
 #include "../disparity.hpp"
+#include <ftl/configuration.hpp>
 
 namespace ftl {
 namespace algorithms {
@@ -23,7 +24,9 @@ class OpenCVSGBM : public ftl::Disparity {
 	
 	void compute(const cv::Mat &l, const cv::Mat &r, cv::Mat &disp);
 
-	static inline Disparity *create(nlohmann::json &config) { return new OpenCVSGBM(config); }
+	static inline Disparity *create(ftl::Configurable *p, const std::string &name) {
+		return ftl::create<OpenCVSGBM>(p, name);
+	}
 	
 	private:
 	cv::Ptr<cv::StereoSGBM> left_matcher_;
diff --git a/components/rgbd-sources/src/algorithms/rtcensus.hpp b/components/rgbd-sources/src/algorithms/rtcensus.hpp
index f4a7d9c26b29c98c831d386ca71c9512d2ad1bfc..0a31a49b108b42ff319a3c61f5f138612160e171 100644
--- a/components/rgbd-sources/src/algorithms/rtcensus.hpp
+++ b/components/rgbd-sources/src/algorithms/rtcensus.hpp
@@ -11,6 +11,7 @@
 #include <nlohmann/json.hpp>
 
 #include <ftl/config.h>
+#include <ftl/configuration.hpp>
 
 #if defined HAVE_CUDA
 #include <opencv2/core/cuda.hpp>
@@ -31,8 +32,8 @@ class RTCensus : public ftl::Disparity {
 	
 	void compute(const cv::Mat &l, const cv::Mat &r, cv::Mat &disp);
 
-	static inline Disparity *create(nlohmann::json &config) {
-		return new RTCensus(config);
+	static inline Disparity *create(ftl::Configurable *p, const std::string &name) {
+		return ftl::create<RTCensus>(p, name);
 	}
 	
 	private:
diff --git a/components/rgbd-sources/src/algorithms/rtcensus_sgm.hpp b/components/rgbd-sources/src/algorithms/rtcensus_sgm.hpp
index c62c21ca8549333c53f836b4090b3b736b125cc3..015c67b08cc4211aee4522d9be5746882a2f6060 100644
--- a/components/rgbd-sources/src/algorithms/rtcensus_sgm.hpp
+++ b/components/rgbd-sources/src/algorithms/rtcensus_sgm.hpp
@@ -9,6 +9,7 @@
 #include <opencv2/opencv.hpp>
 #include "../disparity.hpp"
 #include <nlohmann/json.hpp>
+#include <ftl/configuration.hpp>
 
 #if defined HAVE_CUDA
 #include <opencv2/core/cuda.hpp>
@@ -29,8 +30,8 @@ class RTCensusSGM : public ftl::Disparity {
 	
 	void compute(const cv::Mat &l, const cv::Mat &r, cv::Mat &disp);
 
-	static inline Disparity *create(nlohmann::json &config) {
-		return new RTCensusSGM(config);
+	static inline Disparity *create(ftl::Configurable *p, const std::string &name) {
+		return ftl::create<RTCensusSGM>(p, name);
 	}
 	
 	private:
diff --git a/components/rgbd-sources/src/calibrate.cpp b/components/rgbd-sources/src/calibrate.cpp
index c4bcda7a1dffe9809b8a33c54f9808a472267cdb..3439fe5df9bb08074a28d5b2cc486b1d8168b107 100644
--- a/components/rgbd-sources/src/calibrate.cpp
+++ b/components/rgbd-sources/src/calibrate.cpp
@@ -75,22 +75,22 @@ void Calibrate::Settings::write(FileStorage& fs) const {
 		<< "}";
 }
 
-void Calibrate::Settings::read(const nlohmann::json& node) {
-    boardSize.width = node["board_size"][0];
-    boardSize.height = node["board_size"][1];
-    squareSize = node["square_size"];
-    nrFrames = node["num_frames"];
-    aspectRatio = node["fix_aspect_ratio"];
-    calibZeroTangentDist = node["assume_zero_tangential_distortion"];
-    calibFixPrincipalPoint = node["fix_principal_point_at_center"];
-    useFisheye =  node["use_fisheye_model"];
-    flipVertical = node["flip_vertical"];
-    delay = node["frame_delay"];
-    fixK1 = node["fix_k1"];
-    fixK2 = node["fix_k2"];
-    fixK3 = node["fix_k3"];
-    fixK4 = node["fix_k4"];
-    fixK5 = node["fix_k5"];
+void Calibrate::Settings::read(ftl::Configurable *node) {
+    boardSize.width = node->value<vector<int>>("board_size", {10,10})[0];
+    boardSize.height = node->value<vector<int>>("board_size", {10,10})[1];
+    squareSize = node->value("square_size", 50);
+    nrFrames = node->value("num_frames", 20);
+    aspectRatio = node->value("fix_aspect_ratio", false);
+    calibZeroTangentDist = node->value("assume_zero_tangential_distortion", false);
+    calibFixPrincipalPoint = node->value("fix_principal_point_at_center", false);
+    useFisheye =  node->value("use_fisheye_model", false);
+    flipVertical = node->value("flip_vertical", false);
+    delay = node->value("frame_delay", 1.0f);
+    fixK1 = node->value("fix_k1", false);
+    fixK2 = node->value("fix_k2", false);
+    fixK3 = node->value("fix_k3", false);
+    fixK4 = node->value("fix_k4", true);
+    fixK5 = node->value("fix_k5", true);
 
     validate();
 }
@@ -181,7 +181,7 @@ bool runCalibration(const Calibrate::Settings& s, Size imageSize,
 		bool release_object);
 
 
-Calibrate::Calibrate(ftl::LocalSource *s, nlohmann::json &config) : local_(s) {
+Calibrate::Calibrate(nlohmann::json &config, ftl::LocalSource *s) : ftl::Configurable(config), local_(s) {
     /*FileStorage fs(cal, FileStorage::READ); // Read the settings
     if (!fs.isOpened())
     {
@@ -189,7 +189,7 @@ Calibrate::Calibrate(ftl::LocalSource *s, nlohmann::json &config) : local_(s) {
         return;
     }*/
     // fs["Settings"] >> settings_;
-    settings_.read(config);
+    settings_.read(this);
     // fs.release();
 
     if (!settings_.goodInput) {
diff --git a/components/rgbd-sources/src/calibrate.hpp b/components/rgbd-sources/src/calibrate.hpp
index 0e6a5d81fc0834816c76914b52db4d900d6cf4df..08c0525ea333c25b77f07788f40c0c55a5b77021 100644
--- a/components/rgbd-sources/src/calibrate.hpp
+++ b/components/rgbd-sources/src/calibrate.hpp
@@ -23,7 +23,7 @@ namespace ftl {
  * load any existing cached camera calibration unless explicitely told to
  * redo the calibration.
  */
-class Calibrate {
+class Calibrate : public ftl::Configurable {
 	public:
 	
 	// TODO(nick) replace or remove this class.
@@ -39,7 +39,7 @@ class Calibrate {
 		enum InputType { INVALID, CAMERA, VIDEO_FILE, IMAGE_LIST };
 
 		void write(cv::FileStorage& fs) const;
-		void read(const nlohmann::json& node);
+		void read(ftl::Configurable *node);
 		void validate();
 		//Mat nextImage();
 
@@ -83,7 +83,7 @@ class Calibrate {
 
 	};
 	public:
-	Calibrate(ftl::LocalSource *s, nlohmann::json &config);
+	Calibrate(nlohmann::json &config, ftl::LocalSource *s);
 	
 	/**
 	 * Perform a new camera calibration. Ignore and replace any existing
diff --git a/components/rgbd-sources/src/disparity.cpp b/components/rgbd-sources/src/disparity.cpp
index 5141d438fd3eb299ae2f010373fdd6b46458f0a7..a6af11e7dab463d7b1ade286b024538f27172230 100644
--- a/components/rgbd-sources/src/disparity.cpp
+++ b/components/rgbd-sources/src/disparity.cpp
@@ -5,25 +5,34 @@
 #include "disparity.hpp"
 #include <glog/logging.h>
 #include <ftl/config.h>
+#include <ftl/configuration.hpp>
 
 using ftl::Disparity;
 
-std::map<std::string, std::function<Disparity*(nlohmann::json&)>>
+std::map<std::string, std::function<Disparity*(ftl::Configurable *, const std::string &)>>
 		*Disparity::algorithms__ = nullptr;
 
 Disparity::Disparity(nlohmann::json &config)
-	: 	config_(config),
-		min_disp_(config["minimum"]),
-		max_disp_(config["maximum"]) {}
+	: 	ftl::Configurable(config),
+		min_disp_(value("minimum",0)),
+		max_disp_(value("maximum", 256)) {}
 
-Disparity *Disparity::create(nlohmann::json &config) {
-	if (algorithms__->count(config["algorithm"]) != 1) return nullptr;
-	return (*algorithms__)[config["algorithm"]](config);
+Disparity *Disparity::create(ftl::Configurable *parent, const std::string &name) {
+	nlohmann::json &config = ftl::config::resolve(parent->getConfig()[name]);
+
+	//auto alg = parent->get<std::string>("algorithm");
+	if (!config["algorithm"].is_string()) {
+		return nullptr;
+	}
+	std::string alg = config["algorithm"].get<std::string>();
+
+	if (algorithms__->count(alg) != 1) return nullptr;
+	return (*algorithms__)[alg](parent, name);
 }
 
 void Disparity::_register(const std::string &n,
-		std::function<Disparity*(nlohmann::json&)> f) {
-	if (!algorithms__) algorithms__ = new std::map<std::string, std::function<Disparity*(nlohmann::json&)>>;
+		std::function<Disparity*(ftl::Configurable *, const std::string &)> f) {
+	if (!algorithms__) algorithms__ = new std::map<std::string, std::function<Disparity*(ftl::Configurable *, const std::string &)>>;
 	LOG(INFO) << "Register disparity algorithm: " << n;
 	(*algorithms__)[n] = f;
 }
diff --git a/components/rgbd-sources/src/disparity.hpp b/components/rgbd-sources/src/disparity.hpp
index 33bf58481161a14f8f5d1721a1f600ffb2d9e658..ebe5b9b0849df4a0831679f86600d72a7b348c0b 100644
--- a/components/rgbd-sources/src/disparity.hpp
+++ b/components/rgbd-sources/src/disparity.hpp
@@ -7,6 +7,7 @@
 
 #include <opencv2/opencv.hpp>
 #include <nlohmann/json.hpp>
+#include <ftl/configurable.hpp>
 
 namespace ftl {
 
@@ -16,7 +17,7 @@ namespace ftl {
  * interface, for this to work a static instance of the Register class must
  * be created in the algorithms cpp file.
  */
-class Disparity {
+class Disparity : public ftl::Configurable {
 	public:
 	explicit Disparity(nlohmann::json &config);
 	
@@ -37,7 +38,7 @@ class Disparity {
 	 */
 	class Register {
 		public:
-		Register(const std::string &n, std::function<Disparity*(nlohmann::json&)> f) {
+		Register(const std::string &n, std::function<Disparity*(ftl::Configurable *, const std::string &)> f) {
 			Disparity::_register(n,f);
 		};
 	};
@@ -46,18 +47,18 @@ class Disparity {
 	 * Factory instance creator where config contains an "algorithm" property
 	 * used as the instance name to construct.
 	 */
-	static Disparity *create(nlohmann::json &config);
+	static Disparity *create(ftl::Configurable *, const std::string &);
 	
 	protected:
-	static void _register(const std::string &n, std::function<Disparity*(nlohmann::json&)> f);
+	static void _register(const std::string &n, std::function<Disparity*(ftl::Configurable *, const std::string &)> f);
 	
 	protected:
-	nlohmann::json &config_;
+	//nlohmann::json &config_;
 	size_t min_disp_;
 	size_t max_disp_;
 	
 	private:
-	static std::map<std::string,std::function<Disparity*(nlohmann::json&)>> *algorithms__;
+	static std::map<std::string,std::function<Disparity*(ftl::Configurable *, const std::string &)>> *algorithms__;
 };
 };
 
diff --git a/components/rgbd-sources/src/local.cpp b/components/rgbd-sources/src/local.cpp
index 80190115fda19bc7ad749ccafa6dfb743af035e8..f7ed7ebd20b8596c6d739ce238e8e8c65babc10f 100644
--- a/components/rgbd-sources/src/local.cpp
+++ b/components/rgbd-sources/src/local.cpp
@@ -36,10 +36,10 @@ LocalSource::LocalSource(nlohmann::json &config)
 		{"scale","Change the input image or video scaling","number"}
 	});
 
-	flip_ = config["flip"].get<bool>();
-	flip_v_ = config["flip_vert"].get<bool>();
-	nostereo_ = config["nostereo"].get<bool>();
-	downsize_ = config["scale"].get<float>();
+	flip_ = value("flip", false);
+	flip_v_ = value("flip_vert", false);
+	nostereo_ = value("nostereo", false);
+	downsize_ = value("scale", 1.0f);
 
 	// Use cameras
 	camera_a_ = new VideoCapture;
@@ -67,10 +67,10 @@ LocalSource::LocalSource(nlohmann::json &config)
 		stereo_ = false;
 		LOG(WARNING) << "Not able to find second camera for stereo";
 	} else {
-		camera_a_->set(cv::CAP_PROP_FRAME_WIDTH,(int)config["width"]);
-		camera_a_->set(cv::CAP_PROP_FRAME_HEIGHT,(int)config["height"]);
-		camera_b_->set(cv::CAP_PROP_FRAME_WIDTH,(int)config["width"]);
-		camera_b_->set(cv::CAP_PROP_FRAME_HEIGHT,(int)config["height"]);
+		camera_a_->set(cv::CAP_PROP_FRAME_WIDTH, value("width", 640));
+		camera_a_->set(cv::CAP_PROP_FRAME_HEIGHT, value("height", 480));
+		camera_b_->set(cv::CAP_PROP_FRAME_WIDTH, value("width", 640));
+		camera_b_->set(cv::CAP_PROP_FRAME_HEIGHT, value("height", 480));
 
 		Mat frame;
 		camera_a_->grab();
@@ -81,10 +81,10 @@ LocalSource::LocalSource(nlohmann::json &config)
 		stereo_ = true;
 	}
 
-	tps_ = 1.0 / (double)config["max_fps"];
+	tps_ = 1.0 / value("max_fps", 25.0);
 }
 
-LocalSource::LocalSource(const string &vid, nlohmann::json &config)
+LocalSource::LocalSource(nlohmann::json &config, const string &vid)
 	:	Configurable(config), timestamp_(0.0) {
 
 	REQUIRED({
@@ -97,10 +97,10 @@ LocalSource::LocalSource(const string &vid, nlohmann::json &config)
 		{"scale","Change the input image or video scaling","number"}
 	});
 
-	flip_ = config["flip"].get<bool>();
-	flip_v_ = config["flip_vert"].get<bool>();
-	nostereo_ = config["nostereo"].get<bool>();
-	downsize_ = config["scale"].get<float>();
+	flip_ = value("flip", false);
+	flip_v_ = value("flip_vert", false);
+	nostereo_ = value("nostereo", false);
+	downsize_ = value("scale", 1.0f);
 
 	if (vid == "") {
 		LOG(FATAL) << "No video file specified";
@@ -137,7 +137,7 @@ LocalSource::LocalSource(const string &vid, nlohmann::json &config)
 		stereo_ = false;
 	}
 
-	tps_ = 1.0 / (double)config["max_fps"];
+	tps_ = 1.0 / value("max_fps", 25.0);
 }
 
 bool LocalSource::left(cv::Mat &l) {
diff --git a/components/rgbd-sources/src/local.hpp b/components/rgbd-sources/src/local.hpp
index d483ea185cf0a6db28d9da09fd55c2f1e17822c2..d3286684b70528c7c1e375e21d2e805b93a29818 100644
--- a/components/rgbd-sources/src/local.hpp
+++ b/components/rgbd-sources/src/local.hpp
@@ -14,7 +14,7 @@ namespace ftl {
 class LocalSource : public Configurable {
 	public:
 	explicit LocalSource(nlohmann::json &config);
-	LocalSource(const std::string &vid, nlohmann::json &config);
+	LocalSource(nlohmann::json &config, const std::string &vid);
 	
 	bool left(cv::Mat &m);
 	bool right(cv::Mat &m);
diff --git a/components/rgbd-sources/src/net_source.cpp b/components/rgbd-sources/src/net_source.cpp
index a4e3c9f0a7b9f9b1d3edcd0d8188c09b8ddb18a0..7a9d3cbe21835daed8a704204f238639c15c0203 100644
--- a/components/rgbd-sources/src/net_source.cpp
+++ b/components/rgbd-sources/src/net_source.cpp
@@ -42,16 +42,21 @@ NetSource::NetSource(nlohmann::json &config) : RGBDSource(config) {
 NetSource::NetSource(nlohmann::json &config, ftl::net::Universe *net)
 		: RGBDSource(config, net) {
 
-	auto p = net->findOne<ftl::UUID>("find_stream", getURI());
+	auto uri = get<string>("uri");
+	if (!uri) {
+		LOG(ERROR) << "NetSource does not have a URI";
+		return;
+	}
+	auto p = net->findOne<ftl::UUID>("find_stream", *uri);
 	if (!p) {
-		LOG(ERROR) << "Could not find stream: " << getURI();
+		LOG(ERROR) << "Could not find stream: " << *uri;
 		return;
 	}
 	peer_ = *p;
 
-	has_calibration_ = _getCalibration(*net, peer_, getURI(), params_);
+	has_calibration_ = _getCalibration(*net, peer_, *uri, params_);
 	
-	net->bind(getURI(), [this](const vector<unsigned char> &jpg, const vector<unsigned char> &d) {
+	net->bind(*uri, [this](const vector<unsigned char> &jpg, const vector<unsigned char> &d) {
 		unique_lock<mutex> lk(mutex_);
 		_recv(jpg, d);
 	});
@@ -59,7 +64,7 @@ NetSource::NetSource(nlohmann::json &config, ftl::net::Universe *net)
 	N_ = 10;
 
 	// Initiate stream with request for first 10 frames
-	net->send(peer_, "get_stream", getURI(), 10, 0, net->id(), getURI());
+	net->send(peer_, "get_stream", *uri, 10, 0, net->id(), *uri);
 }
 
 NetSource::~NetSource() {
@@ -75,7 +80,7 @@ void NetSource::_recv(const vector<unsigned char> &jpg, const vector<unsigned ch
 	N_--;
 	if (N_ == 0) {
 		N_ += 10;
-		net_->send(peer_, "get_stream", getURI(), 10, 0, net_->id(), getURI());
+		net_->send(peer_, "get_stream", *get<string>("uri"), 10, 0, net_->id(), *get<string>("uri"));
 	}
 }
 
diff --git a/components/rgbd-sources/src/rgbd_streamer.cpp b/components/rgbd-sources/src/rgbd_streamer.cpp
index b4e256169d598587a637e35624750d8ed3370f90..8cf1e8f96c919aef4c5eb8e118e11272aeab70b5 100644
--- a/components/rgbd-sources/src/rgbd_streamer.cpp
+++ b/components/rgbd-sources/src/rgbd_streamer.cpp
@@ -81,6 +81,8 @@ void Streamer::add(RGBDSource *src) {
 	s->src = src;
 	s->state = 0;
 	sources_[src->getURI()] = s;
+
+	LOG(INFO) << "Streaming: " << src->getURI();
 }
 
 void Streamer::_addClient(const string &source, int N, int rate, const ftl::UUID &peer, const string &dest) {
diff --git a/components/rgbd-sources/src/stereovideo_source.cpp b/components/rgbd-sources/src/stereovideo_source.cpp
index e254099c728371b686566e97107f8f03cc38bcd7..6264caff28ee5458125c518e80ef56c8907ffffb 100644
--- a/components/rgbd-sources/src/stereovideo_source.cpp
+++ b/components/rgbd-sources/src/stereovideo_source.cpp
@@ -22,29 +22,34 @@ StereoVideoSource::StereoVideoSource(nlohmann::json &config, const string &file)
 		: RGBDSource(config), ready_(false) {
 
 	REQUIRED({
-		{"source","Details on source video [object]","object"}
+		{"feed","Details on source video [object]","object"}
 	});
 	
 	if (ftl::is_video(file)) {
 		// Load video file
 		LOG(INFO) << "Using video file...";
-		lsrc_ = new LocalSource(file, config["source"]);
+		//lsrc_ = new LocalSource(file, config["source"]);
+		lsrc_ = ftl::create<LocalSource>(this, "feed", file);
 	} else if (file != "") {
 		auto vid = ftl::locateFile("video.mp4");
 		if (!vid) {
-			LOG(FATAL) << "No video.mp4 file found in provided paths";
+			LOG(FATAL) << "No video.mp4 file found in provided paths (" << file << ")";
 		} else {
 			LOG(INFO) << "Using test directory...";
-			lsrc_ = new LocalSource(*vid, config["source"]);
+			//lsrc_ = new LocalSource(*vid, config["source"]);
+			lsrc_ = ftl::create<LocalSource>(this, "feed", *vid);
 		}
 	} else {
 		// Use cameras
 		LOG(INFO) << "Using cameras...";
-		lsrc_ = new LocalSource(config["source"]);
+		//lsrc_ = new LocalSource(config["source"]);
+		lsrc_ = ftl::create<LocalSource>(this, "feed");
 	}
 
-	calib_ = new Calibrate(lsrc_, config["calibration"]);
-	if (config["calibrate"]) calib_->recalibrate();
+	//calib_ = new Calibrate(lsrc_, ftl::resolve(config["calibration"]));
+	calib_ = ftl::create<Calibrate>(this, "calibration", lsrc_);
+
+	if (value("calibrate", false)) calib_->recalibrate();
 	if (!calib_->isCalibrated()) LOG(WARNING) << "Cameras are not calibrated!";
 	else LOG(INFO) << "Calibration initiated.";
 
@@ -69,7 +74,7 @@ StereoVideoSource::StereoVideoSource(nlohmann::json &config, const string &file)
 	calib_->rectifyStereo(mask_l, mask_r);
 	mask_l_ = (mask_l == 0);
 	
-	disp_ = Disparity::create(config["disparity"]);
+	disp_ = Disparity::create(this, "disparity");
     if (!disp_) LOG(FATAL) << "Unknown disparity algorithm : " << config["disparity"];
 
 	LOG(INFO) << "StereoVideo source ready...";
diff --git a/config/config.json b/config/config.json
deleted file mode 100644
index 4b8f8f75c3e55e51e1af9cfaea77dd1aefa026bb..0000000000000000000000000000000000000000
--- a/config/config.json
+++ /dev/null
@@ -1,101 +0,0 @@
-{
-	"vision": {
-		"type": "stereovideo",
-		"uri": "ftl://utu.fi/dummy/rgb-d",
-		"middlebury": {
-			"dataset": "",
-			"threshold": 10.0,
-			"scale": 0.25
-		},
-		"source": {
-			"flip": false,
-			"nostereo": false,
-			"scale": 1.0,
-			"flip_vert": false,
-			"max_fps": 25,
-			"width": 640,
-			"height": 480,
-			"crosshair": false
-		},
-		"calibrate": false,
-		"calibration": {
-			"board_size": [9,6],
-			"square_size": 50,
-			"frame_delay": 1.0,
-			"num_frames": 35,
-			"assume_zero_tangential_distortion": true,
-			"fix_aspect_ratio": true,
-			"fix_principal_point_at_center": true,
-			"use_fisheye_model": false,
-			"fix_k1": false,
-			"fix_k2": false,
-			"fix_k3": false,
-			"fix_k4": true,
-			"fix_k5": true,
-			"save": true,
-			"use_intrinsics": true,
-			"use_extrinsics": true,
-			"flip_vertical": false
-		},
-		"camera": {
-			"name": "Panasonic Lumix DMC-FZ300",
-			"focal_length": 25,
-			"sensor_width": 6.17,
-			"base_line": 0.1
-		},
-		"disparity": {
-			"algorithm": "rtcensus",
-			"use_cuda": true,
-			"minimum": 0,
-			"maximum": 208,
-			"tau": 0.0,
-			"gamma": 0.0,
-			"window_size": 5,
-			"sigma": 1.5,
-			"lambda": 8000.0,
-			"use_filter": true,
-			"filter_radius": 20,
-			"filter_iter": 3
-		},
-		"display": {
-			"flip_vert": false,
-			"disparity": false,
-			"points": true,
-			"depth": false,
-			"left": false,
-			"right": false,
-			"crosshair": false
-		},
-		"net": {
-			"listen": "tcp://*:9001",
-			"peers": []
-		},
-		"stream": {
-			"name": "dummy"
-		}
-	},
-	"reconstruction": {
-		"net": {
-			"peers": ["tcp://localhost:9001"]
-		},
-		"sources": [{"type": "net", "uri": "ftl://utu.fi/node1/rgb-d"}],
-		"display": {
-			"flip_vert": false,
-			"disparity": false,
-			"points": true,
-			"depth": false,
-			"left": false,
-			"right": false
-		},
-		"registration": {
-			"reference-source" : "ftl://utu.fi/node1/rgb-d",
-			"calibration" : {
-				"run": false,
-				"iterations" : 10,
-				"delay" : 1000,
-				"patternsize" : [9, 6]
-				}
-		}
-	}
-}
-
diff --git a/config/config.jsonc b/config/config.jsonc
new file mode 100644
index 0000000000000000000000000000000000000000..f83dde531bf82ef348b3ea964c919e7c77a157cb
--- /dev/null
+++ b/config/config.jsonc
@@ -0,0 +1,193 @@
+{
+	"$id": "ftl://utu.fi",
+	"$schema": "",
+	"calibrations": {
+		"default": {
+			"board_size": [9,6],
+			"square_size": 50,
+			"frame_delay": 1.0,
+			"num_frames": 35,
+			"assume_zero_tangential_distortion": true,
+			"fix_aspect_ratio": true,
+			"fix_principal_point_at_center": true,
+			"use_fisheye_model": false,
+			"fix_k1": false,
+			"fix_k2": false,
+			"fix_k3": false,
+			"fix_k4": true,
+			"fix_k5": true,
+			"save": true,
+			"use_intrinsics": true,
+			"use_extrinsics": true,
+			"flip_vertical": false
+		}
+	},
+	"disparity": {
+		"libsgm": {
+			"algorithm": "libsgm",
+			"use_cuda": true,
+			"minimum": 0,
+			"maximum": 256,
+			"tau": 0.0,
+			"gamma": 0.0,
+			"window_size": 5,
+			"sigma": 1.5,
+			"lambda": 8000.0
+		}
+	},
+	"sources": {
+		"stereocam": {
+			"type": "stereovideo",
+			"feed": {
+				"flip": false,
+				"nostereo": false,
+				"scale": 1.0,
+				"flip_vert": false,
+				"max_fps": 500,
+				"width": 640,
+				"height": 480,
+				"crosshair": false
+			},
+			"calibrate": false,
+			"calibration": { "$ref": "#calibrations/default" },
+			"disparity": { "$ref": "#disparity/libsgm" }
+		},
+		"stereovid": {},
+		"localhost": {}
+		
+	},
+	// Listen to localhost
+	"net": {
+		"default_vision": {
+			"listen": "tcp://*:9001",
+			"peers": []
+		},
+		"default_reconstruct": {
+			"listen": "tcp://*:9002",
+			"peers": []
+		}
+	},
+	"displays": {
+		"none": {
+			"flip_vert": false,
+			"disparity": false,
+			"points": false,
+			"depth": false,
+			"left": false,
+			"right": false
+		},
+		"left": {
+			"flip_vert": false,
+			"disparity": false,
+			"points": false,
+			"depth": false,
+			"left": true,
+			"right": false
+		}
+	},
+	"middlebury": {
+		"none": {
+			"dataset": "",
+			"threshold": 10.0,
+			"scale": 0.25
+		}
+	},
+	"virtual_cams": {
+		"default": {
+			"hash_renderer": true,
+			"SDFRayIncrementFactor": 0.8,
+			"SDFTruncation": 0.1,
+			"SDFRayThresSampleDistFactor": 50.5,
+			"SDFRayThresDistFactor": 50.0,
+			"focal": 400,
+			"width": 640,
+			"height": 480,
+			"max_depth": 10.0,
+			"min_depth": 0.1,
+			"SDFUseGradients": false,
+			"uri": "ftl://utu.fi/virt1/rgb-d"
+		}
+	},
+	"hash_conf": {
+		"default": {
+			"adapterWidth": 640,
+			"adapterHeight": 480,
+			"sensorDepthMax": 20.0,
+			"sensorDepthMin": 0.2,
+			"SDFRayIncrementFactor": 0.8,
+			"SDFRayThresSampleDistFactor": 50.5,
+			"SDFRayThresDistFactor": 50.0,
+			"SDFUseGradients": false,
+			"hashNumBuckets": 50000,
+			"hashMaxCollisionLinkedListSize": 7,
+			"hashNumSDFBlocks": 1000000,
+			"SDFVoxelSize": 0.005,
+			"SDFMaxIntegrationDistance": 12.0,
+			"SDFTruncation": 0.1,
+			"SDFTruncationScale": 0.05,
+			"SDFIntegrationWeightSample": 10,
+			"SDFIntegrationWeightMax": 255,
+			"hash_renderer": true
+		}
+	},
+
+	"vision_default": {
+		"source": { "$ref": "#sources/stereocam" },
+		"middlebury": { "$ref": "#middlebury/none" },
+		"display": { "$ref": "#displays/none" },
+		"net": { "$ref": "#net/default_vision" },
+		"stream": {}
+	},
+
+	"reconstruction_default": {
+		"net": {
+			"peers": ["tcp://localhost:9001"]
+		},
+		"sources": [
+			{"type": "net", "uri":"ftl://utu.fi/vision_default/source"}
+		],
+		"display": { "$ref": "#displays/left" },
+		"virtual": { "$ref": "#virtual_cams/default" },
+		"voxelhash": { "$ref": "#hash_conf/default" },
+		"registration": {
+			"reference-source" : "ftl://utu.fi/vision_default/source",
+			"calibration" : {
+				"max_error": 25,
+				"run": false,
+				"iterations" : 10,
+				"delay" : 500,
+				"patternsize" : [9, 6]
+				}
+		}
+	},
+
+	"reconstruction_lab": {
+		"net": {
+			"peers": ["tcp://ftl-node-4:9001"]
+		},
+		"sources": [
+			{"type": "net", "uri":"ftl://utu.fi/node4"}
+		],
+		"display": { "$ref": "#displays/left" },
+		"virtual": { "$ref": "#virtual_cams/default" },
+		"voxelhash": { "$ref": "#hash_conf/default" },
+		"registration": {
+			"reference-source" : "ftl://utu.fi/node4",
+			"calibration" : {
+				"max_error": 25,
+				"run": false,
+				"iterations" : 10,
+				"delay" : 500,
+				"patternsize" : [9, 6]
+				}
+		}
+	},
+
+
+	"ftl://gui/default": {
+		"net": {
+			"peers": ["tcp://ftl-node-4:9001"]
+		},
+		"sources": [{"type": "net", "uri": "ftl://utu.fi/node/rgb-d"}]
+	}
+}
diff --git a/config/config_nick.jsonc b/config/config_nick.jsonc
new file mode 100644
index 0000000000000000000000000000000000000000..a34e849c69523877bc9785c147147b0b8429762b
--- /dev/null
+++ b/config/config_nick.jsonc
@@ -0,0 +1,137 @@
+{
+	"$id": "ftl://utu.fi",
+	"$schema": "",
+	"calibrations": {
+		"default": {
+			"board_size": [9,6],
+			"square_size": 50,
+			"frame_delay": 1.0,
+			"num_frames": 35,
+			"assume_zero_tangential_distortion": true,
+			"fix_aspect_ratio": true,
+			"fix_principal_point_at_center": true,
+			"use_fisheye_model": false,
+			"fix_k1": false,
+			"fix_k2": false,
+			"fix_k3": false,
+			"fix_k4": true,
+			"fix_k5": true,
+			"save": true,
+			"use_intrinsics": true,
+			"use_extrinsics": true,
+			"flip_vertical": false
+		}
+	},
+	"disparity": {
+		"libsgm": {
+			"algorithm": "libsgm",
+			"use_cuda": true,
+			"minimum": 0,
+			"maximum": 256,
+			"tau": 0.0,
+			"gamma": 0.0,
+			"window_size": 5,
+			"sigma": 1.5,
+			"lambda": 8000.0
+		}
+	},
+	"sources": {
+		"stereocam": {},
+		"stereovid": {},
+		"localhost": {}
+		
+	},
+	// Listen to localhost
+	"net": {
+		"default": {
+			"listen": "tcp://*:9001",
+			"peers": []
+		}
+	},
+	"vision_default": {
+		"source": {
+			"type": "stereovideo",
+			"feed": {
+				"flip": false,
+				"nostereo": false,
+				"scale": 1.0,
+				"flip_vert": false,
+				"max_fps": 500,
+				"width": 640,
+				"height": 480,
+				"crosshair": false
+			},
+			"calibrate": false,
+			"calibration": { "$ref": "#calibrations/default" },
+			"disparity": { "$ref": "#disparity/libsgm" }
+		},
+		"middlebury": {
+			"dataset": "",
+			"threshold": 10.0,
+			"scale": 0.25
+		},
+		"display": {
+			"flip_vert": false,
+			"disparity": false,
+			"points": false,
+			"depth": false,
+			"left": false,
+			"right": false
+		},
+		"net": { "$ref": "#net/default" },
+		"stream": {
+		}
+	},
+	"ftl://reconstruction/default": {
+		"net": {
+			"peers": ["tcp://ftl-node-5:9001"]
+		},
+		"sources": [
+			{"type": "net", "uri":"ftl://utu.fi/node5/rgb-d"}
+		],
+		"display": {
+			"flip_vert": false,
+			"disparity": false,
+			"points": false,
+			"depth": false,
+			"left": true,
+			"right": false
+		},
+		"voxelhash": {
+			"adapterWidth": 640,
+			"adapterHeight": 480,
+			"sensorDepthMax": 20.0,
+			"sensorDepthMin": 0.2,
+			"SDFRayIncrementFactor": 0.8,
+			"SDFRayThresSampleDistFactor": 50.5,
+			"SDFRayThresDistFactor": 50.0,
+			"SDFUseGradients": false,
+			"hashNumBuckets": 50000,
+			"hashMaxCollisionLinkedListSize": 7,
+			"hashNumSDFBlocks": 1000000,
+			"SDFVoxelSize": 0.005,
+			"SDFMaxIntegrationDistance": 12.0,
+			"SDFTruncation": 0.1,
+			"SDFTruncationScale": 0.05,
+			"SDFIntegrationWeightSample": 10,
+			"SDFIntegrationWeightMax": 255,
+			"hash_renderer": true
+		},
+		"registration": {
+			"reference-source" : "ftl://utu.fi/node5/rgb-d",
+			"calibration" : {
+				"max_error": 25,
+				"run": false,
+				"iterations" : 10,
+				"delay" : 500,
+				"patternsize" : [9, 6]
+				}
+		}
+	},
+	"ftl://gui/default": {
+		"net": {
+			"peers": ["tcp://ftl-node-4:9001"]
+		},
+		"sources": [{"type": "net", "uri": "ftl://utu.fi/node/rgb-d"}]
+	}
+}
diff --git a/config/config_node.jsonc b/config/config_node.jsonc
new file mode 100644
index 0000000000000000000000000000000000000000..2e8923444d302d5e0c91b8e681e9b372478e4fb2
--- /dev/null
+++ b/config/config_node.jsonc
@@ -0,0 +1,11 @@
+{
+	"$id": "ftl://utu.fi/node1",
+	"$schema": "http://json-schema.org/draft-07/schema",
+	"title": "Node 1",
+	"description": "Dev lab camera node 1",
+	"net": {
+		"peers": [],
+		"listen": ["tcp://*:9001"]
+	},
+	"sources": { "$ref": "ftl://utu.fi/dev_lab#sources" }
+}
\ No newline at end of file