diff --git a/cv-node/src/algorithms/rtcensus.cpp b/cv-node/src/algorithms/rtcensus.cpp
index a2c323ea32239fc93157e05f255e4ea33a4c4ca7..ee7650cfa45bd2ada7092651d883cc0221f908c8 100644
--- a/cv-node/src/algorithms/rtcensus.cpp
+++ b/cv-node/src/algorithms/rtcensus.cpp
@@ -245,10 +245,19 @@ void RTCensus::computeCUDA(const cv::Mat &l, const cv::Mat &r, cv::Mat &disp) {
 	if (left2_.empty()) left2_ = cuda::GpuMat(l.size(), CV_8UC4);
 	if (right_.empty()) right_ = cuda::GpuMat(l.size(), CV_8UC4);
 	
+	Mat lhsv, rhsv;
+	cv::cvtColor(l, lhsv,  COLOR_BGR2HSV);
+	cv::cvtColor(r, rhsv, COLOR_BGR2HSV);
+	int from_to[] = {0,0,1,1,2,2,-1,3};
+	Mat hsval(lhsv.size(), CV_8UC4);
+	Mat hsvar(rhsv.size(), CV_8UC4);
+	mixChannels(&lhsv, 1, &hsval, 1, from_to, 4);
+	mixChannels(&rhsv, 1, &hsvar, 1, from_to, 4);
+	
 	// Send images to GPU
-	if (alternate_) left_.upload(l);
-	else left2_.upload(l);
-	right_.upload(r);
+	if (alternate_) left_.upload(hsval);
+	else left2_.upload(hsval);
+	right_.upload(hsvar);
 
 	auto start = std::chrono::high_resolution_clock::now();
 	ftl::gpu::rtcensus_call((alternate_)?left_:left2_, right_, disp_, max_disp_);
diff --git a/cv-node/src/algorithms/rtcensus.cu b/cv-node/src/algorithms/rtcensus.cu
index 583a823876bf089d832a2263921011ec339b56d1..82e911735e91f5782d8bc848be57f90f94e7c940 100644
--- a/cv-node/src/algorithms/rtcensus.cu
+++ b/cv-node/src/algorithms/rtcensus.cu
@@ -16,6 +16,10 @@
 using namespace cv::cuda;
 using namespace cv;
 
+/* Grid stride loop macros */
+#define STRIDE_Y(I,N) int I = blockIdx.y * blockDim.y + threadIdx.y; I < N; I += blockDim.y * gridDim.y
+#define STRIDE_X(I,N) int I = blockIdx.x * blockDim.x + threadIdx.x; I < N; I += blockDim.x * gridDim.x
+
 #define BLOCK_W 60
 #define RADIUS 7
 #define RADIUS2 2
@@ -255,37 +259,38 @@ __global__ void disp_kernel(float *disp_l, float *disp_r,
 	}
 }
 
+/*
+ * Check for consistency between the LR and RL disparity maps, only selecting
+ * those that are similar. Otherwise it sets the disparity to NAN.
+ */
 __global__ void consistency_kernel(cudaTextureObject_t d_sub_l,
 		cudaTextureObject_t d_sub_r, float *disp, int w, int h, int pitch) {
-	//Mat result = Mat::zeros(Size(w,h), CV_32FC1);
-	
-	int u = (blockIdx.x * BLOCK_W) + threadIdx.x + RADIUS;
-	int v_start = (blockIdx.y * ROWSperTHREAD) + RADIUS;
-	int v_end = v_start + ROWSperTHREAD;
-	
-	if (v_end >= h) v_end = h;
-	if (u >= w) return;
-	
-	for (int v=v_start; v<v_end; v++) {
-	
+
+	// TODO This doesn't work at either edge (+-max_disparities)
+	for (STRIDE_Y(v,h)) {
+	for (STRIDE_X(u,w)) {
 		float a = (int)tex2D<float>(d_sub_l, u, v);
 		if (u-a < 0) continue;
 		
 		auto b = tex2D<float>(d_sub_r, u-a, v);
-
-		//disp(v,u) = a; //abs((a+b)/2);
 		
-		if (abs(a-b) <= 1.0) disp[v*pitch+u] = (u < 300) ? b : a;//abs((a+b)/2); // was 1.0
+		if (abs(a-b) <= 1.0) disp[v*pitch+u] = abs((a+b)/2);
 		else disp[v*pitch+u] = NAN;
-	//}
+	}
 	}
 
 }
 
-#define FILTER_WINDOW 11
-#define FILTER_WINDOW_R	5
+#define FILTER_WINDOW 31
+#define FILTER_WINDOW_R	15
 #define FILTER_SIM_THRESH 5
-#define FILTER_DISP_THRESH 50.0f
+#define FILTER_DISP_THRESH 2.0f
+
+__device__ int colour_error(uchar4 v1, uchar4 v2) {
+	int dx = abs(v1.x-v2.x);
+	int dz = abs(v1.z-v2.z);
+	return dx*dx + dz*dz;
+}
 
 __global__ void filter_kernel(cudaTextureObject_t t, cudaTextureObject_t d,
 		cudaTextureObject_t prevD,
@@ -306,40 +311,50 @@ __global__ void filter_kernel(cudaTextureObject_t t, cudaTextureObject_t d,
 	float pdisp = tex2D<float>(nDisp,u,v);
 	if (isnan(pdisp)) pdisp = disp;
 	if (isnan(disp)) disp = pdisp;
-	int pixel = tex2D<uchar4>(t, u, v).x;
-	int ppixel = tex2D<uchar4>(nTex, u, v).x;
+	uchar4 pixel = tex2D<uchar4>(t, u, v);
+	uchar4 ppixel = tex2D<uchar4>(nTex, u, v);
 	float est = 0.0f; //(isnan(disp)) ? tex2D<float>(prev, u, v) : disp;
 	int nn = 0; //(isnan(disp)) ? 0 : 1;
 	int neigh_sq = 0;
 	int neigh_sum = 0;
 	
-	/*if (abs(ppixel-pixel) <= FILTER_SIM_THRESH) {
+	if (isnan(pdisp)) {
+		f(v,u) = disp;
+	} else if (!isnan(disp) && abs(pdisp-disp) <= FILTER_DISP_THRESH) {
+		f(v,u) = (disp+pdisp) / 2;
+	} else {
+		f(v,u) = disp;
+	}
+	return;
+	
+	/*if (!isnan(disp)) {// && colour_error(ppixel,pixel) <= FILTER_SIM_THRESH) {
 		f(v,u) = disp;
+		return;
 	} else {
 		f(v,u) = NAN;
 	}
 	return;*/
 	
-	if (!isnan(pdisp) && isnan(disp) && abs(pixel-ppixel) <= FILTER_SIM_THRESH) {
-		disp = pdisp;
-	}
+	//if (!isnan(pdisp) && isnan(disp) && colour_error(pixel,ppixel) <= FILTER_SIM_THRESH) {
+	//	disp = pdisp;
+	//}
 
 	for (int m=-FILTER_WINDOW_R; m<=FILTER_WINDOW_R; m++) {
 		for (int n=-FILTER_WINDOW_R; n<=FILTER_WINDOW_R; n++) {
-			int neigh = tex2D<uchar4>(t, u+n, v+m).x;
-			neigh_sq += neigh*neigh;
-			neigh_sum += neigh;
+			uchar4 neigh = tex2D<uchar4>(t, u+n, v+m);
+			//neigh_sq += neigh*neigh;
+			//neigh_sum += neigh;
 
 			float ndisp = tex2D<float>(d,u+n,v+m);
-			/*if (isnan(ndisp)) {
+			if (isnan(ndisp)) {
 				ndisp = tex2D<float>(nDisp,u+n,v+m);
-				neigh = tex2D<uchar4>(nTex, u+n, v+m).x;
-			}*/
+				neigh = tex2D<uchar4>(nTex, u+n, v+m);
+			}
 
 			//if (isnan(tex2D<float>(nDisp,u+n,v+m))) continue;
 			if (m == 0 && n == 0) continue;
 
-			if (ndisp > 1.0f && !isnan(ndisp) && (abs(neigh-pixel) <= FILTER_SIM_THRESH)) { // && (isnan(disp) || abs(ndisp-disp) < FILTER_DISP_THRESH)) {
+			if (!isnan(ndisp) && (colour_error(neigh,pixel) <= FILTER_SIM_THRESH)) { // && (isnan(disp) || abs(ndisp-disp) < FILTER_DISP_THRESH)) {
 				est += ndisp;
 				nn++;
 			}
@@ -347,17 +362,19 @@ __global__ void filter_kernel(cudaTextureObject_t t, cudaTextureObject_t d,
 	}
 
 	// Texture map filtering
-	int tm = (neigh_sq / (FILTER_WINDOW*FILTER_WINDOW)) - ((neigh_sum*neigh_sum) / (FILTER_WINDOW*FILTER_WINDOW));
-	//if (tm >= -9000000) {
+	//int tm = (neigh_sq / (FILTER_WINDOW*FILTER_WINDOW)) - ((neigh_sum*neigh_sum) / (FILTER_WINDOW*FILTER_WINDOW));
+	//if (tm >= -5000000) {
+	//	nn = 0;
+	//}
 			 // ) {
 		/*if (!isnan(disp) && disp > 1.0f) { // && (abs(ppixel-pixel) > FILTER_SIM_THRESH || abs(pdisp - disp) <= FILTER_DISP_THRESH)) {
 			f(v,u) = disp;
 		} else*/
-		if (nn > 4) {
+		if (nn > 10) {
 			f(v,u) = (est+disp) / (nn+1);
-		} else if (!isnan(pdisp) && abs(pixel-ppixel) <= FILTER_SIM_THRESH) {
+		} else if (!isnan(pdisp) && colour_error(pixel,ppixel) <= FILTER_SIM_THRESH) {
 			f(v,u) = pdisp;
-		} else f(v,u) = NAN;
+		} else f(v,u) = disp;
 	//} else {
 	//	f(v,u) = NAN;
 	//}
diff --git a/cv-node/src/main.cpp b/cv-node/src/main.cpp
index 157a1d65be95af2f3f549063c25381b0ea26c7b2..e0dbaa00b5d178fa834a9c23b2ef24e84ee88aef 100644
--- a/cv-node/src/main.cpp
+++ b/cv-node/src/main.cpp
@@ -132,48 +132,21 @@ static void run(const string &file) {
 		// Read back from buffer
 		sync->get(LEFT,l);
 		sync->get(RIGHT,r);
-		
-		// Black and white
-        cvtColor(l, lbw,  COLOR_BGR2HSV);
-        cvtColor(r, rbw, COLOR_BGR2HSV);
-        int from_to[] = {0,0,1,1,2,2,-1,3};
-        Mat hsval(lbw.size(), CV_8UC4);
-        Mat hsvar(lbw.size(), CV_8UC4);
-        mixChannels(&lbw, 1, &hsval, 1, from_to, 4);
-        mixChannels(&rbw, 1, &hsvar, 1, from_to, 4);
         
-        disparity->compute(hsval,hsvar,disparity32F);
-		//LOG(INFO) << "Disparity complete ";
+        // TODO Check other algorithms convert to BW
+        disparity->compute(l,r,disparity32F);
 		
 		disparity32F.convertTo(disparity32F, CV_32F);
-		//disparity32F += 10.0f; // TODO REMOVE
+		disparity32F += 10.0f; // TODO REMOVE
 		
 		// Clip the left edge
-		Rect rect((int)config["disparity"]["maximum"],7,disparity32F.cols-(int)config["disparity"]["maximum"],disparity32F.rows-14);
-		//disparity32F = disparity32F(rect);
-		//l = l(rect);
-		
-		// HACK to make bad pixels invisible.
-		//normalize(disparity32F, depth32F, 0, 255, NORM_MINMAX, CV_8U);
-		//r = Mat(l.size(), CV_8UC3, Vec3i(255,255,255));
-		//l.copyTo(r,depth32F);
-		
-		// TODO Send RGB+D data somewhere
-		
-		// Convert disparity to depth
-
-		//normalize(disparity32F, disparity32F, 0, 255, NORM_MINMAX, CV_8U);
-		
-		//cv::imshow("Disparity",filtered_disp);
-		//Mat i3d;
-		//const Mat &Q = calibrate.getQ();
-		
+		//Rect rect((int)config["disparity"]["maximum"],7,disparity32F.cols-(int)config["disparity"]["maximum"],disparity32F.rows-14);
 
 		if (config["display"]["points"]) {
 			cv::Mat Q_32F; // = (Mat_<double>(4,4) << 1, 0, 0, 0,  0, 1, 0, 0,  0, 0, 1, 0,  0, 0, 0, 1); //(4,4,CV_32F);
 			calibrate.getQ().convertTo(Q_32F,CV_32F);
 			cv::Mat_<cv::Vec3f> XYZ(disparity32F.rows,disparity32F.cols);   // Output point cloud
-			reprojectImageTo3D(disparity32F, XYZ, Q_32F, true);
+			reprojectImageTo3D(disparity32F, XYZ, Q_32F, false);
 			
 			//cv::imshow("Points",XYZ);
 			
@@ -208,7 +181,9 @@ static void run(const string &file) {
 		    }
         } else if (config["display"]["disparity"]) {
         	disparity32F = disparity32F / (float)config["disparity"]["maximum"];
+        	disparity32F.convertTo(disparity32F,CV_8U,255.0f);
         	//normalize(disparity32F, disparity32F, 0, 255, NORM_MINMAX, CV_8U);
+        	applyColorMap(disparity32F, disparity32F, cv::COLORMAP_JET);
 			cv::imshow("Disparity", disparity32F);
 			if(cv::waitKey(10) == 27){
 		        //exit if ESC is pressed