diff --git a/components/calibration/include/ftl/calibration/optimize.hpp b/components/calibration/include/ftl/calibration/optimize.hpp
index 77fd1b6d64b67fbbdc714db04e5ec52d38803630..5a2911732d76f31bf3f53ea868ed8b5cc9c7e753 100644
--- a/components/calibration/include/ftl/calibration/optimize.hpp
+++ b/components/calibration/include/ftl/calibration/optimize.hpp
@@ -15,7 +15,7 @@
 
 #include <opencv2/core/core.hpp>
 
-// BundleAdjustment uses Point3d instances with cast to double*
+// BundleAdjustment uses Point3d instances via double*
 static_assert(sizeof(cv::Point2d) == 2*sizeof(double));
 static_assert(std::is_standard_layout<cv::Point2d>());
 static_assert(sizeof(cv::Point3d) == 3*sizeof(double));
@@ -28,7 +28,7 @@ namespace calibration {
  * Camera paramters (Ceres)
  */
 struct Camera {
-	Camera() { data[Parameter::Q1] = 1.0; }
+	Camera() {}
 	Camera(const cv::Mat& K, const cv::Mat& D, const cv::Mat& R, const cv::Mat& tvec, cv::Size size);
 	Camera(const CalibrationData::Calibration& calib);
 
@@ -66,6 +66,8 @@ struct Camera {
 
 	const static int n_parameters = 18;
 	const static int n_distortion_parameters = 8;
+
+	bool quaternion = false;
 	double data[n_parameters] = {0.0};
 
 	enum Parameter {
@@ -259,7 +261,6 @@ private:
 	std::vector<Camera*> cameras_;
 	std::vector<BundleAdjustment::Point> points_;
 	std::vector<BundleAdjustment::Object> objects_;
-	Camera dummy_camera_; // contains identity rotation+translation
 };
 
 #endif
diff --git a/components/calibration/src/extrinsic.cpp b/components/calibration/src/extrinsic.cpp
index cb8e82b33c0c65450250900b49a9f6ced45ea3bb..a1381fd476da5cb798423382efabfbe415b548fe 100644
--- a/components/calibration/src/extrinsic.cpp
+++ b/components/calibration/src/extrinsic.cpp
@@ -635,23 +635,10 @@ double ExtrinsicCalibration::optimize() {
 	cameras.reserve(calib_.size());
 	unsigned int ncameras = calib_.size();
 
-	for (unsigned i = 0; i < calib_.size(); i++) {
-		const auto& calib = calib_[i];
-		T.push_back(calib.extrinsic.inverse().matrix());
-		if (i%2 == 1) {
-			// Ugly hack to use stereo camera parametrization in
-			// BundleAdjustment (would require significant changes to implement
-			// properly). Use l->r extrinsics for right camera (always odd index)
-			auto cam = calib;
-			cam.extrinsic = CalibrationData::Extrinsic
-				(calib.extrinsic.matrix() * calib_[i - 1].extrinsic.inverse().matrix());
-
-			cameras.push_back(Camera(cam));
-		}
-		else {
-			// left camera (even index), use common extrinsic parameters
-			cameras.push_back(Camera(calib));
-		}
+	for (const auto& c : calib_) {
+		auto camera = c;
+		T.push_back(c.extrinsic.inverse().matrix());
+		cameras.push_back(Camera(camera));
 	}
 	for (auto &c : cameras) {
 		// BundleAdjustment stores pointers; do not resize cameras vector
@@ -761,8 +748,6 @@ double ExtrinsicCalibration::optimize() {
 
 	int n_removed = 0;
 	for (const auto& t : prune_observations_) {
-		// Doesn't work without using reprojection error to with stereocam
-		// hacks ...
 		n_removed +=  ba.removeObservations(t);
 		if (float(n_removed)/float(n_points) > threhsold_warning_) {
 			LOG(WARNING) << "significant number of observations removed";
@@ -782,15 +767,7 @@ double ExtrinsicCalibration::optimize() {
 		auto intr = cameras[i].intrinsic();
 		calib_optimized_[i] = calib_[i];
 		calib_optimized_[i].intrinsic.set(intr.matrix(), intr.distCoeffs.Mat(), intr.resolution);
-
-		if (i%2 == 1) {
-			// HACK!!
-			cv::Mat T = cameras[i].extrinsicMatrix() * cameras[i - 1].extrinsicMatrix();
-			calib_optimized_[i].extrinsic.set(T);
-		}
-		else {
-			calib_optimized_[i].extrinsic.set(cameras[i].rvec(), cameras[i].tvec());
-		}
+		calib_optimized_[i].extrinsic.set(cameras[i].rvec(), cameras[i].tvec());
 	}
 
 	rmse_total_ = ba.reprojectionError();
diff --git a/components/calibration/src/optimize.cpp b/components/calibration/src/optimize.cpp
index 8f8089cb614fa18d8bc6ac009eba8f739aac1877..40005872504cdcb01c0a46907b84b1bb636c3b5d 100644
--- a/components/calibration/src/optimize.cpp
+++ b/components/calibration/src/optimize.cpp
@@ -99,14 +99,12 @@ void Camera::setDistortion(const Mat& D) {
 }
 
 Camera::Camera(const Mat &K, const Mat &D, const Mat &R, const Mat &tvec, cv::Size sz) {
-	data[Parameter::Q1] = 1.0;
 	setIntrinsic(K, D, sz);
 	if (!R.empty()) { setRotation(R); }
 	if (!tvec.empty()) { setTranslation(tvec); }
 }
 
 Camera::Camera(const ftl::calibration::CalibrationData::Calibration& calib) {
-	data[Parameter::Q1] = 1.0;
 	setIntrinsic(calib.intrinsic.matrix(), calib.intrinsic.distCoeffs.Mat(), calib.intrinsic.resolution);
 	setExtrinsic(calib.extrinsic.matrix()(cv::Rect(0, 0, 3, 3)), cv::Mat(calib.extrinsic.tvec));
 }
@@ -188,67 +186,21 @@ Mat Camera::extrinsicMatrixInverse() const {
 
 ////////////////////////////////////////////////////////////////////////////////
 
-/*
-* Camera model has 18 parameters (with quaternion rotation):
-*
-* - rotation and translation: q1, q2, q3, q4, tx, ty, tx
-* - focal length: f (fx == fy assumed)
-* - pricipal point: cx, cy
-* - distortion coefficients: k1, k2, k3, k4, k5, k6, p1, p2
-*
-* 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
-*/
-template<typename T>
-void projectPoint_(const T* const camera, const T* const point, T* const projected) {
-
-	T p[3];
-	ceres::QuaternionRotatePoint(camera + Camera::Parameter::ROTATION, point, p);
-
-	p[0] += camera[Camera::Parameter::TX];
-	p[1] += camera[Camera::Parameter::TY];
-	p[2] += camera[Camera::Parameter::TZ];
-
-	T x = p[0]/p[2];
-	T y = p[1]/p[2];
-
-	// Intrinsic parameters
-	const T& f = camera[Camera::Parameter::F];
-	const T& cx = camera[Camera::Parameter::CX];
-	const T& cy = camera[Camera::Parameter::CY];
-
-	// Distortion parameters
-	const T& k1 = camera[Camera::Parameter::K1];
-	const T& k2 = camera[Camera::Parameter::K2];
-	const T& k3 = camera[Camera::Parameter::K3];
-	const T& k4 = camera[Camera::Parameter::K4];
-	const T& k5 = camera[Camera::Parameter::K5];
-	const T& k6 = camera[Camera::Parameter::K6];
-	const T& p1 = camera[Camera::Parameter::P1];
-	const T& p2 = camera[Camera::Parameter::P2];
-
-	const T r2 = x*x + y*y;
-	const T r4 = r2*r2;
-	const T r6 = r4*r2;
-
-	// Radial distortion
-	const T cdist = T(1.0) + k1*r2 + k2*r4 + k3*r6;
-	// Radial distortion: rational model
-	const T icdist = T(1.0)/(T(1.0) + k4*r2 + k5*r4 + k6*r6);
-	// Tangential distortion
-	const T pdistx =      (T(2.0)*x*y)*p1 + (r2 + T(2.0)*x*x)*p2;
-	const T pdisty = (r2 + T(2.0)*y*y)*p1 +      (T(2.0)*x*y)*p2;
-	// Apply distortion
-	const T xd = x*cdist*icdist + pdistx;
-	const T yd = y*cdist*icdist + pdisty;
-	// Projected point position
-	projected[0] = f*xd + cx;
-	projected[1] = f*yd + cy;
-}
-
 struct ReprojectionError {
-	/** Reprojection error */
+	/**
+	 * Reprojection error.
+	 *
+	 * Camera model has _CAMERA_PARAMETERS parameters:
+	 *
+	 * - rotation and translation: q1, q2, q3, q4, tx, ty, tx
+	 * - focal length: f (fx == fy assumed)
+	 * - pricipal point: cx, cy
+	 * - distortion coefficients: k1, k2, k3, k4, k5, k6, p1, p2
+	 *
+	 * 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) {}
 
@@ -257,12 +209,53 @@ struct ReprojectionError {
 					const T* const point,
 					T* residuals) const {
 
-		T predicted[2];
-		projectPoint_(camera, point, predicted);
+		T p[3];
+		ceres::QuaternionRotatePoint(camera + Camera::Parameter::ROTATION, point, p);
+
+
+		p[0] += camera[Camera::Parameter::TX];
+		p[1] += camera[Camera::Parameter::TY];
+		p[2] += camera[Camera::Parameter::TZ];
+
+		T x = p[0] / p[2];
+		T y = p[1] / p[2];
+
+		// Intrinsic parameters
+		const T& f = camera[Camera::Parameter::F];
+		const T& cx = camera[Camera::Parameter::CX];
+		const T& cy = camera[Camera::Parameter::CY];
+
+		// Distortion parameters
+		const T& k1 = camera[Camera::Parameter::K1];
+		const T& k2 = camera[Camera::Parameter::K2];
+		const T& k3 = camera[Camera::Parameter::K3];
+		const T& k4 = camera[Camera::Parameter::K4];
+		const T& k5 = camera[Camera::Parameter::K5];
+		const T& k6 = camera[Camera::Parameter::K6];
+		const T& p1 = camera[Camera::Parameter::P1];
+		const T& p2 = camera[Camera::Parameter::P2];
+
+		const T r2 = x*x + y*y;
+		const T r4 = r2*r2;
+		const T r6 = r4*r2;
+
+		// Radial distortion
+		const T cdist = T(1.0) + k1*r2 + k2*r4 + k3*r6;
+		// Radial distortion: rational model
+		const T icdist = T(1.0)/(T(1.0) + k4*r2 + k5*r4 + k6*r6);
+		// Tangential distortion
+		const T pdistx =      (T(2.0)*x*y)*p1 + (r2 + T(2.0)*x*x)*p2;
+		const T pdisty = (r2 + T(2.0)*y*y)*p1 +      (T(2.0)*x*y)*p2;
+		// Apply distortion
+		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;
 
 		// Error: the difference between the predicted and observed position
-		residuals[0] = predicted[0] - T(observed_x);
-		residuals[1] = predicted[1] - T(observed_y);
+		residuals[0] = predicted_x - T(observed_x);
+		residuals[1] = predicted_y - T(observed_y);
 
 		return true;
 	}
@@ -278,60 +271,15 @@ struct ReprojectionError {
 					new ReprojectionError(observed.x, observed.y)));
 	}
 
-	const double observed_x;
-	const double observed_y;
+	double observed_x;
+	double observed_y;
 };
 
-struct ReprojectionErrorStereo {
-	/** Reprojection error for stereo camera pairs. Second camera's extrinsic
-	 * parameters are rotation and translation from first camera to second.
-	 */
-
-	explicit ReprojectionErrorStereo(double observed_x, double observed_y)
-		: observed_x(observed_x), observed_y(observed_y) {}
-
-	template<typename T>
-	bool operator()(const T* const camera1, const T* const camera2,
-					const T* const point,
-					T* residuals) const {
-
-		T p[3];
-		T predicted[2];
-		ceres::QuaternionRotatePoint(camera1 + Camera::Parameter::ROTATION, point, p);
-		p[0] += camera1[Camera::Parameter::TX];
-		p[1] += camera1[Camera::Parameter::TY];
-		p[2] += camera1[Camera::Parameter::TZ];
-
-		projectPoint_(camera2, p, predicted);
-
-		// Error: the difference between the predicted and observed position
-		residuals[0] = predicted[0] - T(observed_x);
-		residuals[1] = predicted[1] - T(observed_y);
-
-		return true;
-	}
-
-	static ceres::CostFunction* Create(	const double observed_x,
-										const double observed_y) {
-		return (new ceres::AutoDiffCostFunction<ReprojectionErrorStereo, 2, Camera::n_parameters, Camera::n_parameters, 3>(
-					new ReprojectionErrorStereo(observed_x, observed_y)));
-	}
-
-	static ceres::CostFunction* Create(	const Point2d &observed) {
-		return (new ceres::AutoDiffCostFunction<ReprojectionErrorStereo, 2, Camera::n_parameters, Camera::n_parameters, 3>(
-					new ReprojectionErrorStereo(observed.x, observed.y)));
-	}
-
-	const double observed_x;
-	const double observed_y;
-};
+static ReprojectionError reproject_ = ReprojectionError(0.0, 0.0);
 
 cv::Point2d ftl::calibration::projectPoint(const Camera& camera, const cv::Point3d& point) {
 	cv::Point2d out;
-	projectPoint_<double>(
-		static_cast<const double* const>(camera.data),
-		reinterpret_cast<const double* const>(&(point.x)),
-		reinterpret_cast<double*>(&(out.x)));
+	reproject_(static_cast<const double* const>(camera.data), reinterpret_cast<const double* const>(&(point.x)), reinterpret_cast<double*>(&(out.x)));
 	return out;
 }
 
@@ -619,6 +567,7 @@ void BundleAdjustment::_setCameraParametrization(ceres::Problem &problem, const
 					camera_parameterization =
 						new ceres::SubsetParameterization(Camera::n_parameters, params);
 				}
+
 				problem.SetParameterization(getCameraPtr(i), camera_parameterization);
 			}
 		}
@@ -636,35 +585,19 @@ void BundleAdjustment::_buildBundleAdjustmentProblem(ceres::Problem &problem, co
 		loss_function = new ceres::CauchyLoss(1.0);
 	}
 
-	// dummy camera here as a quick hack so both cameras can use same cost
-	// function so setCameraParametrization above works as expected
 	for (auto& point : points_) {
-		for (size_t i = 0; i < cameras_.size(); i++) {
+		for (size_t i = 0; i < point.observations.size(); i++) {
+			if (!point.visibility[i]) { continue; }
 			ceres::CostFunction* cost_function =
-					ReprojectionErrorStereo::Create(point.observations[i]);
+				ReprojectionError::Create(point.observations[i]);
 
-			if (!point.visibility[i]) { continue; }
-			if (i%2 == 0) {
-				// left camera
-				problem.AddResidualBlock(cost_function,
-							loss_function,
-							&(dummy_camera_.data[0]),
-							getCameraPtr(i),
-							&(point.point.x)
-				);
-			}
-			else {
-				// right camera
-				problem.AddResidualBlock(cost_function,
-							loss_function,
-							getCameraPtr(i - 1),
-							getCameraPtr(i),
-							&(point.point.x)
-				);
-			}
+			problem.AddResidualBlock(cost_function,
+						loss_function,
+						getCameraPtr(i),
+						&(point.point.x)
+			);
 		}
 	}
-	problem.SetParameterBlockConstant(&(dummy_camera_.data[0]));
 
 	_setCameraParametrization(problem, options);
 
@@ -687,7 +620,7 @@ void BundleAdjustment::run(const BundleAdjustment::Options &bundle_adjustment_op
 	_buildProblem(problem, bundle_adjustment_options);
 
 	ceres::Solver::Options options;
-	options.linear_solver_type = ceres::DENSE_SCHUR; // http://ceres-solver.org/solving_faqs.html
+	options.linear_solver_type = ceres::DENSE_SCHUR;
 	options.minimizer_progress_to_stdout = bundle_adjustment_options.verbose;
 
 	if (bundle_adjustment_options.max_iter > 0) {
@@ -735,8 +668,7 @@ int BundleAdjustment::removeObservations(double threshold) {
 		}
 		error_total /= n_points;
 
-	 	// should remove (counted in mean error if left)
-		if (n_points <= 1) { continue; }
+		if (n_points <= 1) { continue; } // TODO: remove observation completely
 
 		for (unsigned int c = 0; c < cameras_.size(); c++) {
 			if (!point.visibility[c]) { continue; }