diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 1a2be0d55785081587eeda5b4b183d4d392e4f1a..d0a78f33e4b4cd469c14eb575206b49878aa6156 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,19 +1,24 @@
-windows job:
-  tags:
-    - win
-  script:
-    - 'call "C:/Program Files (x86)/Microsoft Visual Studio/2017/Community/VC/Auxiliary/Build/vcvars64.bat"'
-    - mkdir build
-    - cd build
-    - 'cmake -DWITH_PCL=FALSE -DCMAKE_GENERATOR_PLATFORM=x64 -DEigen3_DIR="C:/Program Files (x86)/Eigen3/share/eigen3/cmake" -DOpenCV_DIR="D:/opencv-4.0.1/build/install" -DCUDA_TOOLKIT_ROOT_DIR="C:/Program Files/NVIDIA GPU Computing Toolkit/CUDA/v10.1" ..'
-    - devenv ftl.utu.fi.sln /build Release
-  
-linux job:
+# Gitlab waits until all jobs for a stage are completed before moving to next
+# stage, so using stages for Windows/Linux builds would wait until the other is
+# finished before continuing.
+#
+# Perhaps relevant in future https://gitlab.com/gitlab-org/gitlab-ce/issues/47063
+
+stages:
+ - all
+# - build
+# - test
+# - deploy
+
+#cache:
+#  paths:
+#    - build/
+
+docker:
+  stage: all
   tags:
     - docker
-
   image: ubuntu:18.04
-  
   before_script:
     - export DEBIAN_FRONTEND=noninteractive
     - apt-get update -qq && apt-get install -y -qq g++ cmake git
@@ -24,3 +29,20 @@ linux job:
     - cmake ..
     - make
     - ctest --output-on-failure
+
+windows:
+  stage: all
+  variables:
+    CMAKE_ARGS: '-DWITH_PCL=FALSE -DCMAKE_GENERATOR_PLATFORM=x64 -DEigen3_DIR="C:/Program Files (x86)/Eigen3/share/eigen3/cmake" -DOpenCV_DIR="D:/opencv-4.0.1/build/install" -DCUDA_TOOLKIT_ROOT_DIR="C:/Program Files/NVIDIA GPU Computing Toolkit/CUDA/v10.1"'
+    DEPLOY_DIR: 'D:/Shared/AutoDeploy'
+  tags:
+    - win
+  script:
+    - 'call "C:/Program Files (x86)/Microsoft Visual Studio/2017/Community/VC/Auxiliary/Build/vcvars64.bat"'
+    - mkdir build
+    - cd build
+    - cmake %CMAKE_ARGS% ..
+    - devenv ftl.utu.fi.sln /build Release
+    - rmdir /q /s "%DEPLOY_DIR%/%CI_COMMIT_REF_SLUG%"
+    - mkdir "%DEPLOY_DIR%/%CI_COMMIT_REF_SLUG%"
+    - 'copy "applications\vision\Release\ftl-vision.exe" "%DEPLOY_DIR%\%CI_COMMIT_REF_SLUG%"'
\ No newline at end of file
diff --git a/applications/vision/src/main.cpp b/applications/vision/src/main.cpp
index ed63b5a18d651341f3b36e27d82db430c4ceaf58..14be06f2c5f21f065a0e63a7d3b553b2a46dd289 100644
--- a/applications/vision/src/main.cpp
+++ b/applications/vision/src/main.cpp
@@ -58,7 +58,7 @@ static void run(const string &file) {
 	Universe net(config["net"]);
 	LOG(INFO) << "Net started.";
 
-	RGBDSource *source = nullptr;
+	StereoVideoSource *source = nullptr;
 	source = new StereoVideoSource(config, file);
 	
 	// Allow remote users to access camera calibration matrix
@@ -143,6 +143,7 @@ static void run(const string &file) {
 
 		// Send RGB+Depth images for local rendering
 		if (prgb.rows > 0) display.render(prgb, pdepth, source->getParameters());
+		if (config["display"]["right"]) cv::imshow("Right: ", source->getRight());
 		display.wait(1);
 
 		// Wait for both pipelines to complete
@@ -160,12 +161,14 @@ static void run(const string &file) {
 }
 
 int main(int argc, char **argv) {
+	std::cout << "FTL Vision Node " << FTL_VERSION_LONG << std::endl;
 	auto paths = ftl::configure(argc, argv, "vision");
 	
 	config["paths"] = paths;
 
 	// Choose normal or middlebury modes
 	if (config["middlebury"]["dataset"] == "") {
+		std::cout << "Loading..." << std::endl;
 		run((paths.size() > 0) ? paths[0] : "");
 	} else {
 		ftl::middlebury::test(config);
diff --git a/components/net/cpp/src/universe.cpp b/components/net/cpp/src/universe.cpp
index 7103c8edae7b39cd4a2012fa35b890a8133edcb4..b78af39903a14d71a24cbc6aafc8820314efca43 100644
--- a/components/net/cpp/src/universe.cpp
+++ b/components/net/cpp/src/universe.cpp
@@ -235,6 +235,8 @@ void Universe::_run() {
 			continue;
 		}
 
+		unique_lock<mutex> lk(net_mutex_);
+
 		//If connection request is waiting
 		for (auto l : listeners_) {
 			if (l && l->isListening()) {
diff --git a/components/rgbd-sources/include/ftl/stereovideo_source.hpp b/components/rgbd-sources/include/ftl/stereovideo_source.hpp
index d59908722779bf7d5c513c8f870db44dcfb60ed0..998c3532e2c75bb7694c8bf2447cdaed971c476a 100644
--- a/components/rgbd-sources/include/ftl/stereovideo_source.hpp
+++ b/components/rgbd-sources/include/ftl/stereovideo_source.hpp
@@ -29,6 +29,8 @@ class StereoVideoSource : public RGBDSource {
 	void getRGBD(cv::Mat &rgb, cv::Mat &depth);
 	bool isReady();
 
+	const cv::Mat &getRight() const { return right_; }
+
 	static inline RGBDSource *create(nlohmann::json &config, ftl::net::Universe *net) {
 		return new StereoVideoSource(config, net);
 	}
diff --git a/components/rgbd-sources/src/calibrate.cpp b/components/rgbd-sources/src/calibrate.cpp
index ea6cd7d276c9d43bf39fd0b360921d3fb0ff83dc..f0b55daa1efa483d9b168119fc06c9c3268cc87b 100644
--- a/components/rgbd-sources/src/calibrate.cpp
+++ b/components/rgbd-sources/src/calibrate.cpp
@@ -274,9 +274,15 @@ bool Calibrate::_loadCalibration() {
     		map1_[1], map2_[1]);
 
 	// Re-distort
-	initUndistortRectifyMap(M1, Mat(), R1.t(), P1,
+	Mat P1_cam = (cv::Mat_<double>(3,3) << P1.at<double>(0, 0), P1.at<double>(0, 1) , P1.at<double>(0, 2),
+		P1.at<double>(1, 0), P1.at<double>(1, 1), P1.at<double>(1, 2),
+		P1.at<double>(2, 0), P1.at<double>(2, 1), P1.at<double>(2, 2));
+	Mat M1_trans = (cv::Mat_<double>(3, 4) << M1.at<double>(0, 0), M1.at<double>(0, 1), M1.at<double>(0, 2), -P1.at<double>(0, 3),
+		M1.at<double>(1, 0), M1.at<double>(1, 1), M1.at<double>(1, 2), -P1.at<double>(1, 3),
+		M1.at<double>(2, 0), M1.at<double>(2, 1), M1.at<double>(2, 2), -P1.at<double>(2, 3));
+	initUndistortRectifyMap(P1_cam, Mat(), R1.t(), P1,
 			img_size, CV_16SC2, imap1_, imap2_);
-	r1_ = R1.t();
+	r1_ = P1;
     return true;
 }
 
diff --git a/components/rgbd-sources/src/calibrate.hpp b/components/rgbd-sources/src/calibrate.hpp
index 78e8fde6af0dc28d327f0034e5eb915f1bdad0fc..2e12f0e0efca21577ad543db8a286d7bc13f9b4d 100644
--- a/components/rgbd-sources/src/calibrate.hpp
+++ b/components/rgbd-sources/src/calibrate.hpp
@@ -108,6 +108,7 @@ class Calibrate {
 	 * a 3D point cloud.
 	 */
 	const cv::Mat &getQ() const { return Q_; }
+	const cv::Mat &getCameraMatrix() const { return r1_; }
 
 	private:
 	bool _recalibrate(std::vector<std::vector<cv::Point2f>> *imagePoints,
diff --git a/components/rgbd-sources/src/stereovideo_source.cpp b/components/rgbd-sources/src/stereovideo_source.cpp
index 4b14dcabf155b802277f34c6a3ba70c7bf652826..101620ed7d1ca1578b081f49bc99eb45878f98fe 100644
--- a/components/rgbd-sources/src/stereovideo_source.cpp
+++ b/components/rgbd-sources/src/stereovideo_source.cpp
@@ -46,12 +46,13 @@ StereoVideoSource::StereoVideoSource(nlohmann::json &config, const string &file)
 	else LOG(INFO) << "Calibration initiated.";
 
 	// Generate camera parameters from Q matrix
-	cv::Mat q = calib_->getQ();
+	cv::Mat q = calib_->getCameraMatrix();
 	params_ = {
-		q.at<double>(2,3),	// FX
-		q.at<double>(2,3),	// FY
-		q.at<double>(0,3),	// Cx
-		q.at<double>(1,3),	// Cy
+		// TODO(Nick) Add fx and fy
+		q.at<double>(0,0),	// Fx
+		q.at<double>(1,1),	// Fy
+		q.at<double>(0,2),	// Cx
+		q.at<double>(1,2),	// Cy
 		(unsigned int)left_.cols,  // TODO (Nick)
 		(unsigned int)left_.rows,
 		0.0f,	// 0m min
@@ -107,5 +108,5 @@ void StereoVideoSource::getRGBD(cv::Mat &rgb, cv::Mat &depth) {
 	disp_->compute(left_, right_, disp);
 	rgb = left_;
 	disparityToDepth(disp, depth, calib_->getQ());
-	calib_->distort(rgb,depth);
+	//calib_->distort(rgb,depth);
 }