diff --git a/applications/calibration/src/common.cpp b/applications/calibration/src/common.cpp
index 9a44ee28ecc5241c28cc2bfed0b49b16b10a4b5e..49967754dc0dd4f6665db7dae9bd9c5e965e35d3 100644
--- a/applications/calibration/src/common.cpp
+++ b/applications/calibration/src/common.cpp
@@ -62,10 +62,10 @@ bool saveExtrinsics(const string &ofile, Mat &R, Mat &T, Mat &R1, Mat &R2, Mat &
 	return false;
 }
 
-bool saveIntrinsics(const string &ofile, const Mat &M, const Mat& D) {
+bool saveIntrinsics(const string &ofile, const vector<Mat> &M, const vector<Mat>& D) {
 	cv::FileStorage fs(ofile, cv::FileStorage::WRITE);
 	if (fs.isOpened()) {
-		fs << "M" << M << "D" << D;
+		fs << "K" << M << "D" << D;
 		fs.release();
 		return true;
 	}
@@ -75,7 +75,7 @@ bool saveIntrinsics(const string &ofile, const Mat &M, const Mat& D) {
 	return false;
 }
 
-bool loadIntrinsics(const string &ifile, Mat &M1, Mat &D1) {
+bool loadIntrinsics(const string &ifile, vector<Mat> &K1, vector<Mat> &D1) {
 	using namespace cv;
 
 	FileStorage fs;
@@ -89,7 +89,7 @@ bool loadIntrinsics(const string &ifile, Mat &M1, Mat &D1) {
 	
 	LOG(INFO) << "Intrinsics from: " << ifile;
 
-	fs["M"] >> M1;
+	fs["M"] >> K1;
 	fs["D"] >> D1;
 
 	return true;
@@ -188,7 +188,14 @@ CalibrationChessboard::CalibrationChessboard(const map<string, string> &opt) {
 	LOG(INFO) << "  square_size: " << pattern_square_size_;
 	LOG(INFO) << "-----------------------------------";
 
-	// CALIB_CB_NORMALIZE_IMAGE | CALIB_CB_EXHAUSTIVE | CALIB_CB_ACCURACY 
+	// From OpenCV (4.1.0) Documentation
+	//
+	// CALIB_CB_NORMALIZE_IMAGE	Normalize the image gamma with equalizeHist before detection.
+	// CALIB_CB_EXHAUSTIVE		Run an exhaustive search to improve detection rate.
+	// CALIB_CB_ACCURACY		Up sample input image to improve sub-pixel accuracy due to
+	//							aliasing effects. This should be used if an accurate camera
+	//							calibration is required.
+
 	chessboard_flags_ = cv::CALIB_CB_NORMALIZE_IMAGE | cv::CALIB_CB_ACCURACY;
 }
 
diff --git a/applications/calibration/src/common.hpp b/applications/calibration/src/common.hpp
index ad904a5eaea58afc760bdc1b97e86671182304ff..274698b32b51ae9b741b94e25b492ab059637e37 100644
--- a/applications/calibration/src/common.hpp
+++ b/applications/calibration/src/common.hpp
@@ -15,8 +15,8 @@ int getOptionInt(const std::map<std::string, std::string> &options, const std::s
 double getOptionDouble(const std::map<std::string, std::string> &options, const std::string &opt, double default_value);
 std::string getOptionString(const std::map<std::string, std::string> &options, const std::string &opt, std::string default_value);
 
-bool loadIntrinsics(const std::string &ifile, cv::Mat &M1, cv::Mat &D1);
-bool saveIntrinsics(const std::string &ofile, const cv::Mat &M1, const cv::Mat &D1);
+bool loadIntrinsics(const std::string &ifile, std::vector<cv::Mat> &K, std::vector<cv::Mat> &D);
+bool saveIntrinsics(const std::string &ofile, const std::vector<cv::Mat> &K, const std::vector<cv::Mat> &D);
 
 // TODO loadExtrinsics()
 bool saveExtrinsics(const std::string &ofile, cv::Mat &R, cv::Mat &T, cv::Mat &R1, cv::Mat &R2, cv::Mat &P1, cv::Mat &P2, cv::Mat &Q);
diff --git a/applications/calibration/src/lens.cpp b/applications/calibration/src/lens.cpp
index db7fc810b46b4aef95edd7161a9d032cbe8f6cfd..924787a740ce46e0d4caf31c6630f38d15a02244 100644
--- a/applications/calibration/src/lens.cpp
+++ b/applications/calibration/src/lens.cpp
@@ -1,6 +1,8 @@
 #include "common.hpp"
 #include "lens.hpp"
 
+#include <ftl/config.h>
+
 #include <loguru.hpp>
 
 #include <opencv2/core.hpp>
@@ -28,88 +30,126 @@ void ftl::calibration::intrinsic(map<string, string> &opt) {
 	LOG(INFO) << "Begin intrinsic calibration";
 
 	// TODO PARAMETERS TO CONFIG FILE
-	Size image_size = Size(	getOptionInt(opt, "width", 1280),
+	const Size image_size = Size(	getOptionInt(opt, "width", 1280),
 							getOptionInt(opt, "height", 720));
-	int iter = getOptionInt(opt, "iter", 60);
-	int delay = getOptionInt(opt, "delay", 750);
-	string filename_intrinsics = getOptionString(opt, "profile", "./panasonic.yml");
+	const int n_cameras = getOptionInt(opt, "n_cameras", 2);
+	const int iter = getOptionInt(opt, "iter", 60);
+	const int delay = getOptionInt(opt, "delay", 750);
+	const double aperture_width = getOptionDouble(opt, "aperture_width", 6.2);
+	const double aperture_height = getOptionDouble(opt, "aperture_height", 4.6);
+	const string filename_intrinsics = getOptionString(opt, "profile", FTL_LOCAL_CONFIG_ROOT "/intrinsics.yml");
 	CalibrationChessboard calib(opt);
 
 	LOG(INFO) << "Intrinsic calibration parameters";
-	LOG(INFO) << "     profile: " << filename_intrinsics;
-	LOG(INFO) << "       width: " << image_size.width;
-	LOG(INFO) << "      height: " << image_size.height;
-	LOG(INFO) << "        iter: " << iter;
-	LOG(INFO) << "       delay: " << delay;
+	LOG(INFO) << "         profile: " << filename_intrinsics;
+	LOG(INFO) << "       n_cameras: " << n_cameras;
+	LOG(INFO) << "           width: " << image_size.width;
+	LOG(INFO) << "          height: " << image_size.height;
+	LOG(INFO) << "            iter: " << iter;
+	LOG(INFO) << "           delay: " << delay;
+	LOG(INFO) << "  aperture_width: " << aperture_width;
+	LOG(INFO) << " aperture_height: " << aperture_height;
 	LOG(INFO) << "-----------------------------------";
 
 	int calibrate_flags = cv::CALIB_ZERO_TANGENT_DIST | cv::CALIB_FIX_ASPECT_RATIO;
 	// PARAMETERS
 
-	cv::VideoCapture camera = cv::VideoCapture(0);
-
-	if (!camera.isOpened()) {
-		LOG(ERROR) << "Could not open camera device";
-		return;
+	vector<cv::VideoCapture> cameras;
+	cameras.reserve(n_cameras);
+	for (int c = 0; c < n_cameras; c++) { cameras.emplace_back(c); }
+	for (auto &camera : cameras) {
+		if (!camera.isOpened()) {
+			LOG(ERROR) << "Could not open camera device";
+			return;
+		}
+		camera.set(cv::CAP_PROP_FRAME_WIDTH, image_size.width); 
+		camera.set(cv::CAP_PROP_FRAME_HEIGHT, image_size.height);
 	}
-	camera.set(cv::CAP_PROP_FRAME_WIDTH, image_size.width); 
-	camera.set(cv::CAP_PROP_FRAME_HEIGHT, image_size.height);
 
-	vector<vector<Vec2f>> image_points;
-	vector<vector<Vec3f>> object_points;
-	
-	while (iter > 0) {
-		Mat img;
-		vector<Vec2f> points;
-		
-		camera.grab();
-		camera.retrieve(img);
-		
-		bool found = calib.findPoints(img, points);
-		if (found) { calib.drawPoints(img, points); }
+	vector<vector<vector<Vec2f>>> image_points(n_cameras);
+	vector<vector<vector<Vec3f>>> object_points(n_cameras);
+	vector<Mat> img(n_cameras);
+	vector<int> count(n_cameras, 0);
 
-		cv::imshow("Camera", img);
-		cv::waitKey(delay);
+	while (iter > *std::min_element(count.begin(), count.end())) {
 
-		if (!found) { continue; }
+		for (auto &camera : cameras) { camera.grab(); }
 
-		vector<Vec3f> points_ref;
-		calib.objectPoints(points_ref);
+		for (int c = 0; c < n_cameras; c++) {
+			vector<Vec2f> points;
+			cameras[c].retrieve(img[c]);
+		
+			if (calib.findPoints(img[c], points)) {
+				calib.drawPoints(img[c], points);
+				count[c]++;
+			}
+			else { continue; }
+		
+			vector<Vec3f> points_ref;
+			calib.objectPoints(points_ref);
 
-		Mat camera_matrix, dist_coeffs;
-		vector<Mat> rvecs, tvecs;
+			Mat camera_matrix, dist_coeffs;
+			vector<Mat> rvecs, tvecs;
 		
-		image_points.push_back(points);
-		object_points.push_back(points_ref);
+			image_points[c].push_back(points);
+			object_points[c].push_back(points_ref);
+		}
+		
+		for (int c = 0; c < n_cameras; c++) {
+			cv::imshow("Camera " + std::to_string(c), img[c]);
+		}
 
-		iter--;
+		cv::waitKey(delay);
 	}
 	
-	Mat camera_matrix, dist_coeffs;
-	vector<Mat> rvecs, tvecs;
-	
-	double rms = cv::calibrateCamera(
-						object_points, image_points,
-						image_size, camera_matrix, dist_coeffs,
-						rvecs, tvecs, calibrate_flags
-	);
+	vector<Mat> camera_matrix(n_cameras), dist_coeffs(n_cameras);
 
-	LOG(INFO) << "final reprojection RMS error: " << rms;
+	for (int c = 0; c < n_cameras; c++) {
+		LOG(INFO) << "Calculating intrinsic paramters for camera " << std::to_string(c);
+		vector<Mat> rvecs, tvecs;
+		
+		double rms = cv::calibrateCamera(
+							object_points[c], image_points[c],
+							image_size, camera_matrix[c], dist_coeffs[c],
+							rvecs, tvecs, calibrate_flags
+		);
+
+		LOG(INFO) << "final reprojection RMS error: " << rms;
+
+		double fovx, fovy, focal_length, aspect_ratio;
+		cv::Point2d principal_point;
+
+		// TODO: check for valid aperture width/height; do not run if not valid values
+		cv::calibrationMatrixValues(camera_matrix[c], image_size, aperture_width, aperture_height,
+									fovx, fovy, focal_length, principal_point, aspect_ratio);
 		
+		LOG(INFO) << "";
+		LOG(INFO) << "            fovx (deg): " << fovx;
+		LOG(INFO) << "            fovy (deg): " << fovy;
+		LOG(INFO) << "     focal length (mm): " << focal_length;
+		LOG(INFO) << "  principal point (mm): " << principal_point;
+		LOG(INFO) << "          aspect ratio: " << aspect_ratio;
+		LOG(INFO) << "";
+		LOG(INFO) << "Camera matrix:\n" << camera_matrix[c];
+		LOG(INFO) << "Distortion coefficients:\n" << dist_coeffs[c];
+		LOG(INFO) << "";
+	}
+
 	saveIntrinsics(filename_intrinsics, camera_matrix, dist_coeffs);
 	LOG(INFO) << "intrinsic paramaters saved to: " << filename_intrinsics;
 	
-	Mat map1, map2;
-	cv::initUndistortRectifyMap(camera_matrix, dist_coeffs, Mat::eye(3,3, CV_64F), camera_matrix,
-								image_size, CV_16SC2, map1, map2);
-
+	vector<Mat> map1(n_cameras), map2(n_cameras);
+	for (int c = 0; c < n_cameras; c++) {
+		cv::initUndistortRectifyMap(camera_matrix[c], dist_coeffs[c], Mat::eye(3,3, CV_64F), camera_matrix[c],
+									image_size, CV_16SC2, map1[c], map2[c]);
+	}
+	
 	while (cv::waitKey(25) != 27) {
-		Mat img, img_out;
-		
-		camera.grab();
-		camera.retrieve(img);
-		cv::remap(img, img_out, map1, map2, cv::INTER_CUBIC);
-
-		cv::imshow("Camera", img_out);
+		for (auto &camera : cameras ) {	camera.grab(); }
+		for (int c = 0; c < n_cameras; c++) {
+			cameras[c].retrieve(img[c]);
+			cv::remap(img[c], img[c], map1[c], map2[c], cv::INTER_CUBIC);
+			cv::imshow("Camera " + std::to_string(c), img[c]);
+		}
 	}
 }
diff --git a/applications/calibration/src/stereo.cpp b/applications/calibration/src/stereo.cpp
index 59b37bc1f70601a37365099bd1cc750db9c3df5c..c009d6dd0311c27bc952e7daba18afc76e30288f 100644
--- a/applications/calibration/src/stereo.cpp
+++ b/applications/calibration/src/stereo.cpp
@@ -28,7 +28,7 @@ void ftl::calibration::stereo(map<string, string> &opt) {
 	Size image_size = Size(	getOptionInt(opt, "width", 1280),
 							getOptionInt(opt, "height", 720));
 	// iterations
-	int iter = getOptionInt(opt, "iter", 3);
+	int iter = getOptionInt(opt, "iter", 50);
 	// delay between images
 	double delay = getOptionInt(opt, "delay", 250);
 	// max_error for a single image; if error larger image discarded
@@ -38,7 +38,7 @@ void ftl::calibration::stereo(map<string, string> &opt) {
 	// intrinsics filename
 	string filename_intrinsics = getOptionString(opt, "profile", "./panasonic.yml");
 
-	bool use_grid = (bool) getOptionInt(opt, "use_grid", 1);
+	bool use_grid = (bool) getOptionInt(opt, "use_grid", 0);
 
 	LOG(INFO) << "Stereo calibration parameters";
 	LOG(INFO) << "     profile: " << filename_intrinsics;
@@ -94,9 +94,7 @@ void ftl::calibration::stereo(map<string, string> &opt) {
 	vector<Mat> dist_coeffs(2);
 	vector<Mat> camera_matrices(2);
 
-	// assume identical cameras; load intrinsic parameters
-	if (!(	loadIntrinsics(filename_intrinsics, camera_matrices[0], dist_coeffs[0]) &&
-			loadIntrinsics(filename_intrinsics, camera_matrices[1], dist_coeffs[1]))) {
+	if (!loadIntrinsics(filename_intrinsics, camera_matrices, dist_coeffs)) {
 		LOG(FATAL) << "Failed to load intrinsic camera parameters from file.";
 	}
 	
diff --git a/components/common/cpp/include/ftl/configuration.hpp b/components/common/cpp/include/ftl/configuration.hpp
index 3333ed5764818f487323d688944950a11cde359f..c060b70382d0174d351d836d5476fa5a1f29db61 100644
--- a/components/common/cpp/include/ftl/configuration.hpp
+++ b/components/common/cpp/include/ftl/configuration.hpp
@@ -116,66 +116,70 @@ using config::configure;
 
 template <typename T, typename... ARGS>
 T *ftl::config::create(json_t &link, ARGS ...args) {
-    //auto &r = link; // = ftl::config::resolve(link);
-
-    if (!link["$id"].is_string()) {
-        LOG(FATAL) << "Entity does not have $id or parent: " << link;
-        return nullptr;
-    }
-
-    ftl::Configurable *cfg = ftl::config::find(link["$id"].get<std::string>());
-    if (!cfg) {
-        try {
-            cfg = new T(link, args...);
-        } catch(...) {
-            LOG(FATAL) << "Could not construct " << link;
-        }
-    } else {
-        // Make sure configurable has newest object pointer
-        cfg->patchPtr(link);
-    }
-
-    try {
-        return dynamic_cast<T*>(cfg);
-    } catch(...) {
-        LOG(FATAL) << "Configuration URI object is of wrong type: " << link;
-        return nullptr;
-    }
+	//auto &r = link; // = ftl::config::resolve(link);
+
+	if (!link["$id"].is_string()) {
+		LOG(FATAL) << "Entity does not have $id or parent: " << link;
+		return nullptr;
+	}
+
+	ftl::Configurable *cfg = ftl::config::find(link["$id"].get<std::string>());
+	if (!cfg) {
+		try {
+			cfg = new T(link, args...);
+		} catch (std::exception &ex) {	
+			LOG(ERROR) << ex.what();
+			LOG(FATAL) << "Could not construct " << link;
+		} catch(...) {
+			LOG(ERROR) << "Unknown exception";
+			LOG(FATAL) << "Could not construct " << link;
+		}
+	} else {
+		// Make sure configurable has newest object pointer
+		cfg->patchPtr(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 = (!parent->getConfig()[name].is_null())
+	nlohmann::json &entity = (!parent->getConfig()[name].is_null())
 			? parent->getConfig()[name]
 			: ftl::config::resolve(parent->getConfig())[name];
 
-    if (entity.is_object()) {
-        if (!entity["$id"].is_string()) {
-            std::string id_str = *parent->get<std::string>("$id");
-            if (id_str.find('#') != std::string::npos) {
-                entity["$id"] = id_str + std::string("/") + name;
-            } else {
-                entity["$id"] = id_str + std::string("#") + name;
-            }
-        }
-
-        return create<T>(entity, args...);
-    } else if (entity.is_null()) {
-        // Must create the object from scratch...
-        std::string id_str = *parent->get<std::string>("$id");
-        if (id_str.find('#') != std::string::npos) {
-            id_str = id_str + std::string("/") + name;
-        } else {
-            id_str = id_str + std::string("#") + name;
-        }
-        parent->getConfig()[name] = {
+	if (entity.is_object()) {
+		if (!entity["$id"].is_string()) {
+			std::string id_str = *parent->get<std::string>("$id");
+			if (id_str.find('#') != std::string::npos) {
+				entity["$id"] = id_str + std::string("/") + name;
+			} else {
+				entity["$id"] = id_str + std::string("#") + name;
+			}
+		}
+
+		return create<T>(entity, args...);
+	} else if (entity.is_null()) {
+		// Must create the object from scratch...
+		std::string id_str = *parent->get<std::string>("$id");
+		if (id_str.find('#') != std::string::npos) {
+			id_str = id_str + std::string("/") + name;
+		} else {
+			id_str = id_str + std::string("#") + name;
+		}
+		parent->getConfig()[name] = {
 			// cppcheck-suppress constStatement
-            {"$id", id_str}
-        };
+			{"$id", id_str}
+		};
 
-        nlohmann::json &entity2 = parent->getConfig()[name];
-        return create<T>(entity2, args...);
-    }
+		nlohmann::json &entity2 = parent->getConfig()[name];
+		return create<T>(entity2, args...);
+	}
 
 	LOG(ERROR) << "Unable to create Configurable entity '" << name << "'";
 	return nullptr;
@@ -183,7 +187,7 @@ T *ftl::config::create(ftl::Configurable *parent, const std::string &name, ARGS
 
 template <typename T, typename... ARGS>
 std::vector<T*> ftl::config::createArray(ftl::Configurable *parent, const std::string &name, ARGS ...args) {
-    nlohmann::json &base = (!parent->getConfig()[name].is_null())
+	nlohmann::json &base = (!parent->getConfig()[name].is_null())
 			? parent->getConfig()[name]
 			: ftl::config::resolve(parent->getConfig())[name];
 
diff --git a/components/rgbd-sources/src/calibrate.cpp b/components/rgbd-sources/src/calibrate.cpp
index cfe164baacf16ad722188aa1ecd72564a4db19f0..6a1ce062085f0833c58aa308d0579fd707aceb02 100644
--- a/components/rgbd-sources/src/calibrate.cpp
+++ b/components/rgbd-sources/src/calibrate.cpp
@@ -78,11 +78,21 @@ bool Calibrate::_loadCalibration(cv::Size img_size, std::pair<Mat, Mat> &map1, s
 		return false;
 	}
 
-	fs["M"] >> M1_;
-	fs["D"] >> D1_;
+	{
+		vector<Mat> K, D;
+		fs["K"] >> K;
+		fs["D"] >> D;
+		
+		K[0].copyTo(M1_);
+		K[1].copyTo(M2_);
+		D[0].copyTo(D1_);
+		D[1].copyTo(D2_);
+	}
 
-	M2_ = M1_;
-	D2_ = D1_;
+	CHECK(M1_.size() == Size(3, 3));
+	CHECK(M2_.size() == Size(3, 3));
+	CHECK(D1_.size() == Size(5, 1));
+	CHECK(D2_.size() == Size(5, 1));
 
 	auto efile = ftl::locateFile("extrinsics.yml");
 	if (efile) {
@@ -106,8 +116,6 @@ bool Calibrate::_loadCalibration(cv::Size img_size, std::pair<Mat, Mat> &map1, s
 	fs["P2"] >> P2_;
 	fs["Q"] >> Q_;
 
-	P_ = P1_;
-
 	img_size_ = img_size;
 
 	// cv::cuda::remap() works only with CV_32FC1
diff --git a/components/rgbd-sources/src/calibrate.hpp b/components/rgbd-sources/src/calibrate.hpp
index a621c3dab9c5c54020ce6bbaf3a10c50bdeb81b6..c5f4786831edb77435a4d205702e7909517829a9 100644
--- a/components/rgbd-sources/src/calibrate.hpp
+++ b/components/rgbd-sources/src/calibrate.hpp
@@ -63,7 +63,6 @@ private:
 	std::pair<cv::cuda::GpuMat, cv::cuda::GpuMat> map1_gpu_;
 	std::pair<cv::cuda::GpuMat, cv::cuda::GpuMat> map2_gpu_;
 
-	cv::Mat P_;
 	cv::Mat Q_;
 	cv::Mat R_, T_, R1_, P1_, R2_, P2_;
 	cv::Mat M1_, D1_, M2_, D2_;