diff --git a/applications/gui2/src/modules/calibration/extrinsic.cpp b/applications/gui2/src/modules/calibration/extrinsic.cpp
index a12aa4b93e2bf4f117847859938f590d753ac1eb..6b006ab2aa74d98da43441b6ae1df1d09afad0b2 100644
--- a/applications/gui2/src/modules/calibration/extrinsic.cpp
+++ b/applications/gui2/src/modules/calibration/extrinsic.cpp
@@ -299,23 +299,23 @@ void ExtrinsicCalibration::run() {
 		try {
 			state_.calib.run();
 			LOG(INFO) << "Before:"; // DEBUG INFO
-			for (int c1 = 0; c1 < cameraCount(); c1++) {
-			for (int c2 = c1; c2 < cameraCount(); c2++) {
+			for (int c1 = 0; c1 < cameraCount(); c1 += 2) {
+
 				auto t1 = state_.calib.calibration(c1).extrinsic.tvec;
-				auto t2 = state_.calib.calibration(c2).extrinsic.tvec;
+				auto t2 = state_.calib.calibration(c1+1).extrinsic.tvec;
 
-				LOG(INFO) << "baseline (" << c1 << ", " << c2 << "): "
+				LOG(INFO) << "baseline (" << c1 << ", " << c1+1 << "): "
 						  << cv::norm(t1, t2);
-			}}
+			}
 			LOG(INFO) << "After:"; // DEBUG INFO
-			for (int c1 = 0; c1 < cameraCount(); c1++) {
-			for (int c2 = c1; c2 < cameraCount(); c2++) {
-				auto t1 = state_.calib.calibrationOptimized(c1).extrinsic.tvec;
-				auto t2 = state_.calib.calibrationOptimized(c2).extrinsic.tvec;
+			for (int c1 = 0; c1 < cameraCount(); c1 += 2) {
 
-				LOG(INFO) << "baseline (" << c1 << ", " << c2 << "): "
+				auto t1 = state_.calib.calibration(c1).extrinsic.tvec;
+				auto t2 = state_.calib.calibration(c1+1).extrinsic.tvec;
+
+				LOG(INFO) << "baseline (" << c1 << ", " << c1+1 << "): "
 						  << cv::norm(t1, t2);
-			}}
+			}
 
 			// Rectification maps for visualization; stereo cameras assumed
 			// if non-stereo cameras added visualization/grouping (by index)
diff --git a/applications/gui2/src/screen.cpp b/applications/gui2/src/screen.cpp
index 5ec71e4c224e89253aff5acfc9b35bb7415364ee..fe7747306860f942bb33fa83512fa4326fa7cf5f 100644
--- a/applications/gui2/src/screen.cpp
+++ b/applications/gui2/src/screen.cpp
@@ -45,8 +45,7 @@ Screen::Screen() :
 		toolbar_->setFixedSize({toolbar_->width(), s[1]});
 		toolbar_->setPosition({0, 0});
 		if (active_view_) {
-			active_view_->setFixedSize(viewSize());
-			// active_view_->setFixedSize({s[0], s[1]});
+			active_view_->setSize(viewSize(s));
 		}
 		performLayout();
 	});
@@ -98,10 +97,15 @@ void Screen::redraw() {
 	glfwPostEmptyEvent();
 }
 
+nanogui::Vector2i Screen::viewSize(const nanogui::Vector2i &ws) {
+	return {ws.x() - toolbar_->width(), ws.y()};
+}
+
 nanogui::Vector2i Screen::viewSize() {
-	return {width() - toolbar_->width(), height()};
+	return viewSize(size());
 }
 
+
 void Screen::showError(const std::string&title, const std::string& msg) {
 	if (msgerror_) { return; }
 	msgerror_ = new nanogui::MessageDialog
@@ -208,4 +212,4 @@ void Screen::drawAll() {
 	}
 	last_draw_time_ = now;
 	nanogui::Screen::drawAll();
-}
\ No newline at end of file
+}
diff --git a/applications/gui2/src/screen.hpp b/applications/gui2/src/screen.hpp
index b46f6dc464eae00f9f93c9834c95e285a08dae8a..a5e47d6a3fab057a8e8633a012f53511f7dc6c25 100644
--- a/applications/gui2/src/screen.hpp
+++ b/applications/gui2/src/screen.hpp
@@ -73,13 +73,14 @@ public:
 	nanogui::Color getColor(const std::string &name);
 	void setColor(const std::string &name, const nanogui::Color &c);
 
-	nanogui::Vector2i viewSize();
-
 	// Implement in View or Screen? Add ID (address of creating instance)
 	// to each error to prevent spam?
 	/** Show error message popup */
 	void showError(const std::string& title, const std::string &msg);
 
+	nanogui::Vector2i viewSize(const nanogui::Vector2i &ws);
+	nanogui::Vector2i viewSize();
+
 private:
 	Module* addModule_(const std::string &name, Module* ptr);
 
diff --git a/applications/gui2/src/view.cpp b/applications/gui2/src/view.cpp
index 9b14660197b7fd3fd83f09e7d5ee02040d56cddc..67297b6b439a7d59bb6617a65ce8914b40a47d1a 100644
--- a/applications/gui2/src/view.cpp
+++ b/applications/gui2/src/view.cpp
@@ -5,7 +5,6 @@
 
 using ftl::gui2::View;
 
-View::View(Screen* screen) : nanogui::Widget(screen) {
-	setFixedSize(screen->viewSize());
-	screen_ = screen;
+View::View(Screen* screen) : nanogui::Widget(screen), screen_(screen) {
+	setSize(screen_->viewSize());
 }
diff --git a/components/calibration/include/ftl/calibration/extrinsic.hpp b/components/calibration/include/ftl/calibration/extrinsic.hpp
index f68ad4f7f4cdb7ba6cb622e095bdce5fec72c49e..58f954fa25e573340217efea4bd02938c06c4400 100644
--- a/components/calibration/include/ftl/calibration/extrinsic.hpp
+++ b/components/calibration/include/ftl/calibration/extrinsic.hpp
@@ -1,5 +1,7 @@
 #pragma once
 
+#include <ftl/utility/msgpack.hpp>
+
 #include <ftl/calibration/visibility.hpp>
 #include <ftl/calibration/structures.hpp>
 #include <ftl/calibration/optimize.hpp>
@@ -89,6 +91,8 @@ public:
 	/** Get all points. See Points struct. */
 	const std::vector<Points>& all() { return points_; }
 
+
+
 protected:
 	bool hasCamera(unsigned int c);
 	void setCamera(unsigned int c);
@@ -208,6 +212,9 @@ private:
 	// TODO: add map {c1,c2} for existing calibration which is used if available.
 	//
 	std::shared_ptr<std::string> status_;
+
+	float threshold_bad_ = 0.25; // theshold for point to be skipped (cm)
+	float threhsold_warning_ = 0.1; // theshold for warning message (%)
 };
 
 
diff --git a/components/calibration/include/ftl/calibration/structures.hpp b/components/calibration/include/ftl/calibration/structures.hpp
index ab8544763a4c5c9f650e3ad08ff9dd5889da7b6e..e0eeb2fcea82b491637b580680d0398813f90137 100644
--- a/components/calibration/include/ftl/calibration/structures.hpp
+++ b/components/calibration/include/ftl/calibration/structures.hpp
@@ -77,6 +77,8 @@ struct CalibrationData {
 		void set(const cv::Mat &T);
 		void set(cv::InputArray R, cv::InputArray t);
 
+		Extrinsic inverse() const;
+
 		/** get as a 4x4 matrix */
 		cv::Mat matrix() const;
 		/** get 3x3 rotation matrix */
diff --git a/components/calibration/src/extrinsic.cpp b/components/calibration/src/extrinsic.cpp
index e3c3fbf80c37909c77ffeb70f8bf2e5b52bd268a..971ca74ec0b59bfc8d45a4be5fad05ff3c079433 100644
--- a/components/calibration/src/extrinsic.cpp
+++ b/components/calibration/src/extrinsic.cpp
@@ -318,7 +318,6 @@ double calibratePair(const cv::Mat &K1, const cv::Mat &D1,
 	double s = ftl::calibration::optimizeScale(object_points, points_out);
 	t = t * s;
 
-	//transform::inverse(R, t);
 	auto params1 = Camera(K1, D1, cv::Mat::eye(3, 3, CV_64FC1), cv::Mat::zeros(3, 1, CV_64FC1), {0, 0});
 	auto params2 = Camera(K2, D2, R, t, {0, 0});
 
@@ -416,8 +415,10 @@ void ExtrinsicCalibration::calculatePairPoses() {
 		auto object = points().getObject(0);
 		cv::Mat R, t;
 		std::vector<cv::Point3d> points3d;
-		calibratePair(K1, distCoeffs1, K2, distCoeffs2, pts[0], pts[1], object,
-					  R, t, points3d, true);
+		double rms = calibratePair(K1, distCoeffs1, K2, distCoeffs2,
+			pts[0], pts[1], object, R, t, points3d, true);
+
+		LOG(INFO) << "Pair error " << rms;
 
 		points().setTriangulatedPoints(c1, c2, points3d);
 
@@ -487,6 +488,28 @@ static std::vector<bool> visibility(unsigned int ncameras, uint64_t visible) {
 	return res;
 }
 
+static cv::Point3d absdiff(const std::vector<double> &xs, const std::vector<double> &ys, const std::vector<double> &zs) {
+	double minx = INFINITY;
+	double maxx = -INFINITY;
+	for (auto x : xs) {
+		minx = std::min(minx, x);
+		maxx = std::max(maxx, x);
+	}
+	double miny = INFINITY;
+	double maxy = -INFINITY;
+	for (auto y : ys) {
+		miny = std::min(miny, y);
+		maxy = std::max(maxy, y);
+	}
+	double minz = INFINITY;
+	double maxz = -INFINITY;
+	for (auto z : zs) {
+		minz = std::min(minz, z);
+		maxz = std::max(maxz, z);
+	}
+	return {abs(minx - maxx), abs(miny - maxy), abs(minz - maxz)};
+}
+
 double ExtrinsicCalibration::optimize() {
 
 	BundleAdjustment ba;
@@ -495,8 +518,9 @@ double ExtrinsicCalibration::optimize() {
 	unsigned int ncameras = calib_.size();
 
 	for (const auto& c : calib_) {
-		cameras.push_back(Camera(c));
-		cameras.back().setDistortion(cv::Mat(cv::Size{12, 1}, CV_64FC1, 0.0));
+		auto cam = c;
+		cameras.push_back(Camera(cam));
+		//cameras.back().setDistortion(cv::Mat(cv::Size{12, 1}, CV_64FC1, 0.0));
 	}
 	for (auto &c : cameras ) {
 		ba.addCamera(c);
@@ -507,7 +531,6 @@ double ExtrinsicCalibration::optimize() {
 	// inverse transformations, as points are transformed from camera to world
 	// (instead the other way around by parameters in cameras[]).
 
-	std::vector<cv::Point3d> pointsw;
 	std::vector<cv::Mat> T;
 	for (const auto &c : cameras) {
 		T.push_back(c.extrinsicMatrixInverse());
@@ -521,13 +544,21 @@ double ExtrinsicCalibration::optimize() {
 	//		 suited as they can be easily interpreted as flat arrays or
 	//		 multi-dimensional.
 
+	int n_points_bad = 0;
+	int n_points_missing = 0;
+	int n_points = 0;
+
 	for (const auto& itm : points_.all()) {
 		auto sz = points_.getObject(itm.object).size();
 		auto vis = visibility(ncameras, itm.cameras);
 
 		for (unsigned int i = 0; i < sz; i++) {
-			// observation and triangulated coordinates
-			std::vector<cv::Point2d> obs(ncameras, {0, 0});
+			n_points++;
+
+			// observation and triangulated coordinates; Use {NAN, NAN} for
+			// non-visible points (if those are used by mistake, Ceres will
+			// fail with error message).
+			std::vector<cv::Point2d> obs(ncameras, {NAN, NAN});
 			std::vector<double> px;
 			std::vector<double> py;
 			std::vector<double> pz;
@@ -547,10 +578,12 @@ double ExtrinsicCalibration::optimize() {
 			std::sort(px.begin(), px.end());
 			std::sort(py.begin(), py.end());
 			std::sort(pz.begin(), pz.end());
+			cv::Point3d p;
 
 			unsigned int n = px.size();
 			unsigned int m = n / 2;
 			if (m == 0) {
+				n_points_missing++;
 				break;
 				// not triangulated (see earlier steps)
 				// TODO: triangulate here
@@ -559,18 +592,42 @@ double ExtrinsicCalibration::optimize() {
 				// mean of two points if number of points even
 				cv::Point3d p1 = {px[m - 1], py[m - 1], pz[m - 1]};
 				cv::Point3d p2 = {px[m], py[m], pz[m]};
-				pointsw.push_back((p1 + p2)/2.0);
+				p = (p1 + p2)/2.0;
 			}
 			else {
-				pointsw.push_back({px[m], py[m], pz[m]});
+				p = {px[m], py[m], pz[m]};
 			}
 
-			ba.addPoint(vis, obs, pointsw.back());
+			if (cv::norm(absdiff(px, py, pz)) > threshold_bad_) {
+				n_points_bad++;
+				continue;
+			}
+
+			ba.addPoint(vis, obs, p);
 		}
 	}
 
+	if (float(n_points_bad)/float(n_points - n_points_missing) > threhsold_warning_) {
+		// print wanrning message; calibration possibly fails if triangulation
+		// was very low quality (more than 10% bad points)
+		LOG(ERROR) << "Large variation in "<< n_points_bad << " "
+					  "triangulated points. Are initial intrinsic parameters "
+					  "good?";
+	}
+
+	if (float(n_points_missing)/float(n_points - n_points_bad) > threhsold_warning_) {
+		// low number of points; most points only visible in pairs?
+		LOG(WARNING) << "Large number of points skipped. Are there enough "
+						"visible points between stereo camera pairs? (TODO: "
+						"implement necessary triangulation after pair "
+						"calibration)";
+	}
+
 	updateStatus_("Bundle adjustment");
 	options_.verbose = true;
+	for (unsigned int i = 0; i < cameras.size(); i++) {
+		LOG(INFO) << "RMS (camera " << i << ": " << ba.reprojectionError(i);
+	}
 	ba.run(options_);
 
 	calib_optimized_.resize(calib_.size());
@@ -580,6 +637,7 @@ double ExtrinsicCalibration::optimize() {
 		calib_optimized_[i] = calib_[i];
 		calib_optimized_[i].intrinsic.set(intr.matrix(), intr.distCoeffs.Mat(), intr.resolution);
 		calib_optimized_[i].extrinsic.set(extr.rvec, extr.tvec);
+		LOG(INFO) << "RMS (camera " << i << ": " << ba.reprojectionError(i);
 	}
 
 	return ba.reprojectionError();
diff --git a/components/calibration/src/optimize.cpp b/components/calibration/src/optimize.cpp
index 7b3030363298081281d155a899a987e6c1ece9e0..da7680e7e84a07b690f3febc4f724cd9fffefdae 100644
--- a/components/calibration/src/optimize.cpp
+++ b/components/calibration/src/optimize.cpp
@@ -38,6 +38,7 @@ struct ReprojectionError {
 	 *
 	 * Camera model documented in
 	 * https://docs.opencv.org/master/d9/d0c/group__calib3d.html
+	 * https://github.com/opencv/opencv/blob/b698d0a6ee12342a87b8ad739d908fd8d7ff1fca/modules/calib3d/src/calibration.cpp#L774
 	 */
 	explicit ReprojectionError(double observed_x, double observed_y)
 		: observed_x(observed_x), observed_y(observed_y) {}
@@ -78,8 +79,6 @@ struct ReprojectionError {
 		const T r4 = r2*r2;
 		const T r6 = r4*r2;
 
-		// see also https://github.com/opencv/opencv/blob/b698d0a6ee12342a87b8ad739d908fd8d7ff1fca/modules/calib3d/src/calibration.cpp#L774
-
 		// Radial distortion
 		const T cdist = T(1.0) + k1*r2 + k2*r4 + k3*r6;
 		// Radial distortion: rational model
@@ -91,8 +90,8 @@ struct ReprojectionError {
 		const T xd = x*cdist*icdist + pdistx;
 		const T yd = y*cdist*icdist + pdisty;
 		// Projected point position
-		T predicted_x = f*xd + cx;
-		T predicted_y = f*yd + cy;
+		T predicted_x = f*x/*xd*/ + cx;
+		T predicted_y = f*y/*yd*/ + cy;
 
 		// Error: the difference between the predicted and observed position
 		residuals[0] = predicted_x - T(observed_x);
diff --git a/components/calibration/src/parameters.cpp b/components/calibration/src/parameters.cpp
index 5549f75bded8fa9efdb279a60901a199b038ab50..2c9c2f1a37c55350845f12bc4410c552bb95cefb 100644
--- a/components/calibration/src/parameters.cpp
+++ b/components/calibration/src/parameters.cpp
@@ -169,7 +169,7 @@ Mat Camera::extrinsicMatrix() const {
 }
 
 Mat Camera::extrinsicMatrixInverse() const {
-	return extrinsicMatrix().inv();
+	return transform::inverse(extrinsicMatrix());
 }
 
 ////////////////////////////////////////////////////////////////////////////////
diff --git a/components/calibration/src/structures.cpp b/components/calibration/src/structures.cpp
index ce67daec3950cf36cd79df215e56d99bbabdc282..a7c49886067784ac4ffc07da611d6eb34eac96c1 100644
--- a/components/calibration/src/structures.cpp
+++ b/components/calibration/src/structures.cpp
@@ -91,15 +91,14 @@ void CalibrationData::Extrinsic::set(const cv::Mat& T) {
 	if (T.type() != CV_64FC1) {
 		throw ftl::exception("Input must be CV_64FC1");
 	}
-	if (ftl::calibration::validate::pose(T)) {
+	if (!ftl::calibration::validate::pose(T)) {
 		throw ftl::exception("T is not a valid transform matrix");
 	}
 
 	cv::Rodrigues(T(cv::Rect(0, 0, 3, 3)), rvec);
 	tvec[0] = T.at<double>(0, 3);
 	tvec[1] = T.at<double>(1, 3);
-	tvec[2] = T.at<double>(3, 3);
-	tvec[3] = 1.0;
+	tvec[2] = T.at<double>(2, 3);
 }
 
 void CalibrationData::Extrinsic::set(cv::InputArray R, cv::InputArray t) {
@@ -156,6 +155,12 @@ cv::Mat CalibrationData::Extrinsic::matrix() const {
 	return T;
 }
 
+ CalibrationData::Extrinsic CalibrationData::Extrinsic::inverse() const {
+	cv::Mat T = matrix();
+	ftl::calibration::transform::inverse(T);
+	return CalibrationData::Extrinsic(T);
+}
+
 cv::Mat CalibrationData::Extrinsic::rmat() const {
 	cv::Mat R(cv::Size(3, 3), CV_64FC1, cv::Scalar(0));
 	cv::Rodrigues(rvec, R);
diff --git a/components/common/cpp/include/ftl/utility/msgpack.hpp b/components/common/cpp/include/ftl/utility/msgpack.hpp
index ca95fd90ddfb85004d3e07646a909ba625a1aed0..3e13df14462ea4edc3210a6c0b8aae55ca6e5649 100644
--- a/components/common/cpp/include/ftl/utility/msgpack.hpp
+++ b/components/common/cpp/include/ftl/utility/msgpack.hpp
@@ -149,7 +149,7 @@ struct object_with_zone<cv::Vec<T, SIZE>> {
 };
 
 ////////////////////////////////////////////////////////////////////////////////
-// cv::Point_ and cv::Point3_
+// cv::Point_
 
 template<typename T>
 struct pack<cv::Point_<T>> {
@@ -190,6 +190,9 @@ struct object_with_zone<cv::Point_<T>> {
 	}
 };
 
+////////////////////////////////////////////////////////////////////////////////
+// cv::Point3_
+
 template<typename T>
 struct pack<cv::Point3_<T>> {
 	template <typename Stream>