diff --git a/applications/gui2/CMakeLists.txt b/applications/gui2/CMakeLists.txt
index 9cc2407305bba5062d8cfd1c71c226ad0f1b6028..60032ebebc0f2dddebc58dd8d70949007bbaf407 100644
--- a/applications/gui2/CMakeLists.txt
+++ b/applications/gui2/CMakeLists.txt
@@ -41,6 +41,7 @@ if (WITH_CERES)
 		src/modules/calibration/extrinsic.cpp
 		src/modules/calibration/intrinsic.cpp
 		src/modules/calibration/stereo.cpp
+		src/views/calibration/widgets.cpp
 		src/views/calibration/extrinsicview.cpp
 		src/views/calibration/intrinsicview.cpp
 		src/views/calibration/stereoview.cpp
diff --git a/applications/gui2/src/modules/calibration/calibration.hpp b/applications/gui2/src/modules/calibration/calibration.hpp
index e2e45e9e43528c27ed1fb8676f69611f3acb2812..307244cf13687ee427983c30a49929c7eeaa2555 100644
--- a/applications/gui2/src/modules/calibration/calibration.hpp
+++ b/applications/gui2/src/modules/calibration/calibration.hpp
@@ -20,21 +20,21 @@ public:
 	void unset(unsigned int  flag) { flags_ &= ~flag; }
 	void reset() { flags_ = 0; }
 	std::string name(int) const;
-
-	int defaultFlags() const;
-	std::vector<int> list() const;
-	std::string explain(int) const;
 	operator int() { return flags_; }
 
+	virtual int defaultFlags() const;
+	virtual std::vector<int> list() const;
+	virtual std::string explain(int) const;
+
 private:
 	int flags_ = 0;
 };
 
 class  OpenCVCalibrateFlagsStereo : public OpenCVCalibrateFlags {
 public:
-	int defaultFlags() const;
-	std::vector<int> list() const;
-	std::string explain(int) const;
+	int defaultFlags() const override;
+	std::vector<int> list() const override;
+	std::string explain(int) const override;
 };
 
 /**
@@ -129,7 +129,7 @@ public:
 	int calibrated() { return state_->calibrated; }
 
 	OpenCVCalibrateFlags& flags() { return state_->flags; };
-	void resetFlags();
+	int defaultFlags();
 
 	/** Reset calibration instance, discards drops all state. */
 	void reset();
@@ -168,19 +168,9 @@ public:
 
 	float reprojectionError() { return state_->reprojection_error; }
 
-	/** Get sensor size from config (in mm) */
-	float sensorWidth();
-	float sensorHeight();
-	
-	struct Values {
-		double fov_x;
-		double fov_y;
-		double f;
-		double pp_x;
-		double pp_y;
-		double aspect_ratio;
-	};
-	Values values(double sensor_width, double sensor_height);
+	/** Get sensor size from config/previous calibration (in mm) */
+	cv::Size2d sensorSize();
+	void setSensorSize(cv::Size2d size);
 
 	/** get current frame */
 	cv::cuda::GpuMat getFrame();
diff --git a/applications/gui2/src/modules/calibration/intrinsic.cpp b/applications/gui2/src/modules/calibration/intrinsic.cpp
index 326568dfdc699cca491ed014c175151b22459805..2a39b8ceced33841b6d78d16c520df9b853b59ac 100644
--- a/applications/gui2/src/modules/calibration/intrinsic.cpp
+++ b/applications/gui2/src/modules/calibration/intrinsic.cpp
@@ -49,7 +49,7 @@ void IntrinsicCalibration::reset() {
 	state_->object = std::make_unique<ChessboardObject>();
 	state_->channel = Channel::Left;
 	state_->channel_alt = Channel::Left;
-	resetFlags();
+	state_->flags.set(defaultFlags());
 }
 
 void IntrinsicCalibration::start(ftl::data::FrameID id) {
@@ -98,7 +98,7 @@ void IntrinsicCalibration::setChannel(Channel channel) {
 void IntrinsicCalibration::setChannel_(FrameSetPtr fs) {
 	state_->channel_alt = state_->channel;
 	if (fs == nullptr) { return; }
-	
+
 	auto& frame = (*fs)[state_->id.source()].cast<ftl::rgbd::Frame>();
 	if ((state_->channel== Channel::Left) && frame.has(Channel::LeftHighRes)) {
 		state_->channel_alt = Channel::LeftHighRes;
@@ -121,7 +121,7 @@ void IntrinsicCalibration::setChannel_(FrameSetPtr fs) {
 		auto calib = frame.get<CalibrationData>(Channel::CalibrationData);
 		if (calib.hasCalibration(state_->channel)) {
 			auto intrinsic = calib.get(state_->channel).intrinsic;
-			state_->calib = CalibrationData::Intrinsic(intrinsic.matrix(size), intrinsic.distCoeffs.Mat(), size);
+			state_->calib = CalibrationData::Intrinsic(intrinsic, size);
 			state_->calibrated = true;
 		}
 	}
@@ -196,19 +196,19 @@ void IntrinsicCalibration::save() {
 	setCalibration(frame, calib_data);
 }
 
-void IntrinsicCalibration::resetFlags() {
-	// reset flags and get class defaults
-	state_->flags.reset();
-	state_->flags.set(state_->flags.defaultFlags());
+int IntrinsicCalibration::defaultFlags() {
+	int flags = state_->flags.defaultFlags();
 
 	// load config flags
 	for (int i : state_->flags.list()) {
 		auto flag = get<bool>(state_->flags.name(i));
 		if (flag) {
-			if (*flag)	state_->flags.set(i);
-			else		state_->flags.unset(i);
+			if (*flag)	flags |= i;
+			else		flags &= (~i);
 		}
 	}
+
+	return flags;
 }
 
 bool IntrinsicCalibration::isBusy() {
@@ -224,20 +224,26 @@ void IntrinsicCalibration::run() {
 					LOG(INFO) << state_->flags.name(f);
 				}
 			}
-			cv::Mat K = state_->calib.matrix();
+			cv::Size2d ssize = sensorSize();
+			cv::Mat K;
 			cv::Mat distCoeffs;
 			cv::Size size = state_->calib.resolution;
-			state_->calib.distCoeffs.Mat(12).copyTo(distCoeffs);
+			if (state_->flags.has(cv::CALIB_USE_INTRINSIC_GUESS)) {
+				// OpenCV seems to use these anyways?
+				K = state_->calib.matrix();
+				state_->calib.distCoeffs.Mat(12).copyTo(distCoeffs);
+			}
 			std::vector<cv::Mat> rvecs, tvecs;
 			auto term = cv::TermCriteria
 				(cv::TermCriteria::COUNT|cv::TermCriteria::EPS, state_->max_iter, 1.0e-6);
-			
+
 			state_->reprojection_error = cv::calibrateCamera(
 				state_->points_object, state_->points,
 				size, K, distCoeffs, rvecs, tvecs,
 				state_->flags, term);
-			state_->calib = CalibrationData::Intrinsic(K, distCoeffs, size);
 
+			state_->calib = CalibrationData::Intrinsic(K, distCoeffs, size);
+			state_->calib.sensorSize = ssize;
 			state_->calibrated = true;
 		}
 		catch (std::exception &e) {
@@ -283,15 +289,6 @@ cv::cuda::GpuMat IntrinsicCalibration::getFrameUndistort() {
 		return cv::cuda::GpuMat();
 	}
 
-	/*if (state_->map1.empty()) {
-		auto K = state_->calib.matrix();
-		auto distCoeffs = state_->calib.distCoeffs.Mat();
-		auto size = state_->calib.resolution;
-		cv::initUndistortRectifyMap
-			(K, distCoeffs, cv::noArray(), cv::noArray(), size, CV_16SC2, 
-			state_->map1, state_->map2);
-	}*/
-
 	auto im = getMat((*fs_current_)[state_->id.source()].cast<ftl::rgbd::Frame>(),
 					 state_->channel_alt);
 
@@ -304,13 +301,20 @@ cv::cuda::GpuMat IntrinsicCalibration::getFrameUndistort() {
 	return gpu;
 }
 
-float IntrinsicCalibration::sensorWidth() {
-	return value("sensor_width", 0.0f);
+cv::Size2d IntrinsicCalibration::sensorSize() {
+	if (state_->calib.sensorSize == cv::Size2d{0.0, 0.0}) {
+		double w = value("sensor_width", 0.0);
+		double h = value("sensor_height", 0.0);
+		return {w, h};
+	}
+	else {
+		return state_->calib.sensorSize;
+	}
 };
 
-float IntrinsicCalibration::sensorHeight() {
-	return value("sensor_height", 0.0f);
-};
+void IntrinsicCalibration::setSensorSize(cv::Size2d sz) {
+	state_->calib.sensorSize = sz;
+}
 
 bool IntrinsicCalibration::hasChannel(Channel c) {
 	if (fs_current_) {
@@ -341,25 +345,6 @@ std::vector<cv::Point2f> IntrinsicCalibration::previousPoints() {
 	return {};
 }
 
-IntrinsicCalibration::Values IntrinsicCalibration::values(double sensor_width,
-		double sensor_height) {
-
-	Values result{ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 };
-	if (!calibrated()) {
-		return result;
-	}
-	cv::Point2d pp;
-	cv::calibrationMatrixValues
-		(state_->calib.matrix(), state_->calib.resolution,
-		 sensor_width, sensor_height, result.fov_x, result.fov_y, result.f,
-		 pp, result.aspect_ratio);
-	
-	result.pp_x = pp.x;
-	result.pp_y = pp.y;
-
-	return result;
-}
-
 ftl::calibration::CalibrationData::Intrinsic IntrinsicCalibration::calibration() {
 	return state_->calib;
 }
diff --git a/applications/gui2/src/views/calibration/intrinsicview.cpp b/applications/gui2/src/views/calibration/intrinsicview.cpp
index 8c85dc82b08d265c749698261cab2798cbeff1dc..998c9773c2cdf0ec0ebd0e6f7264cefb344441e1 100644
--- a/applications/gui2/src/views/calibration/intrinsicview.cpp
+++ b/applications/gui2/src/views/calibration/intrinsicview.cpp
@@ -1,7 +1,7 @@
 #include <sstream>
 
 #include "visualization.hpp"
-
+#include "widgets.hpp"
 #include "intrinsicview.hpp"
 
 #include "../../screen.hpp"
@@ -85,8 +85,6 @@ class IntrinsicCalibrationView::CalibrationWindow : public FixedWindow {
 public:
 	CalibrationWindow(nanogui::Widget* parent, IntrinsicCalibrationView* view);
 	virtual void draw(NVGcontext* ctx) override;
-	double sensorWidth() { return sensor_width_->value(); }
-	double sensorHeight() { return sensor_width_->value(); }
 
 private:
 	IntrinsicCalibrationView* view_;
@@ -113,8 +111,8 @@ private:
 	IntrinsicCalibration* ctrl_;
 
 	nanogui::Button* bsave_;
-	nanogui::Widget* params_;
-	nanogui::Widget* dist_;
+	nanogui::Label* rms_;
+	ftl::gui2::IntrinsicDetails* info_;
 
 public:
 	EIGEN_MAKE_ALIGNED_OPERATOR_NEW
@@ -301,12 +299,14 @@ IntrinsicCalibrationView::ControlWindow::ControlWindow(nanogui::Widget* parent,
 
 	auto* bback_ = new nanogui::Button(buttons, "", ENTYPO_ICON_ARROW_LEFT);
 	bback_->setFixedWidth(40);
+	bback_->setTooltip("Back to capture options");
 	bback_->setCallback([this, button = bback_](){
 		view_->setMode(Mode::CAPTURE_INIT);
 	});
 
 	bsave_ = new nanogui::Button(buttons, "", ENTYPO_ICON_SAVE);
 	bsave_->setFixedWidth(40);
+	bsave_->setTooltip("Save calibration");
 	bsave_->setEnabled(ctrl_->calibrated());
 	bsave_->setCallback([ctrl = ctrl_, view = view_](){
 		ctrl->save();
@@ -316,6 +316,7 @@ IntrinsicCalibrationView::ControlWindow::ControlWindow(nanogui::Widget* parent,
 
 	bapply_ = new nanogui::Button(buttons, "");
 	bapply_->setFixedWidth(40);
+	bapply_->setTooltip("Apply distortion correction");
 	bapply_->setEnabled(ctrl_->calibrated());
 	bapply_->setFlags(nanogui::Button::Flags::ToggleButton);
 	bapply_->setPushed(view_->undistort());
@@ -323,7 +324,6 @@ IntrinsicCalibrationView::ControlWindow::ControlWindow(nanogui::Widget* parent,
 		view->setUndistort(v);
 	});
 
-
 	bresults_ = new nanogui::Button(buttons, "Details");
 	bresults_->setFixedWidth(120);
 
@@ -354,7 +354,8 @@ void IntrinsicCalibrationView::ControlWindow::draw(NVGcontext* ctx) {
 	bresults_->setEnabled(ctrl_->calibrated());
 	bsave_->setEnabled(ctrl_->calibrated());
 	bapply_->setEnabled(ctrl_->calibrated());
-	bapply_->setIcon(ctrl_->calibrated() ? ENTYPO_ICON_EYE : ENTYPO_ICON_EYE);
+	bapply_->setIcon(view_->undistort() ? ENTYPO_ICON_EYE : ENTYPO_ICON_EYE);
+	bapply_->setPushed(view_->undistort());
 	updateCount();
 	FixedWindow::draw(ctx);
 }
@@ -392,49 +393,28 @@ IntrinsicCalibrationView::CalibrationWindow::CalibrationWindow(nanogui::Widget*
 	sensor->setLayout(grid_layout);
 
 	new nanogui::Label(sensor, "Width");
-	sensor_width_ = new nanogui::FloatBox<double>(sensor, ctrl_->sensorWidth());
+	sensor_width_ = new nanogui::FloatBox<double>(sensor, ctrl_->sensorSize().width);
 	sensor_width_->setEditable(true);
 	sensor_width_->setFormat("[0-9]*\\.?[0-9]+");
 	sensor_width_->setUnits("mm");
+	sensor_width_->setCallback([ctrl = this->ctrl_](double value) {
+		auto size = ctrl->sensorSize();
+		ctrl->setSensorSize({value, size.height});
+	});
 
 	new nanogui::Label(sensor, "Height");
-	sensor_height_ = new nanogui::FloatBox<double>(sensor, ctrl_->sensorHeight());
+	sensor_height_ = new nanogui::FloatBox<double>(sensor, ctrl_->sensorSize().height);
 	sensor_height_->setEditable(true);
 	sensor_height_->setFormat("[0-9]*\\.?[0-9]+");
 	sensor_height_->setUnits("mm");
+	sensor_height_->setCallback([ctrl = this->ctrl_](double value) {
+		auto size = ctrl->sensorSize();
+		ctrl->setSensorSize({size.width, value});
+	});
 
 	// flags
 	new nanogui::Label(this, "Flags");
-	auto* flags = new nanogui::Widget(this);
-	flags->setLayout(new nanogui::BoxLayout
-		(nanogui::Orientation::Vertical, nanogui::Alignment::Fill, 0, 4));
-
-	for(int flag : ctrl_->flags().list()) {
-		auto* checkbox = new nanogui::CheckBox(flags, ctrl_->flags().name(flag),
-		[flag, this](bool state){
-			if (state)	{ ctrl_->flags().set(flag); }
-			else		{ ctrl_->flags().unset(flag); }
-		});
-		checkbox->setChecked(ctrl_->flags().has(flag));
-		checkbox->setTooltip(ctrl_->flags().explain(flag));
-	}
-
-	// reset button
-	auto* reset = new nanogui::Button(this, "Reset flags");
-	reset->setCallback([ctrl = ctrl_, flags](){
-		ctrl->resetFlags();
-
-		// update widget
-		auto all_flags = ctrl->flags().list();
-		for(size_t i = 0; i < all_flags.size(); i++) {
-			auto* checkbox = dynamic_cast<nanogui::CheckBox*>(flags->childAt(i));
-			checkbox->setChecked(ctrl->flags().has(all_flags[i]));
-			if (all_flags[i] == cv::CALIB_USE_INTRINSIC_GUESS) {
-				checkbox->setEnabled(ctrl->calibrated());
-			}
-		}
-	});
-
+	new ftl::gui2::OpenCVFlagWidget(this, &(ctrl_->flags()), ctrl_->defaultFlags());
 	status_ = new nanogui::Label(this, " ");
 
 	bcalibrate_ = new nanogui::Button(this, "Run");
@@ -463,6 +443,7 @@ void IntrinsicCalibrationView::CalibrationWindow::draw(NVGcontext* ctx) {
 	bcalibrate_->setEnabled(!ctrl_->isBusy() && (ctrl_->count() > 0));
 	if (calibrating_ && !ctrl_->isBusy()) {
 		calibrating_ = false;
+		view_->setUndistort(true);
 		view_->setMode(Mode::RESULTS);
 	}
 	FixedWindow::draw(ctx);
@@ -475,24 +456,16 @@ IntrinsicCalibrationView::ResultWindow::ResultWindow(nanogui::Widget* parent, In
 	FixedWindow(parent, "Results"), view_(view), ctrl_(view->ctrl_) {
 
 	setLayout(new nanogui::BoxLayout
-		(nanogui::Orientation::Vertical, nanogui::Alignment::Fill, 8 , 0));
+		(nanogui::Orientation::Vertical, nanogui::Alignment::Fill, 16 , 0));
 
 	(new nanogui::Button(buttonPanel(), "", ENTYPO_ICON_CROSS))->setCallback(
 		[view = view_]() {
 		view->setMode(Mode::VIDEO);
 	});
 
-	params_ = new nanogui::Widget(this);
-	nanogui::GridLayout *grid_layout = new nanogui::GridLayout
-		(nanogui::Orientation::Horizontal, 3, nanogui::Alignment::Fill, 8, 8);
-	grid_layout->setColAlignment
-		({nanogui::Alignment::Maximum, nanogui::Alignment::Fill});
+	rms_ = new nanogui::Label(this, "");
 
-	params_->setLayout(grid_layout);
-
-	dist_ = new nanogui::Widget(this);
-	dist_->setLayout(new nanogui::BoxLayout
-		(nanogui::Orientation::Vertical, nanogui::Alignment::Fill, 8, 8));
+	info_ = new ftl::gui2::IntrinsicDetails(this);
 
 	bsave_ = new nanogui::Button(this, "Save");
 	bsave_->setCallback([button = bsave_, ctrl = ctrl_](){
@@ -507,83 +480,16 @@ void IntrinsicCalibrationView::ResultWindow::draw(NVGcontext* ctx) {
 }
 
 void IntrinsicCalibrationView::ResultWindow::update() {
-	while (params_->childCount() > 0) {
-		params_->removeChild(params_->childCount() - 1);
-	}
-	while (dist_->childCount() > 0) {
-		dist_->removeChild(dist_->childCount() - 1);
-	}
-	
-	auto sw = view_->wcalibration_->sensorWidth();
-	auto sh = view_->wcalibration_->sensorHeight();
-	auto values = ctrl_->values(sw, sh);
-	auto K = ctrl_->calibration().matrix();
-	std::vector<double> D = ctrl_->calibration().distCoeffs.Mat(12);
-	auto fx = K.at<double>(0, 0);
-	auto fy = K.at<double>(1, 1);
-
-	new nanogui::Label(params_, "Reprojection error:");
-	new nanogui::Label(params_, to_string(ctrl_->reprojectionError()));
-	new nanogui::Label(params_, "");
-
-	new nanogui::Label(params_, "Focal length:");
-	new nanogui::Label(params_, to_string(values.f) + " mm");
-	new nanogui::Label(params_,
-		((fx == fy) ? to_string(fx) + " px": (
-		"(" + to_string(fx) + ", "
-			+ to_string(fy) + ")")));
-
-	new nanogui::Label(params_, "Principal point:");
-	new nanogui::Label(params_,
-		"(" + to_string(values.pp_x) + ", " +
-			  to_string(values.pp_y) + ")");
-	new nanogui::Label(params_,
-		"(" + to_string(K.at<double>(0, 2)) + ", " +
-			  to_string(K.at<double>(1, 2)) + ")");
-
-	new nanogui::Label(params_, "Field of View (x):");
-	new nanogui::Label(params_, to_string(values.fov_x) + "°");
-	new nanogui::Label(params_, "");
-
-	new nanogui::Label(params_, "Field of View (y):");
-	new nanogui::Label(params_, to_string(values.fov_y)+ "°");
-	new nanogui::Label(params_, "");
-
-	new nanogui::Label(params_, "Aspect ratio:");
-	new nanogui::Label(params_, to_string(values.aspect_ratio));
-	new nanogui::Label(params_, "");
-
-	std::string pK;
-	std::string pP;
-	std::string pS;
-	if(D.size() >= 4) {
-		pK += "K1: " + to_string(D[0] ,3);
-		pK += ", K2: " + to_string(D[1] ,3);
-		pP += "P1: " + to_string(D[2], 3);
-		pP += "P1: " + to_string(D[3], 3);
-	}
-	if (D.size() >= 5) {
-		pK += ", K3: " + to_string(D[4], 3);
-	}
-	if (D.size() >= 8) {
-		pK += ", K4: " + to_string(D[5] ,3);
-		pK += ", K5: " + to_string(D[6] ,3);
-		pK += ", K6: " + to_string(D[7] ,3);
+	if (!isnan(ctrl_->reprojectionError())) {
+		rms_->setCaption("Reprojection error (RMS): " + to_string(ctrl_->reprojectionError()));
+		rms_->setVisible(true);
 	}
-	if (D.size() >= 12) {
-		pS += "S1: " + to_string(D[8] ,3);
-		pS += ", S2: " + to_string(D[9] ,3);
-		pS += ", S3: " + to_string(D[10] ,3);
-		pS += ", S4: " + to_string(D[11] ,3);
+	else {
+		rms_->setVisible(false);
 	}
-
-	if (!pK.empty()) new nanogui::Label(dist_, pK);
-	if (!pP.empty()) new nanogui::Label(dist_, pP);
-	if (!pS.empty()) new nanogui::Label(dist_, pS);
-
+	info_->update(ctrl_->calibration());
 	bsave_->setEnabled(true);
 	bsave_->setCaption("Save");
-	screen()->performLayout();
 }
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -602,6 +508,7 @@ IntrinsicCalibrationView::IntrinsicCalibrationView(Screen* parent,
 	wcalibration_ = new CalibrationWindow(screen(), this);
 	wcalibration_->setFixedWidth(w);
 	wresults_ = new ResultWindow(screen(), this);
+	wresults_->update();
 
 	screen()->performLayout();
 	setMode(Mode::CAPTURE_INIT);
@@ -627,6 +534,7 @@ void IntrinsicCalibrationView::performLayout(NVGcontext *ctx) {
 	wcalibration_->center();
 	wresults_->center();
 	imview_->setSize(size());
+	View::performLayout(ctx);
 }
 
 void IntrinsicCalibrationView::draw(NVGcontext *ctx) {
@@ -645,7 +553,9 @@ void IntrinsicCalibrationView::draw(NVGcontext *ctx) {
 		}
 	}
 	View::draw(ctx);
-	drawChessboardCorners(ctx, imview_, ctrl_->previousPoints());
+	if (ctrl_->capturing()) {
+		drawChessboardCorners(ctx, imview_, ctrl_->previousPoints());
+	}
 }
 
 void IntrinsicCalibrationView::setMode(Mode m) {
@@ -691,4 +601,5 @@ void IntrinsicCalibrationView::setMode(Mode m) {
 			wresults_->update();
 			break;
 	}
+	screen()->performLayout();
 }
diff --git a/applications/gui2/src/views/calibration/stereoview.cpp b/applications/gui2/src/views/calibration/stereoview.cpp
index 921d1225896e5dcfa38517d5ae3d1ff68f16c38e..9433d816c09dd1902c878c4326d7501821aa2f93 100644
--- a/applications/gui2/src/views/calibration/stereoview.cpp
+++ b/applications/gui2/src/views/calibration/stereoview.cpp
@@ -1,9 +1,10 @@
 #include <sstream>
 
 #include "visualization.hpp"
-
+#include "widgets.hpp"
 #include "stereoview.hpp"
 
+
 #include "../../screen.hpp"
 #include "../../widgets/window.hpp"
 
@@ -298,8 +299,8 @@ void StereoCalibrationView::ControlWindow::draw(NVGcontext* ctx) {
 }
 
 void StereoCalibrationView::ControlWindow::updateCount() {
-	/*txtnframes_->setCaption("Detected patterns: " +
-							std::to_string(ctrl_->calib().count()));*/
+	txtnframes_->setCaption("Detected patterns: " +
+							std::to_string(ctrl_->count()));
 }
 ////////////////////////////////////////////////////////////////////////////////
 // Calibration Window
@@ -326,35 +327,9 @@ StereoCalibrationView::CalibrationWindow::CalibrationWindow(nanogui::Widget* par
 	auto* sensor = new nanogui::Widget(this);
 	sensor->setLayout(grid_layout);
 
-
 	// flags
 	new nanogui::Label(this, "Flags");
-	auto* flags = new nanogui::Widget(this);
-	flags->setLayout(new nanogui::BoxLayout
-		(nanogui::Orientation::Vertical, nanogui::Alignment::Fill, 0, 4));
-
-	for(int flag : ctrl_->flags().list()) {
-		auto* checkbox = new nanogui::CheckBox(flags, ctrl_->flags().name(flag),
-		[flag, this](bool state){
-			if (state)	{ ctrl_->flags().set(flag); }
-			else		{ ctrl_->flags().unset(flag); }
-		});
-		checkbox->setChecked(ctrl_->flags().has(flag));
-		checkbox->setTooltip(ctrl_->flags().explain(flag));
-	}
-
-	// reset button
-	auto* reset = new nanogui::Button(this, "Reset flags");
-	reset->setCallback([ctrl = ctrl_, flags](){
-		ctrl->resetFlags();
-
-		// update widget
-		auto all_flags = ctrl->flags().list();
-		for(size_t i = 0; i < all_flags.size(); i++) {
-			auto* checkbox = dynamic_cast<nanogui::CheckBox*>(flags->childAt(i));
-			checkbox->setChecked(ctrl->flags().has(all_flags[i]));
-		}
-	});
+	new ftl::gui2::OpenCVFlagWidget(this, &(ctrl_->flags()));
 
 	bcalibrate_ = new nanogui::Button(this, "Run");
 	bcalibrate_->setEnabled(false);
diff --git a/applications/gui2/src/views/calibration/widgets.cpp b/applications/gui2/src/views/calibration/widgets.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..d6732dd9f2b5014251ccd2c962c9dbb6dca8d0a9
--- /dev/null
+++ b/applications/gui2/src/views/calibration/widgets.cpp
@@ -0,0 +1,165 @@
+#include "widgets.hpp"
+
+#include <nanogui/label.h>
+#include <nanogui/layout.h>
+#include <nanogui/checkbox.h>
+
+#include <opencv2/calib3d.hpp>
+
+using ftl::gui2::OpenCVFlagWidget;
+using ftl::gui2::OpenCVCalibrateFlags;
+
+template<typename T>
+std::string to_string(T v, int precision = 2) {
+	std::stringstream stream;
+	stream << std::fixed << std::setprecision(precision) << v;
+	return stream.str();
+}
+
+OpenCVFlagWidget::OpenCVFlagWidget(nanogui::Widget* parent, OpenCVCalibrateFlags* flags, int defaultv) :
+		nanogui::Widget(parent), flags_(flags), defaults_(defaultv) {
+
+	if (defaultv == -1) {
+		defaults_ = flags_->defaultFlags();
+	}
+
+	setLayout(new nanogui::BoxLayout
+		(nanogui::Orientation::Vertical, nanogui::Alignment::Fill, 0, 4));
+
+	reset();
+}
+
+void OpenCVFlagWidget::reset() {
+	while(childCount() > 0) {
+		removeChild(childCount() - 1);
+	}
+
+	for(int flag : flags_->list()) {
+		auto* checkbox = new nanogui::CheckBox(this, flags_->name(flag),
+		[flag, this](bool state){
+			if (state)	{ flags_->set(flag); }
+			else		{ flags_->unset(flag); }
+		});
+		checkbox->setChecked(flags_->has(flag));
+		checkbox->setTooltip(flags_->explain(flag));
+	}
+
+	// reset button
+	auto* reset = new nanogui::Button(this, "Reset flags");
+	reset->setCallback([this](){
+
+		// update widget
+		auto all_flags = flags_->list();
+		for(size_t i = 0; i < all_flags.size(); i++) {
+			auto* checkbox = dynamic_cast<nanogui::CheckBox*>(childAt(i));
+			checkbox->setChecked(all_flags[i] & defaults_);
+		}
+	});
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+using ftl::gui2::IntrinsicDetails;
+
+IntrinsicDetails::IntrinsicDetails(nanogui::Widget* parent) :
+	nanogui::Widget(parent) {
+
+	setLayout(new nanogui::BoxLayout
+		(nanogui::Orientation::Vertical, nanogui::Alignment::Fill, 0 , 0));
+
+	params_ = new nanogui::Widget(this);
+	dist_ = new nanogui::Widget(this);
+	dist_->setLayout(new nanogui::BoxLayout
+		(nanogui::Orientation::Vertical, nanogui::Alignment::Fill, 0, 8));
+}
+
+void IntrinsicDetails::update(const ftl::calibration::CalibrationData::Intrinsic &values) {
+	while (params_->childCount() > 0) {
+		params_->removeChild(params_->childCount() - 1);
+	}
+	while (dist_->childCount() > 0) {
+		dist_->removeChild(dist_->childCount() - 1);
+	}
+	bool use_physical = values.sensorSize != cv::Size2d{0.0, 0.0};
+	nanogui::GridLayout* grid_layout;
+	if (use_physical) {
+		grid_layout = new nanogui::GridLayout
+			(nanogui::Orientation::Horizontal, 3, nanogui::Alignment::Fill, 0, 8);
+	}
+	else {
+		grid_layout = new nanogui::GridLayout
+			(nanogui::Orientation::Horizontal, 2, nanogui::Alignment::Fill, 0, 8);
+	}
+	grid_layout->setColAlignment
+		({nanogui::Alignment::Maximum, nanogui::Alignment::Fill});
+	params_->setLayout(grid_layout);
+
+	auto sw = values.sensorSize.width;
+	auto sh = values.sensorSize.height;
+	auto K = values.matrix();
+	auto imsize = values.resolution;
+
+	double fovx;
+	double fovy;
+	double f;
+	cv::Point2d pp;
+	double ar;
+	cv::calibrationMatrixValues(K, imsize, sw, sh, fovx, fovy, f, pp, ar);
+
+	new nanogui::Label(params_, "Image size:");
+	if (use_physical) new nanogui::Label(params_, to_string(sw, 1) + std::string("x") + to_string(sh, 1));
+	new nanogui::Label(params_, std::to_string(imsize.width) + std::string("x") + std::to_string(imsize.height));
+
+	new nanogui::Label(params_, "Focal length:");
+	if (use_physical) new nanogui::Label(params_, to_string(f) + " mm");
+	new nanogui::Label(params_,
+		((values.fx == values.fy) ? to_string(values.fx) + " px": (
+		"(" + to_string(values.fx) + ", "
+			+ to_string(values.fy) + ")")));
+
+	new nanogui::Label(params_, "Principal point:");
+	if (use_physical) new nanogui::Label(params_,
+			"(" + to_string(pp.x) + ", " +
+				to_string(pp.y) + ")");
+
+	new nanogui::Label(params_,
+		"(" + to_string(values.cx) + ", " +
+			  to_string(values.cy) + ")");
+
+	new nanogui::Label(params_, "Field of View (x):");
+	new nanogui::Label(params_, to_string(fovx) + "°");
+	if (use_physical) new nanogui::Label(params_, "");
+
+	new nanogui::Label(params_, "Field of View (y):");
+	new nanogui::Label(params_, to_string(fovy)+ "°");
+	if (use_physical) new nanogui::Label(params_, "");
+
+	new nanogui::Label(params_, "Aspect ratio:");
+	new nanogui::Label(params_, to_string(ar));
+	if (use_physical) new nanogui::Label(params_, "");
+
+	std::string pK;
+	std::string pP;
+	std::string pS;
+	auto& D = values.distCoeffs;
+
+	pK += "K1: " + to_string(D[0] ,3);
+	pK += ", K2: " + to_string(D[1] ,3);
+	pP += "P1: " + to_string(D[2], 3);
+	pP += ", P2: " + to_string(D[3], 3);
+
+	pK += ", K3: " + to_string(D[4], 3);
+
+	pK += ", K4: " + to_string(D[5] ,3);
+	pK += ", K5: " + to_string(D[6] ,3);
+	pK += ", K6: " + to_string(D[7] ,3);
+
+	pS += "S1: " + to_string(D[8] ,3);
+	pS += ", S2: " + to_string(D[9] ,3);
+	pS += ", S3: " + to_string(D[10] ,3);
+	pS += ", S4: " + to_string(D[11] ,3);
+
+	if (!pK.empty()) new nanogui::Label(dist_, pK);
+	if (!pP.empty()) new nanogui::Label(dist_, pP);
+	if (!pS.empty()) new nanogui::Label(dist_, pS);
+}
diff --git a/applications/gui2/src/views/calibration/widgets.hpp b/applications/gui2/src/views/calibration/widgets.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..381f2c9bc1a40274137d1fa621b99513eabf4dca
--- /dev/null
+++ b/applications/gui2/src/views/calibration/widgets.hpp
@@ -0,0 +1,34 @@
+#pragma once
+
+#include <nanogui/widget.h>
+
+#include <ftl/calibration/structures.hpp>
+
+#include "../../modules/calibration/calibration.hpp"
+
+namespace ftl {
+namespace gui2 {
+
+class OpenCVFlagWidget : public nanogui::Widget {
+public:
+	OpenCVFlagWidget(nanogui::Widget* parent, OpenCVCalibrateFlags* flags, int defaultv=-1);
+	void reset();
+	void setDefaults(int v) { defaults_ = v; }
+
+private:
+	OpenCVCalibrateFlags* flags_;
+	int defaults_;
+};
+
+class IntrinsicDetails : public nanogui::Widget {
+public:
+	IntrinsicDetails(nanogui::Widget* parent);
+	void update(const ftl::calibration::CalibrationData::Intrinsic &values);
+
+private:
+	nanogui::Widget* params_;
+	nanogui::Widget* dist_;
+};
+
+}
+}
\ No newline at end of file
diff --git a/components/calibration/include/ftl/calibration/structures.hpp b/components/calibration/include/ftl/calibration/structures.hpp
index 45217f82bd0b144d8974174f8b15fab8fe524277..c93f31404c6b06cc9fb0d82209b4516c10317147 100644
--- a/components/calibration/include/ftl/calibration/structures.hpp
+++ b/components/calibration/include/ftl/calibration/structures.hpp
@@ -45,6 +45,9 @@ struct CalibrationData {
 		Intrinsic(const cv::Mat &K, cv::Size sz);
 		Intrinsic(const cv::Mat &K, const cv::Mat &D, cv::Size sz);
 
+		/** New instance with scaled values for new resolution */
+		Intrinsic(const Intrinsic& other, cv::Size sz);
+
 		/** Camera matrix */
 		cv::Mat matrix() const;
 		/** Camera matrix (scaled) */
@@ -56,7 +59,11 @@ struct CalibrationData {
 		double cx;
 		double cy;
 		DistortionCoefficients distCoeffs;
-		MSGPACK_DEFINE(resolution, fx, fy, cx, cy, distCoeffs);
+
+		/** (optional) sensor size */
+		cv::Size2d sensorSize;
+
+		MSGPACK_DEFINE(resolution, fx, fy, cx, cy, distCoeffs, sensorSize);
 	};
 	struct Extrinsic {
 		Extrinsic();
diff --git a/components/calibration/src/structures.cpp b/components/calibration/src/structures.cpp
index f0614cd13cd5063d5e253838f4fcfe44ff6340f8..755c44458c171c6692e0569d5fb691a994d43f97 100644
--- a/components/calibration/src/structures.cpp
+++ b/components/calibration/src/structures.cpp
@@ -56,6 +56,17 @@ CalibrationData::Intrinsic::Intrinsic(const cv::Mat &K, const cv::Mat &D, cv::Si
 	D.copyTo(distCoeffs.Mat(D.cols));
 }
 
+CalibrationData::Intrinsic::Intrinsic(const CalibrationData::Intrinsic& other, cv::Size size) {
+	distCoeffs = DistortionCoefficients(other.distCoeffs);
+	sensorSize = other.sensorSize;
+	auto K = other.matrix(size);
+	fx = K.at<double>(0, 0);
+	fy = K.at<double>(1, 1);
+	cx = K.at<double>(0, 2);
+	cy = K.at<double>(1, 2);
+	resolution = size;
+}
+
 cv::Mat CalibrationData::Intrinsic::matrix() const {
 	cv::Mat K(cv::Size(3, 3), CV_64FC1, cv::Scalar(0));
 	K.at<double>(0, 0) = fx;
@@ -75,7 +86,7 @@ cv::Mat CalibrationData::Intrinsic::matrix(cv::Size size) const {
 CalibrationData::Extrinsic::Extrinsic() : rvec(0.0, 0.0, 0.0), tvec(0.0, 0.0, 0.0) {}
 
 CalibrationData::Extrinsic::Extrinsic(const cv::Mat &T) {
-	
+
 	if (T.type() != CV_64FC1) {
 		throw ftl::exception("Input must be CV_64FC1");
 	}
@@ -91,15 +102,15 @@ CalibrationData::Extrinsic::Extrinsic(const cv::Mat &T) {
 }
 
 CalibrationData::Extrinsic::Extrinsic(cv::InputArray R, cv::InputArray t) {
-	
+
 	if ((t.type() != CV_64FC1) || (R.type() != CV_64FC1)) {
 		throw ftl::exception("R and t must be CV_64FC1");
 	}
-	
+
 	if ((t.size() != cv::Size(3, 1)) && (t.size() != cv::Size(1, 3))) {
 		throw ftl::exception("t must be have size (3, 1) or (1, 3");
 	}
-	
+
 	if (R.isMat()) {
 		const auto& rmat = R.getMat();
 
@@ -158,6 +169,7 @@ CalibrationData CalibrationData::readFile(const std::string &path) {
 		(*it)["cx"] >> calib.intrinsic.cx;
 		(*it)["cy"] >> calib.intrinsic.cy;
 		(*it)["distCoeffs"] >> calib.intrinsic.distCoeffs.data_;
+		(*it)["sensorSize"] >> calib.intrinsic.sensorSize;
 		(*it)["rvec"] >> calib.extrinsic.rvec;
 		(*it)["tvec"] >> calib.extrinsic.tvec;
 
@@ -176,7 +188,7 @@ void CalibrationData::writeFile(const std::string &path) const {
 	fs << "enabled" << enabled;
 	fs << "calibration" << "[";
 	for (auto &[channel, data] : data_) {
-		fs	<< "{:"
+		fs	<< "{"
 			<<	"channel" << int(channel)
 			<<	"resolution" << data.intrinsic.resolution
 			<<	"fx" << data.intrinsic.fx
@@ -186,6 +198,7 @@ void CalibrationData::writeFile(const std::string &path) const {
 			<<	"distCoeffs" << data.intrinsic.distCoeffs.data_
 			<< 	"rvec" << data.extrinsic.rvec
 			<< 	"tvec" << data.extrinsic.tvec
+			<< 	"sensorSize" << data.intrinsic.sensorSize
 			<< "}";
 	}
 	fs << "]";