diff --git a/lib/libstereo/include/stereo.hpp b/lib/libstereo/include/stereo.hpp
index 303d81509aefc7b6cb033f8fda4414315fa06f47..e5680c0f997146902c58aaebd8a434b15a820ef6 100644
--- a/lib/libstereo/include/stereo.hpp
+++ b/lib/libstereo/include/stereo.hpp
@@ -3,10 +3,10 @@
 #include <opencv2/core/mat.hpp>
 #include <stereo_types.hpp>
 
-class StereoGTCensusSgm {
+class StereoGCensusSgm {
 public:
-	StereoGTCensusSgm();
-	~StereoGTCensusSgm();
+	StereoGCensusSgm();
+	~StereoGCensusSgm();
 
 	void compute(cv::InputArray l, cv::InputArray r, cv::OutputArray disparity);
 	void setPrior(cv::InputArray disp) {};
@@ -15,9 +15,9 @@ public:
 	struct Parameters {
 		int d_min = 0;
 		int d_max = 0;
-		unsigned short P1 = 5;
-		unsigned short P2 = 25;
-		float uniqueness = std::numeric_limits<unsigned short>::max();
+		float P1 = 0.0645;
+		float P2 = 1.2903;
+		float uniqueness = std::numeric_limits<float>::max();
 		int subpixel = 1; // subpixel interpolation method
 		bool lr_consistency = true;
 		int paths = AggregationDirections::HORIZONTAL |
@@ -27,9 +27,31 @@ public:
 	};
 	Parameters params;
 
+	enum Pattern {
+		DENSE,
+		SPARSE,
+		RANDOM,
+		GCT,
+	};
+
+	/**
+	 * Set pattern.
+	 *
+	 * 		DENSE: size required, param ignored
+	 * 		SPARSE: size and parama required, param is step (number of skipped pixels)
+	 * 		RANDOM: size and param required, param is number of edges
+	 * 		GCT: param required, size ignored, param is pattern type (number of edges), see the paper for description
+	 */
+	void setPattern(Pattern type, cv::Size size, int param=-1);
+	/**
+	 * Set custom pattern.
+	 */
+	void setPattern(const std::vector<std::pair<cv::Point2i, cv::Point2i>> &edges);
+
 private:
 	struct Impl;
 	Impl *impl_;
+	std::vector<std::pair<cv::Point2i, cv::Point2i>> pattern_;
 };
 
 
diff --git a/lib/libstereo/middlebury/algorithms.hpp b/lib/libstereo/middlebury/algorithms.hpp
index f3878fe0ce8e14a6ab9d62a2ef6ef26ffda8382d..0b510df4a46349397b8b738884f570e7c1740df6 100644
--- a/lib/libstereo/middlebury/algorithms.hpp
+++ b/lib/libstereo/middlebury/algorithms.hpp
@@ -337,23 +337,26 @@ namespace Impl {
 		}
 	};
 
+	/** Generalized Census Transform */
 	struct GCTSgm : public Algorithm {
-		GCTSgm() { P1 = 12.0f; P2 = 52.0f; }  // Tuned to total error 2.0
+
+		GCTSgm() { P1 = 30.0f / float(9*7-1); P2 = 132.0f / float(9*7-1); }
 
 		virtual void run(const MiddleburyData &data, cv::Mat &disparity) override {
-			StereoGTCensusSgm stereo;
+			StereoGCensusSgm stereo;
+			stereo.setPattern(StereoGCensusSgm::Pattern::GCT, {0, 0}, 12);
+
 			stereo.params.P1 = P1;
 			stereo.params.P2 = P2;
 			stereo.params.subpixel = subpixel;
 			stereo.params.lr_consistency = lr_consistency;
 
-			stereo.params.debug = false;
+			stereo.params.debug = true;
 			stereo.params.d_min = data.calib.vmin;
 			stereo.params.d_max = data.calib.vmax;
 			stereo.compute(data.imL, data.imR, disparity);
 		}
 	};
-
 }
 
 static const std::map<std::string, Algorithm*> algorithms = {
diff --git a/lib/libstereo/src/algorithms/gct.cu b/lib/libstereo/src/algorithms/gct.cu
index ebe8d906cbcd555ca561f3fa5ddeb9719d3917df..cd070d1e4e15ac7d7abe4ac2df0af7ce26114bf6 100644
--- a/lib/libstereo/src/algorithms/gct.cu
+++ b/lib/libstereo/src/algorithms/gct.cu
@@ -2,64 +2,78 @@
 #include "stereosgm.hpp"
 #include "../costs/gct.hpp"
 
-struct StereoGTCensusSgm::Impl : public StereoSgm<GeneralizedCensusMatchingCost, StereoGTCensusSgm::Parameters> {
+struct StereoGCensusSgm::Impl : public StereoSgm<GeneralizedCensusMatchingCost, StereoGCensusSgm::Parameters> {
 	Array2D<uchar> l;
 	Array2D<uchar> r;
 
-	Impl(StereoGTCensusSgm::Parameters &params, int width, int height, int dmin, int dmax) :
+	Impl(StereoGCensusSgm::Parameters &params, int width, int height, int dmin, int dmax) :
 		StereoSgm(params, width, height, dmin, dmax), l(width, height), r(width, height) {}
 };
 
-StereoGTCensusSgm::StereoGTCensusSgm() : impl_(nullptr) {
+StereoGCensusSgm::StereoGCensusSgm() : impl_(nullptr) {
 	impl_ = new Impl(params, 0, 0, 0, 0);
 }
 
-void StereoGTCensusSgm::compute(cv::InputArray l, cv::InputArray r, cv::OutputArray disparity) {
-
-	//cudaSetDevice(0);
+void StereoGCensusSgm::compute(cv::InputArray l, cv::InputArray r, cv::OutputArray disparity) {
 
 	if (l.rows() != impl_->cost.height() || r.cols() != impl_->cost.width()) {
 		delete impl_; impl_ = nullptr;
 		impl_ = new Impl(params, l.cols(), l.rows(), params.d_min, params.d_max);
+		impl_->cost.setEdges(pattern_);
 	}
 
 	mat2gray(l, impl_->l);
 	mat2gray(r, impl_->r);
-
-	impl_->cost.setEdges({
-		{{-1,-1}, {1,1}},
-		{{1,-1}, {-1,1}},
-		{{0,-1}, {0,1}},
-		{{-1,0}, {1,0}},
-		{{-2,-2}, {2,2}},
-		{{2,-2}, {-2,2}},
-		{{0,-2}, {0,2}},
-		{{-2,0}, {2,0}}
-	});
-	/* wx * wy square window
-	std::vector<std::pair<std::pair<int, int>,std::pair<int, int>>> edges;
-	{
-		int wx = 7;
-		int wy = 7;
-
-		for (int x = -wx/2; x <= wx/2; x++) {
-			for (int y = -wy/2; y <= wy/2; y++) {
-				edges.push_back({{0,0},{y,x}});
-			}
-		}
-	}*/
-
 	impl_->cost.set(impl_->l, impl_->r);
 
 	cudaSafeCall(cudaDeviceSynchronize());
 	impl_->compute(disparity);
-	/*WinnerTakesAll<GeneralizedCensusMatchingCost> wta;
+
+	median_filter(impl_->wta.disparity, disparity);
+
+	/* without sgm:
+	mat2gray(l, impl_->l);
+	mat2gray(r, impl_->r);
+	impl_->cost.set(impl_->l, impl_->r);
+
+	WinnerTakesAll<GeneralizedCensusMatchingCost> wta;
 	wta(impl_->cost, 0, true);
 
-	median_filter(wta.disparity, disparity);*/
+	median_filter(wta.disparity, disparity);
+	*/
+}
+
+void StereoGCensusSgm::setPattern(StereoGCensusSgm::Pattern pattern, cv::Size size, int param) {
+	switch(pattern) {
+		case Pattern::DENSE:
+			pattern_ = pattern_dense(size);
+			break;
+
+		case Pattern::SPARSE:
+			pattern_ = pattern_sparse(size, param);
+			break;
+
+		case Pattern::RANDOM:
+			pattern_ = pattern_random(size, param);
+			break;
+
+		case Pattern::GCT:
+			pattern_ = pattern_gct(param);
+			break;
+
+		default:
+			printf("invalid pattern\n");
+			throw std::exception();
+	}
+	impl_->cost.setEdges(pattern_);
+}
+
+void StereoGCensusSgm::setPattern(const std::vector<std::pair<cv::Point2i, cv::Point2i>> &edges) {
+	pattern_ = edges;
+	impl_->cost.setEdges(edges);
 }
 
-StereoGTCensusSgm::~StereoGTCensusSgm() {
+StereoGCensusSgm::~StereoGCensusSgm() {
 	if (impl_) {
 		delete impl_;
 		impl_ = nullptr;
diff --git a/lib/libstereo/src/costs/census.cu b/lib/libstereo/src/costs/census.cu
index bd3e01357053a01c0fe3fd984e178f8c445b2837..124eda70375fb6ebdf2d6d4d7d79643d8718c95d 100644
--- a/lib/libstereo/src/costs/census.cu
+++ b/lib/libstereo/src/costs/census.cu
@@ -27,6 +27,10 @@ namespace algorithms {
 					const int y_ = y + wy;
 					const int x_ = x + wx;
 
+					if (y == 0 && x == 0) {
+						continue;
+					}
+
 					// zero if first value, otherwise shift to left
 					res = (res << 1);
 					res |= (center < (im(y_,x_)) ? 1 : 0);
diff --git a/lib/libstereo/src/costs/gct.cu b/lib/libstereo/src/costs/gct.cu
index 5d76340b969d16e4c0a37ce6d44820dc2ac7caae..f2b1a5d099c600d17815fffaf83fe8e60e211f00 100644
--- a/lib/libstereo/src/costs/gct.cu
+++ b/lib/libstereo/src/costs/gct.cu
@@ -1,114 +1,131 @@
 #include "gct.hpp"
 #include "../util.hpp"
 
-static const int WX = 11;
-static const int WY = 11;
+#include <random>
+
+static const int NBITS = 128;
 
 namespace algorithms {
 	/** Fife, W. S., & Archibald, J. K. (2012). Improved census transforms for
 	* resource-optimized stereo vision. IEEE Transactions on Circuits and
 	* Systems for Video Technology, 23(1), 60-73.
 	*/
-	template<int WINX, int WINY>
+
+	template<int BITS>
 	struct GeneralizedCensusTransform {
-		__host__ __device__ inline void window(const int y, const int x, uint64_t* __restrict__ out) {
+		static_assert(BITS%64 == 0);
+
+		__host__ __device__ inline void compute(const int y, const int x, uint64_t* __restrict__ out) {
 
 			uint8_t i = 0; // bit counter for *out
+			// BUG in operator(), gets called more than once per pixel; local
+			// variable for sub-bitstring to avoid data race (no read
+			// dependency to out; writes are identical)
+			uint64_t res = 0;
 
 			for (int e = 0; e < nedges; e++) {
 
 				// edges contain window indices, calculate window coordinates
-				const int y1 = y + edges(e,0) % WINY - WINY/2;
-				const int x1 = x + edges(e,0) / WINY - WINX/2;
+				//const int y1 = y + edges(e,0) % WINY - WINY/2;
+				//const int x1 = x + edges(e,0) / WINY - WINX/2;
+				//const int y2 = y + edges(e,1) % WINY - WINY/2;
+				//const int x2 = x + edges(e,1) / WINY - WINX/2;
 
-				const int y2 = y + edges(e,1) % WINY - WINY/2;
-				const int x2 = x + edges(e,1) / WINY - WINX/2;
+				// edges contain relative pixel coordinates
+				const int x1 = x + edges(e,0);
+				const int y1 = y + edges(e,1);
+				const int x2 = x + edges(e,2);
+				const int y2 = y + edges(e,3);
 
-				// zero if first value, otherwise shift to left
-				if (i % 64 == 0) { *out = 0; }
-				else             { *out = (*out << 1); }
-				*out |= (im(y1,x1) < (im(y2,x2)) ? 1 : 0);
+				res = (res << 1);
+				res |= ((im(y1,x1) < im(y2,x2)) ? 1 : 0);
 
-				i += 1;
 				// if all bits set, continue to next element
-				if (i % 64 == 0) { out++; }
+				if (++i % 64 == 0) {
+					*out = res;
+					out++;
+				}
+			}
+
+			// zero remaining bits (less edges than bits in output array)
+			for(i = BITS/64 - i/64; i > 0; i--) {
+				*out++ = res;
+				res = 0;
 			}
 		}
 
 		__host__ __device__  void operator()(ushort2 thread, ushort2 stride, ushort2 size) {
-			for (int y = thread.y+WINY/2; y<size.y-WINY/2-1; y+=stride.y) {
-				for (int x = thread.x+WINX/2; x<size.x-WINX/2-1; x+=stride.x) {
-					window(y, x, &(out(y, x*WSTEP)));
+			for (int y = thread.y+winy/2; y<size.y-winy/2-1; y+=stride.y) {
+				for (int x = thread.x+winx/2; x<size.x-winx/2-1; x+=stride.x) {
+					compute(y, x, &(out(y, x*WSTEP)));
 				}
 			}
 		}
 
 		int nedges;
-		Array2D<uchar>::Data edges;
+		int winx;
+		int winy;
+		Array2D<char>::Data edges;
 		Array2D<uchar>::Data im;
 		Array2D<uint64_t>::Data out;
 
 		// number of uint64_t values for each window
-		static constexpr int WSTEP = (WINX*WINY - 1)/(sizeof(uint64_t)*8) + 1;
+		static constexpr int WSTEP = (BITS - 1)/(sizeof(uint64_t)*8) + 1;
 	};
 }
 
 void GeneralizedCensusMatchingCost::set(const Array2D<uchar> &l, const Array2D<uchar> &r) {
+	if (edges_.height == 0) {
+		printf("edges must be set before processing input images\n");
+		throw std::exception();
+	}
 
-	parallel2D<algorithms::GeneralizedCensusTransform<WX,WY>>({edges_.height, edges_.data(), l.data(), ct_l_.data()}, l.width, l.height);
-	parallel2D<algorithms::GeneralizedCensusTransform<WX,WY>>({edges_.height, edges_.data(), r.data(), ct_r_.data()}, r.width, r.height);
+	int winx = std::max(std::abs(pmin.x), pmax.x)*2 + 1;
+	int winy = std::max(std::abs(pmin.y), pmax.y)*2 + 1;
+	parallel2D<algorithms::GeneralizedCensusTransform<128>>({
+			edges_.height, winx, winy,
+			edges_.data(), l.data(), ct_l_.data()
+		}, l.width, l.height);
+	parallel2D<algorithms::GeneralizedCensusTransform<128>>({
+			edges_.height, winx, winy,
+			edges_.data(), r.data(), ct_r_.data()
+		}, r.width, r.height);
 }
 
-void GeneralizedCensusMatchingCost::setEdges(const std::vector<std::pair<int, int>> &edges) {
-	if (edges.size() >= (WX * WY)) {
+void GeneralizedCensusMatchingCost::setEdges(const std::vector<std::pair<cv::Point2i, cv::Point2i>> &edges) {
+	if (edges.size() > NBITS) {
+		printf("Too many edges %i, maximum number %i\n", int(edges.size()), NBITS);
 		throw std::exception(); // too many edges
 	}
 
-	cv::Mat data(cv::Size(2, edges.size()), CV_8UC1);
+	cv::Mat data_(cv::Size(4, edges.size()), CV_8SC1);
 	for (size_t i = 0; i < edges.size(); i++) {
-		if (edges[i].first < 0 || edges[0].second < 0) {
-			throw std::exception(); // indices must be positive
-		}
-
-		data.at<uchar>(i,0) = edges[i].first;
-		data.at<uchar>(i,1) = edges[i].second;
+		const auto &p1 = edges[i].first;
+		const auto &p2 = edges[i].second;
+
+		data_.at<char>(i,0) = p1.x;
+		data_.at<char>(i,1) = p1.y;
+		data_.at<char>(i,2) = p2.x;
+		data_.at<char>(i,3) = p2.y;
+
+		pmax.x = std::max(pmax.x, std::max(p1.x, p2.x));
+		pmax.y = std::max(pmax.y, std::max(p1.y, p2.y));
+		pmin.x = std::min(pmax.x, std::min(p1.x, p2.x));
+		pmin.y = std::min(pmax.y, std::min(p1.y, p2.y));
 	}
 
-	edges_.create(2, edges.size());
+	edges_.create(4, edges.size());
 	#ifdef USE_GPU
-	edges_.toGpuMat().upload(data);
+	edges_.toGpuMat().upload(data_);
 	#else
-	data.copyTo(edges_.toMat());
+	data_.copyTo(edges_.toMat());
 	#endif
-}
-
-void GeneralizedCensusMatchingCost::setEdges(const std::vector<std::pair<std::pair<int, int>,std::pair<int, int>>> &edges) {
-	std::vector<std::pair<int, int>> edges_idx;
-	for (const auto& p : edges) {
-		const auto& p1 = p.first;
-		const auto& p2 = p.second;
-
-		int i1 = p1.first * WX + p1.second + (WX*WY-1)/2;
-		int i2 = p2.first * WX + p2.second + (WX*WY-1)/2;
-		printf("i1: %i, i2: %i\n", i1, i2);
-		edges_idx.push_back({i1, i2});
-	}
 
-	/* TODO: move to unit test
-	for (int i = 0; i < edges.size(); i++) {
-		auto p = edges_idx[i];
-
-		const int y1 = p.first % WY - WY/2;
-		const int x1 = p.first / WY - WX/2;
-
-		const int y2 = p.second % WY - WY/2;
-		const int x2 = p.second / WY - WX/2;
-		printf("(%i,%i), (%i,%i)\n", y1, x1, y2, x2);
-	}*/
-
-	setEdges(edges_idx);
+	// normalization factor: 1.0/(number of comparisons)
+	data().normalize = 1.0f/float(edges.size());
 }
 
+
 void GeneralizedCensusMatchingCost::set(cv::InputArray l, cv::InputArray r) {
 	if (l.type() != CV_8UC1 || r.type() != CV_8UC1) { throw std::exception(); }
 	if (l.rows() != r.rows() || l.cols() != r.cols() || l.rows() != height() || l.cols() != width()) {
@@ -126,46 +143,102 @@ void GeneralizedCensusMatchingCost::set(cv::InputArray l, cv::InputArray r) {
 		set(Array2D<uchar>(ml), Array2D<uchar>(mr));
 	}
 	else {
+		printf("Bad input array type\n");
 		throw std::exception();
 	}
 }
 
-////////////////////////////////////////////////////////////////////////////////
-/*
-struct StereoCensusSgm::Impl : public StereoSgm<CensusMatchingCost, StereoCensusSgm::Parameters> {
-	Array2D<uchar> l;
-	Array2D<uchar> r;
+// ==== Pattern generators =====================================================
+
+std::vector<std::pair<cv::Point2i, cv::Point2i>> pattern_dense(const cv::Size size) {
+	return pattern_sparse(size, 1);
+}
+
+std::vector<std::pair<cv::Point2i, cv::Point2i>> pattern_sparse(const cv::Size size, int step) {
+	std::vector<std::pair<cv::Point2i, cv::Point2i>> pattern;
 
-	Impl(StereoCensusSgm::Parameters &params, int width, int height, int dmin, int dmax) :
-		StereoSgm(params, width, height, dmin, dmax), l(width, height), r(width, height) {}
-};
+	for (int y = -size.height/2; y <= size.height/2; y += step) {
+		for (int x = -size.width/2; x <= size.width/2; x += step) {
+			if (cv::Point2i{x, y} == cv::Point2i{0, 0}) { continue; }
+			pattern.push_back({{0, 0}, {x, y}});
+		}
+	}
 
-StereoCensusSgm::StereoCensusSgm() : impl_(nullptr) {
-	impl_ = new Impl(params, 0, 0, 0, 0);
+	return pattern;
 }
 
-void StereoCensusSgm::compute(cv::InputArray l, cv::InputArray r, cv::OutputArray disparity) {
+std::vector<std::pair<cv::Point2i, cv::Point2i>> pattern_random(const cv::Size size, int nedges) {
+	std::vector<std::pair<cv::Point2i, cv::Point2i>> pattern;
 
-	//cudaSetDevice(0);
+	std::random_device rd;
+	std::mt19937 gen(rd());
+	std::uniform_int_distribution<> rand_x(-size.width/2, size.width/2);
+	std::uniform_int_distribution<> rand_y(-size.height/2, size.height/2);
+
+	for (int i = 0; i < nedges; i++) {
+		cv::Point2i p1;
+		cv::Point2i p2;
+		do {
+			p1 = {rand_x(gen), rand_y(gen)};
+			p2 = {rand_x(gen), rand_y(gen)};
+		}
+		while (p1 == p2); // try again if points happen to be same
 
-	if (l.rows() != impl_->cost.height() || r.cols() != impl_->cost.width()) {
-		delete impl_; impl_ = nullptr;
-		impl_ = new Impl(params, l.cols(), l.rows(), params.d_min, params.d_max);
+		pattern.push_back({p1, p2});
 	}
 
-	mat2gray(l, impl_->l);
-	mat2gray(r, impl_->r);
-	impl_->cost.setPattern(params.pattern);
-	impl_->cost.set(impl_->l, impl_->r);
+	return pattern;
+}
 
-	cudaSafeCall(cudaDeviceSynchronize());
-	impl_->compute(disparity);
+std::vector<std::pair<cv::Point2i, cv::Point2i>> pattern_random(const cv::Size size) {
+	return pattern_random(size, size.width*size.height);
 }
 
-StereoCensusSgm::~StereoCensusSgm() {
-	if (impl_) {
-		delete impl_;
-		impl_ = nullptr;
+std::vector<std::pair<cv::Point2i, cv::Point2i>> pattern_gct(int nedges) {
+	std::vector<std::pair<cv::Point2i, cv::Point2i>> pattern;
+	pattern.reserve(nedges);
+	switch(nedges) {
+		case 16:
+			pattern.push_back({{-2, -1}, {2, 1}});
+			pattern.push_back({{-2, 1}, {2, -1}});
+			pattern.push_back({{-1, -2}, {1, 2}});
+			pattern.push_back({{1, -2}, {-1, -2}});
+			[[fallthrough]]
+
+		case 12:
+			pattern.push_back({{-1, -1}, {1, 0}});
+			pattern.push_back({{1, -1}, {-1, 0}});
+			pattern.push_back({{-1, 1}, {1, 0}});
+			pattern.push_back({{1, 1}, {-1, 0}});
+			[[fallthrough]]
+
+		case 8:
+			pattern.push_back({{-2, -2}, {2, 2}});
+			pattern.push_back({{-2, 2}, {2, -2}});
+			pattern.push_back({{0, -2}, {0, 2}});
+			pattern.push_back({{-2, 0}, {2, 0}});
+			[[fallthrough]]
+
+		case 4:
+			pattern.push_back({{-1, -1}, {1, 1}});
+			pattern.push_back({{-1, 1}, {1, -1}});
+			[[fallthrough]]
+
+		case 2:
+			pattern.push_back({{0, -1}, {0, 1}});
+			[[fallthrough]]
+
+		case 1:
+			pattern.push_back({{-1, 0}, {1, 0}});
+			break;
+
+		default:
+			printf("Bad number of edges %i, valid values are 1, 2, 4, 8 and 16", nedges);
+			throw std::exception();
+	}
+	if (nedges != pattern.size()) {
+		printf("error (assert): pattern size incorrect");
+		throw std::exception();
 	}
+	return pattern;
 }
-*/
diff --git a/lib/libstereo/src/costs/gct.hpp b/lib/libstereo/src/costs/gct.hpp
index 90f7d855556e4cb386fcf9fd0eb3b23af89c4f64..4214df43f9a9a1d534dfe8e626f25cfaae5760c2 100644
--- a/lib/libstereo/src/costs/gct.hpp
+++ b/lib/libstereo/src/costs/gct.hpp
@@ -1,18 +1,23 @@
 #pragma once
+#include <opencv2/core.hpp>
 
 #include "../dsbase.hpp"
 #include "../array2d.hpp"
 #include "census.hpp"
 
+/**
+ * Generalized Census Transform
+ */
+
 namespace impl {
-	template<uint8_t WMAX>
-	using GeneralizedCensusMatchingCost = HammingCost<WMAX*WMAX>;
+	template<uint8_t BITS>
+	using GeneralizedCensusMatchingCost = NormalizedHammingCost<BITS>;
 }
 
-class GeneralizedCensusMatchingCost : public DSBase<impl::GeneralizedCensusMatchingCost<11>> {
+class GeneralizedCensusMatchingCost : public DSBase<impl::GeneralizedCensusMatchingCost<128>> {
 public:
-	typedef impl::GeneralizedCensusMatchingCost<11> DataType;
-	typedef unsigned short Type;
+	typedef impl::GeneralizedCensusMatchingCost<128> DataType;
+	typedef float Type;
 
 	GeneralizedCensusMatchingCost() : DSBase<DataType>(0, 0, 0, 0) {};
 	GeneralizedCensusMatchingCost(int width, int height, int disp_min, int disp_max)
@@ -23,10 +28,12 @@ public:
 			data().r = ct_r_.data();
 		}
 
-	// pairs of indices (window size 11x11, origin top left)
-	void setEdges(const std::vector<std::pair<int, int>> &edges);
-	// pairs of (y,x) coordinates (relative to window, origin at center)
-	void setEdges(const std::vector<std::pair<std::pair<int, int>,std::pair<int, int>>> &edges);
+	/** Pairs of (x, y) coordinates (relative to window, origin at center)
+	 *  indices must fit in signed char [-128,127].
+	 *  TODO: Indices must fit within 11x11 window (operator() in
+	 * GeneralizedCensusTransform)
+	 */
+	void setEdges(const std::vector<std::pair<cv::Point2i,cv::Point2i>> &edges);
 
 	void set(cv::InputArray l, cv::InputArray r);
 	void set(const Array2D<uchar>& l, const Array2D<uchar>& r);
@@ -35,5 +42,20 @@ public:
 protected:
 	Array2D<uint64_t> ct_l_;
 	Array2D<uint64_t> ct_r_;
-	Array2D<uchar> edges_;
+	Array2D<char> edges_;
+
+	cv::Point2i pmax;
+	cv::Point2i pmin;
 };
+
+// ==== Pattern generators =====================================================
+
+std::vector<std::pair<cv::Point2i, cv::Point2i>> pattern_dense(const cv::Size size);
+
+std::vector<std::pair<cv::Point2i, cv::Point2i>> pattern_sparse(const cv::Size size, int step=2);
+
+std::vector<std::pair<cv::Point2i, cv::Point2i>> pattern_random(const cv::Size size, int nedges);
+std::vector<std::pair<cv::Point2i, cv::Point2i>> pattern_random(const cv::Size size);
+
+/** patterns presented in the original paper */
+std::vector<std::pair<cv::Point2i, cv::Point2i>> pattern_gct(int nedges);
diff --git a/lib/libstereo/src/util_opencv.hpp b/lib/libstereo/src/util_opencv.hpp
index 94e9db6fe735ddcc48661ed42ceaac95f96be94a..fc30984956fff26a27386d7426866ead584f3314 100644
--- a/lib/libstereo/src/util_opencv.hpp
+++ b/lib/libstereo/src/util_opencv.hpp
@@ -10,8 +10,14 @@
 #include <opencv2/cudaimgproc.hpp>
 
 static void mat2gray(const cv::cuda::GpuMat &in, Array2D<unsigned char> &out) {
-	if (in.depth() != CV_8U) { throw std::exception(); }
-	if (out.width != in.cols || out.height != in.rows) { throw std::exception(); }
+	if (in.depth() != CV_8U) {
+		printf("input must be 8-bit\n");
+		throw std::exception();
+	}
+	if (out.width != in.cols || out.height != in.rows) {
+		printf("input and output have different sizes\n");
+		throw std::exception();
+	}
 
 	switch (in.channels()) {
 		case 4:
@@ -27,14 +33,21 @@ static void mat2gray(const cv::cuda::GpuMat &in, Array2D<unsigned char> &out) {
 			break;
 
 		default:
+			printf("bad number of channels\n");
 			throw std::exception();
 	}
 }
 #endif
 
 static void mat2gray(const cv::Mat &in, Array2D<unsigned char> &out) {
-	if (in.depth() != CV_8U) { throw std::exception(); }
-	if (out.width != in.cols || out.height != in.rows) { throw std::exception(); }
+	if (in.depth() != CV_8U) {
+		printf("input must be 8-bit\n");
+		throw std::exception();
+	}
+	if (out.width != in.cols || out.height != in.rows) {
+		printf("input and output have different sizes\n");
+		throw std::exception();
+	}
 
 #ifndef USE_GPU
 	switch (in.channels()) {
@@ -51,6 +64,7 @@ static void mat2gray(const cv::Mat &in, Array2D<unsigned char> &out) {
 			break;
 
 		default:
+			printf("bad number of channels\n");
 			throw std::exception();
 	}
 #else
@@ -69,6 +83,7 @@ static void mat2gray(const cv::Mat &in, Array2D<unsigned char> &out) {
 			break;
 
 		default:
+			printf("bad number of channels\n");
 			throw std::exception();
 	}
 
@@ -86,6 +101,7 @@ static void mat2gray(cv::InputArray in, Array2D<unsigned char> &out) {
 		mat2gray(in.getMat(), out);
 	}
 	else {
+		printf("bad input type\n");
 		throw std::exception();
 	}
 }