diff --git a/applications/gui2/src/modules/calibration/calibration.hpp b/applications/gui2/src/modules/calibration/calibration.hpp
index a55f3d77dbe78ca0bea068d6aca07f8dde236768..00b33b3ec5288c7f49469f8c51002e2a33efa372 100644
--- a/applications/gui2/src/modules/calibration/calibration.hpp
+++ b/applications/gui2/src/modules/calibration/calibration.hpp
@@ -262,6 +262,7 @@ public:
 	int cameraCount();
 
 	std::string cameraName(int camera);
+	std::vector<std::string> cameraNames();
 
 	ftl::calibration::ExtrinsicCalibration& calib();
 
diff --git a/applications/gui2/src/modules/calibration/extrinsic.cpp b/applications/gui2/src/modules/calibration/extrinsic.cpp
index 21246352da2d5ebf8686ad0b15b22ec5f57aa569..b0ced5bb10af804b3c3a1f999c4fa344bf2a69d6 100644
--- a/applications/gui2/src/modules/calibration/extrinsic.cpp
+++ b/applications/gui2/src/modules/calibration/extrinsic.cpp
@@ -121,6 +121,15 @@ std::string ExtrinsicCalibration::cameraName(int c) {
 		((camera.channel == Channel::Left) ? "Left" : "Right");
 }
 
+std::vector<std::string> ExtrinsicCalibration::cameraNames() {
+	std::vector<std::string> names;
+	names.reserve(cameraCount());
+	for (int i = 0; i < cameraCount(); i++) {
+		names.push_back(cameraName(i));
+	}
+	return names;
+}
+
 CalibrationData::Calibration ExtrinsicCalibration::calibration(int c) {
 	return state_.calib.calibrationOptimized(c);
 }
diff --git a/applications/gui2/src/views/calibration/extrinsicview.cpp b/applications/gui2/src/views/calibration/extrinsicview.cpp
index 2b8be17b76664a700e65ab712ca017ce06e1dbba..c637ab249563d615984f2ab60f8c43862df5f65a 100644
--- a/applications/gui2/src/views/calibration/extrinsicview.cpp
+++ b/applications/gui2/src/views/calibration/extrinsicview.cpp
@@ -407,6 +407,9 @@ class ExtrinsicCalibrationView::ResultsWindow : public FixedWindow {
 public:
 	ResultsWindow(nanogui::Widget* parent, ExtrinsicCalibrationView* view);
 	virtual void draw(NVGcontext* ctx) override;
+	virtual void performLayout(NVGcontext* ctx);
+	//virtual nanogui::Vector2i preferredSize(NVGcontext* ctx) const override;
+
 	void update();
 
 private:
@@ -416,7 +419,7 @@ private:
 	std::vector<ftl::calibration::CalibrationData::Calibration> calib_;
 	std::vector<std::string> names_;
 
-	nanogui::TabWidget* tabs_;
+	nanogui::TabWidget* tabs_ = nullptr;
 
 public:
 	EIGEN_MAKE_ALIGNED_OPERATOR_NEW
@@ -425,6 +428,9 @@ public:
 ExtrinsicCalibrationView::ResultsWindow::ResultsWindow(nanogui::Widget* parent, ExtrinsicCalibrationView* view) :
 	FixedWindow(parent, "Results"), view_(view), ctrl_(view->ctrl_) {
 
+	setLayout(new nanogui::BoxLayout
+		(nanogui::Orientation::Vertical, nanogui::Alignment::Maximum));
+
 	(new nanogui::Button(buttonPanel(), "", ENTYPO_ICON_CROSS))->setCallback(
 		[view = view_]() {
 		view->setMode(Mode::VIDEO);
@@ -432,20 +438,35 @@ ExtrinsicCalibrationView::ResultsWindow::ResultsWindow(nanogui::Widget* parent,
 
 	tabs_ = new nanogui::TabWidget(this);
 	tabs_->createTab("Extrinsic");
+}
 
+/*nanogui::Vector2i ExtrinsicCalibrationView::ResultsWindow::preferredSize(NVGcontext* ctx) const {
+	return {600, 400};
+}*/
+
+void ExtrinsicCalibrationView::ResultsWindow::ResultsWindow::performLayout(NVGcontext* ctx) {
 	setFixedSize({600, 400});
-	tabs_->setFixedSize(fixedSize());
+	tabs_->setFixedWidth(width());
+	FixedWindow::performLayout(ctx);
 }
 
 void ExtrinsicCalibrationView::ResultsWindow::ResultsWindow::update() {
 	calib_.resize(ctrl_->cameraCount());
 	while (tabs_->tabCount() > 1) {
-		tabs_->removeTab(tabs_->tabCount() - 1);
+		// bug in nanogui: incorrect assert in removeTab(int).
+		// workaround: use tabLabelAt()
+		tabs_->removeTab(tabs_->tabLabelAt(tabs_->tabCount() - 1));
 	}
 
 	for (int i = 0; i < ctrl_->cameraCount(); i++) {
 		calib_[i] = ctrl_->calibration(i);
+		// nanogui issue: too many tabs/long names header goes outside of widget
+		// use just idx for now
 		auto* tab = tabs_->createTab(std::to_string(i));
+		new nanogui::Label(tab, ctrl_->cameraName(i), "sans-bold", 18);
+		tab->setLayout(new nanogui::BoxLayout
+			(nanogui::Orientation::Vertical, nanogui::Alignment::Middle, 0, 8));
+
 		auto* display = new IntrinsicDetails(tab);
 		display->update(calib_[i].intrinsic);
 	}
@@ -471,6 +492,73 @@ static void  drawText(NVGcontext* ctx, nanogui::Vector2f &pos, const std::string
 	nvgText(ctx, pos.x() + 1, pos.y() + 1, text.c_str(), nullptr);
 }
 
+////////////////////////////////////////////////////////////////////////////////
+
+class StereoCalibrationImageView : public ftl::gui2::StereoImageView {
+public:
+	using ftl::gui2::StereoImageView::StereoImageView;
+	virtual bool mouseButtonEvent(const nanogui::Vector2i &p, int button, bool down, int modifiers) override;
+	virtual void draw(NVGcontext* ctx) override;
+private:
+	bool draw_line_ = false;
+	float row_image_ = 0;
+
+public:
+	EIGEN_MAKE_ALIGNED_OPERATOR_NEW;
+};
+
+bool StereoCalibrationImageView::mouseButtonEvent(const nanogui::Vector2i &p, int button, bool down, int modifiers) {
+	if (button == GLFW_MOUSE_BUTTON_1 && !down) {
+		float row = round(imageCoordinateAt(p.cast<float>()).y());
+		if (row == row_image_ && draw_line_) {
+			draw_line_ = false;
+		}
+		else {
+			draw_line_ = true;
+			row_image_ = row;
+		}
+	}
+	return true;
+}
+
+void StereoCalibrationImageView::draw(NVGcontext* ctx) {
+	StereoImageView::draw(ctx);
+	if (!draw_line_) { return; }
+
+	// assumes vertical alignment (horizontal not tested)
+	CHECK(orientation() == nanogui::Orientation::Vertical);
+	int x = position().x();
+	int y = position().y();
+	int w = width();
+	int h = left()->height();
+	nanogui::Vector2f l = left()->positionForCoordinate({0.0f, row_image_}) + left()->position().cast<float>();
+	nanogui::Vector2f r = right()->positionForCoordinate({0.0f, row_image_}) + right()->position().cast<float>();
+	float swidth = std::max(1.0f, left()->scale());
+
+	for (auto& p : {l, r}) {
+		nvgScissor(ctx, x, y, w, h);
+		nvgBeginPath(ctx);
+		nvgMoveTo(ctx, x, p.y() - swidth*0.5f); // "half a pixel" shift
+		nvgLineTo(ctx, x + w, p.y() - swidth*0.5f);
+		nvgStrokeColor(ctx, nvgRGBA(255, 32, 32, (swidth == 1.0f) ? 255 : 96));
+		nvgStrokeWidth(ctx, swidth);
+		nvgStroke(ctx);
+
+		if (swidth*0.5f > 2.0f) {
+			nvgBeginPath(ctx);
+			nvgMoveTo(ctx, x, p.y() - swidth*0.5f);
+			nvgLineTo(ctx, x + w, p.y() - swidth*0.5f);
+			nvgStrokeColor(ctx, nvgRGBA(0, 0, 0, 196));
+			nvgStrokeWidth(ctx, 1.0f);
+			nvgStroke(ctx);
+		}
+		nvgResetScissor(ctx);
+		y += h;
+	}
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
 ExtrinsicCalibrationView::ExtrinsicCalibrationView(Screen* widget, ExtrinsicCalibration* ctrl) :
 		ftl::gui2::View(widget), ctrl_(ctrl), rows_(0) {
 
@@ -483,7 +571,7 @@ ExtrinsicCalibrationView::ExtrinsicCalibrationView(Screen* widget, ExtrinsicCali
 
 	// assumes all cameras stereo cameras, indexed in order
 	for (int i = 0; i < ctrl_->cameraCount(); i += 2) {
-		new StereoImageView(frames_, nanogui::Orientation::Vertical);
+		new StereoCalibrationImageView(frames_, nanogui::Orientation::Vertical);
 	}
 	paused_ = false;
 	wcontrol_ = new ControlWindow(screen(), this);
@@ -505,7 +593,7 @@ void ExtrinsicCalibrationView::performLayout(NVGcontext* ctx) {
 
 	nanogui::Vector2i fsize = { width()/(frames_->childCount()), height() };
 	for (int i = 0; i < frames_->childCount(); i++) {
-		auto* stereo = dynamic_cast<StereoImageView*>(frames_->childAt(i));
+		auto* stereo = dynamic_cast<StereoCalibrationImageView*>(frames_->childAt(i));
 		stereo->setFixedSize(fsize);
 		stereo->fit();
 	}
diff --git a/applications/gui2/src/views/calibration/widgets.cpp b/applications/gui2/src/views/calibration/widgets.cpp
index f907b2ce1a37ecb5f69504b6987f3d186ae361a7..80a4af5d5d4a43ff19b3bf206891af924de456aa 100644
--- a/applications/gui2/src/views/calibration/widgets.cpp
+++ b/applications/gui2/src/views/calibration/widgets.cpp
@@ -126,17 +126,23 @@ void IntrinsicDetails::update(const ftl::calibration::CalibrationData::Intrinsic
 		"(" + to_string(values.cx) + ", " +
 			  to_string(values.cy) + ")");
 
+	new nanogui::Widget(params_);
+	new nanogui::Label(params_,
+			"(" + to_string(100.0*(2.0*values.cx/double(imsize.width) - 1.0)) + "% , " +
+				to_string(100.0*(2.0*values.cy/double(imsize.height) - 1.0)) + "%)");
+	if (use_physical) new nanogui::Widget(params_);
+
 	new nanogui::Label(params_, "Field of View (x):");
 	new nanogui::Label(params_, to_string(fovx) + "°");
-	if (use_physical) new nanogui::Label(params_, "");
+	if (use_physical) new nanogui::Widget(params_);
 
 	new nanogui::Label(params_, "Field of View (y):");
 	new nanogui::Label(params_, to_string(fovy)+ "°");
-	if (use_physical) new nanogui::Label(params_, "");
+	if (use_physical) new nanogui::Widget(params_);
 
 	new nanogui::Label(params_, "Aspect ratio:");
 	new nanogui::Label(params_, to_string(ar));
-	if (use_physical) new nanogui::Label(params_, "");
+	if (use_physical) new nanogui::Widget(params_);
 
 	std::string pK;
 	std::string pP;
diff --git a/applications/gui2/src/widgets/imageview.cpp b/applications/gui2/src/widgets/imageview.cpp
index 3562577975d3d882b3599e6efbbc693811bb5c8f..2137b1ce9f8124c65234952dfaaa93605323bbb5 100644
--- a/applications/gui2/src/widgets/imageview.cpp
+++ b/applications/gui2/src/widgets/imageview.cpp
@@ -538,6 +538,24 @@ StereoImageView::StereoImageView(nanogui::Widget* parent, nanogui::Orientation o
 	right_->setFixedScale(true);
 }
 
+
+nanogui::Vector2f StereoImageView::imageCoordinateAt(const nanogui::Vector2f& p) const {
+
+	nanogui::Vector2f pos = position().cast<float>();
+	nanogui::Vector2f posr = pos + right_->position().cast<float>();
+
+	bool is_right =
+		((p.x() >= posr.x()) && (orientation_ == nanogui::Orientation::Horizontal)) ||
+		((p.y() >= posr.y()) && (orientation_ == nanogui::Orientation::Vertical));
+
+	if (is_right) {
+		return right_->imageCoordinateAt(p - right_->position().cast<float>());
+	}
+	else {
+		return left_->imageCoordinateAt(p);
+	}
+}
+
 bool StereoImageView::mouseMotionEvent(const nanogui::Vector2i &p, const nanogui::Vector2i &rel, int button, int modifiers) {
 	if ((button & (1 << GLFW_MOUSE_BUTTON_RIGHT)) != 0) {
 		nanogui::Vector2f posl = left_->imageCoordinateAt(p.cast<float>());
@@ -554,7 +572,7 @@ bool StereoImageView::mouseMotionEvent(const nanogui::Vector2i &p, const nanogui
 	}
 	return false;
 }
-#include <loguru.hpp>
+
 bool StereoImageView::scrollEvent(const nanogui::Vector2i& p, const nanogui::Vector2f& rel) {
 	// synchronized zoom
 
diff --git a/applications/gui2/src/widgets/imageview.hpp b/applications/gui2/src/widgets/imageview.hpp
index 740152cdf15183a2aed2440c53ea52edb1059fa3..0209636fafd0049e4fcc8304db2b0240f15c8f2d 100644
--- a/applications/gui2/src/widgets/imageview.hpp
+++ b/applications/gui2/src/widgets/imageview.hpp
@@ -217,7 +217,10 @@ public:
 	EIGEN_MAKE_ALIGNED_OPERATOR_NEW
 };
 
-/** Two ImageViews with synchronized zoom and pan. */
+/** Two ImageViews with synchronized zoom and pan. Widget split in two equal
+ * size sections (left and right). With vertical orientation right is the lower
+ * image.
+*/
 class StereoImageView : public nanogui::Widget {
 public:
 	StereoImageView(nanogui::Widget* parent, nanogui::Orientation orientation = nanogui::Orientation::Horizontal);
@@ -232,6 +235,11 @@ public:
 	FTLImageView* left() { return left_; }
 	FTLImageView* right() { return right_; }
 
+	/** get image coordinate at given widget coordinate */
+	nanogui::Vector2f imageCoordinateAt(const nanogui::Vector2f& position) const;
+
+	nanogui::Orientation orientation() { return orientation_; }
+
 	void fit();
 
 	void bindLeft(GLuint id) { left_->texture().free(); left_->bindImage(id); }
diff --git a/components/calibration/src/extrinsic.cpp b/components/calibration/src/extrinsic.cpp
index 99bbf4036d954200abc0ff184d12f65a648495ad..d32961040338efec217048c1017b84588b12d443 100644
--- a/components/calibration/src/extrinsic.cpp
+++ b/components/calibration/src/extrinsic.cpp
@@ -356,6 +356,7 @@ double calibratePair(const cv::Mat &K1, const cv::Mat &D1,
 unsigned int ExtrinsicCalibration::addCamera(const CalibrationData::Intrinsic &c) {
 	unsigned int idx = calib_.size();
 	calib_.push_back({c, {}});
+	calib_optimized_.push_back(calib_.back());
 	is_calibrated_.push_back(false);
 	return idx;
 }
@@ -363,6 +364,7 @@ unsigned int ExtrinsicCalibration::addCamera(const CalibrationData::Intrinsic &c
 unsigned int ExtrinsicCalibration::addCamera(const CalibrationData::Calibration &c) {
 	unsigned int idx = calib_.size();
 	calib_.push_back(c);
+	calib_optimized_.push_back(calib_.back());
 	is_calibrated_.push_back(true);
 	return idx;
 }
@@ -370,7 +372,9 @@ unsigned int ExtrinsicCalibration::addCamera(const CalibrationData::Calibration
 unsigned int ExtrinsicCalibration::addStereoCamera(const CalibrationData::Intrinsic &c1, const CalibrationData::Intrinsic &c2) {
 	unsigned int idx = calib_.size();
 	calib_.push_back({c1, {}});
+	calib_optimized_.push_back(calib_.back());
 	calib_.push_back({c2, {}});
+	calib_optimized_.push_back(calib_.back());
 	is_calibrated_.push_back(false);
 	is_calibrated_.push_back(false);
 	mask_.insert({idx, idx + 1});
@@ -380,7 +384,9 @@ unsigned int ExtrinsicCalibration::addStereoCamera(const CalibrationData::Intrin
 unsigned int ExtrinsicCalibration::addStereoCamera(const CalibrationData::Calibration &c1, const CalibrationData::Calibration &c2) {
 	unsigned int idx = calib_.size();
 	calib_.push_back({c1.intrinsic, c1.extrinsic});
+	calib_optimized_.push_back(calib_.back());
 	calib_.push_back({c2.intrinsic, c2.extrinsic});
+	calib_optimized_.push_back(calib_.back());
 	is_calibrated_.push_back(c1.extrinsic.valid());
 	is_calibrated_.push_back(c2.extrinsic.valid());
 	mask_.insert({idx, idx + 1});
@@ -673,9 +679,9 @@ double ExtrinsicCalibration::optimize() {
 	LOG(INFO) << "fix distortion: " << (options_.fix_distortion ? "yes" : "no");
 
 	ba.run(options_);
-	LOG(INFO) << "removed points: " << ba.removeObservations(1.0);
+	LOG(INFO) << "removed points: " << ba.removeObservations(2.0);
 	ba.run(options_);
-	LOG(INFO) << "removed points: " << ba.removeObservations(0.67);
+	LOG(INFO) << "removed points: " << ba.removeObservations(1.0);
 	ba.run(options_);
 
 	calib_optimized_.resize(calib_.size());
diff --git a/components/calibration/src/optimize.cpp b/components/calibration/src/optimize.cpp
index 65e10fb512a83503e8178cd5c818c5e4f5983448..a7ff076238bfdadaf835bc1db5c644f5f0b566d3 100644
--- a/components/calibration/src/optimize.cpp
+++ b/components/calibration/src/optimize.cpp
@@ -445,7 +445,7 @@ void BundleAdjustment::addPoints(const vector<vector<Point2d>>& observations, st
 
 void BundleAdjustment::_setCameraParametrization(ceres::Problem &problem, const BundleAdjustment::Options &options) {
 
-	vector<int> constant_camera_parameters;
+	std::set<int> constant_camera_parameters;
 
 	// apply options
 	for (size_t i = 0; i < cameras_.size(); i++) {
@@ -453,6 +453,9 @@ void BundleAdjustment::_setCameraParametrization(ceres::Problem &problem, const
 			cameras_[i]->data[Camera::Parameter::K4] = 0.0;
 			cameras_[i]->data[Camera::Parameter::K5] = 0.0;
 			cameras_[i]->data[Camera::Parameter::K6] = 0.0;
+			constant_camera_parameters.insert(Camera::Parameter::K4);
+			constant_camera_parameters.insert(Camera::Parameter::K5);
+			constant_camera_parameters.insert(Camera::Parameter::K6);
 		}
 		if (options.zero_distortion) {
 			cameras_[i]->data[Camera::Parameter::K1] = 0.0;
@@ -468,33 +471,33 @@ void BundleAdjustment::_setCameraParametrization(ceres::Problem &problem, const
 
 	// set extrinsic paramters constant for all cameras
 	if (!options.optimize_motion) {
-		constant_camera_parameters.push_back(Camera::Parameter::Q1);
-		constant_camera_parameters.push_back(Camera::Parameter::Q2);
-		constant_camera_parameters.push_back(Camera::Parameter::Q3);
-		constant_camera_parameters.push_back(Camera::Parameter::Q4);
-		constant_camera_parameters.push_back(Camera::Parameter::TX);
-		constant_camera_parameters.push_back(Camera::Parameter::TY);
-		constant_camera_parameters.push_back(Camera::Parameter::TZ);
+		constant_camera_parameters.insert(Camera::Parameter::Q1);
+		constant_camera_parameters.insert(Camera::Parameter::Q2);
+		constant_camera_parameters.insert(Camera::Parameter::Q3);
+		constant_camera_parameters.insert(Camera::Parameter::Q4);
+		constant_camera_parameters.insert(Camera::Parameter::TX);
+		constant_camera_parameters.insert(Camera::Parameter::TY);
+		constant_camera_parameters.insert(Camera::Parameter::TZ);
 	}
 
 	// set intrinsic parameters constant for all cameras
 	if (!options.optimize_intrinsic || options.fix_focal) {
-		constant_camera_parameters.push_back(Camera::Parameter::F);
+		constant_camera_parameters.insert(Camera::Parameter::F);
 	}
 	if (!options.optimize_intrinsic || options.fix_principal_point) {
-		constant_camera_parameters.push_back(Camera::Parameter::CX);
-		constant_camera_parameters.push_back(Camera::Parameter::CY);
+		constant_camera_parameters.insert(Camera::Parameter::CX);
+		constant_camera_parameters.insert(Camera::Parameter::CY);
 	}
 
 	if (!options.optimize_intrinsic || options.fix_distortion) {
-		constant_camera_parameters.push_back(Camera::Parameter::K1);
-		constant_camera_parameters.push_back(Camera::Parameter::K2);
-		constant_camera_parameters.push_back(Camera::Parameter::K3);
-		constant_camera_parameters.push_back(Camera::Parameter::K4);
-		constant_camera_parameters.push_back(Camera::Parameter::K5);
-		constant_camera_parameters.push_back(Camera::Parameter::K6);
-		constant_camera_parameters.push_back(Camera::Parameter::P1);
-		constant_camera_parameters.push_back(Camera::Parameter::P2);
+		constant_camera_parameters.insert(Camera::Parameter::K1);
+		constant_camera_parameters.insert(Camera::Parameter::K2);
+		constant_camera_parameters.insert(Camera::Parameter::K3);
+		constant_camera_parameters.insert(Camera::Parameter::K4);
+		constant_camera_parameters.insert(Camera::Parameter::K5);
+		constant_camera_parameters.insert(Camera::Parameter::K6);
+		constant_camera_parameters.insert(Camera::Parameter::P1);
+		constant_camera_parameters.insert(Camera::Parameter::P2);
 	}
 
 	if (!options.optimize_motion && !options.optimize_intrinsic) {
@@ -504,14 +507,14 @@ void BundleAdjustment::_setCameraParametrization(ceres::Problem &problem, const
 		}
 	}
 	else {
-		std::unordered_set<int> fix_extrinsic(
+		std::set<int> fix_extrinsic(
 			options.fix_camera_extrinsic.begin(), options.fix_camera_extrinsic.end());
 
-		std::unordered_set<int> fix_intrinsic(
+		std::set<int> fix_intrinsic(
 			options.fix_camera_extrinsic.begin(), options.fix_camera_extrinsic.end());
 
 		for (size_t i = 0; i < cameras_.size(); i++) {
-			std::unordered_set<int> constant_parameters(
+			std::set<int> constant_parameters(
 				constant_camera_parameters.begin(),
 				constant_camera_parameters.end());