diff --git a/cv-node/include/ftl/middlebury.hpp b/cv-node/include/ftl/middlebury.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/cv-node/src/middlebury.cpp b/cv-node/src/middlebury.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..596d7a9a3258e95fee7f57959039a9ba9150d351
--- /dev/null
+++ b/cv-node/src/middlebury.cpp
@@ -0,0 +1,255 @@
+#include <ftl/middlebury.hpp>
+
+using cv::Mat;
+using cv::Size;
+
+static void skip_comment(FILE *fp) {
+    // skip comment lines in the headers of pnm files
+
+    char c;
+    while ((c=getc(fp)) == '#')
+        while (getc(fp) != '\n') ;
+    ungetc(c, fp);
+}
+
+static void skip_space(FILE *fp) {
+    // skip white space in the headers or pnm files
+
+    char c;
+    do {
+        c = getc(fp);
+    } while (c == '\n' || c == ' ' || c == '\t' || c == '\r');
+    ungetc(c, fp);
+}
+
+static void read_header(FILE *fp, const char *imtype, char c1, char c2, 
+                 int *width, int *height, int *nbands, int thirdArg)
+{
+    // read the header of a pnmfile and initialize width and height
+
+    char c;
+  
+	if (getc(fp) != c1 || getc(fp) != c2)
+		throw CError("ReadFilePGM: wrong magic code for %s file", imtype);
+	skip_space(fp);
+	skip_comment(fp);
+	skip_space(fp);
+	fscanf(fp, "%d", width);
+	skip_space(fp);
+	fscanf(fp, "%d", height);
+	if (thirdArg) {
+		skip_space(fp);
+		fscanf(fp, "%d", nbands);
+	}
+    // skip SINGLE newline character after reading image height (or third arg)
+	c = getc(fp);
+    if (c == '\r')      // <cr> in some files before newline
+        c = getc(fp);
+    if (c != '\n') {
+        if (c == ' ' || c == '\t' || c == '\r')
+            throw CError("newline expected in file after image height");
+        else
+            throw CError("whitespace expected in file after image height");
+  }
+}
+
+// check whether machine is little endian
+static int littleendian() {
+    int intval = 1;
+    uchar *uval = (uchar *)&intval;
+    return uval[0] == 1;
+}
+
+// 1-band PFM image, see http://netpbm.sourceforge.net/doc/pfm.html
+// 3-band not yet supported
+void ftl::middlebury::readFilePFM(Mat &img, const char* filename)
+{
+    // Open the file and read the header
+    FILE *fp = fopen(filename, "rb");
+    if (fp == 0)
+        throw CError("ReadFilePFM: could not open %s", filename);
+
+    int width, height, nBands;
+    read_header(fp, "PFM", 'P', 'f', &width, &height, &nBands, 0);
+
+    skip_space(fp);
+
+    float scalef;
+    fscanf(fp, "%f", &scalef);  // scale factor (if negative, little endian)
+
+    // skip SINGLE newline character after reading third arg
+    char c = getc(fp);
+    if (c == '\r')      // <cr> in some files before newline
+        c = getc(fp);
+    if (c != '\n') {
+        if (c == ' ' || c == '\t' || c == '\r')
+            throw CError("newline expected in file after scale factor");
+        else
+            throw CError("whitespace expected in file after scale factor");
+    }
+
+    // Set the image shape
+    Size sh(width, height, 1);
+    
+    // Allocate the image if necessary
+    img.ReAllocate(sh);
+
+    int littleEndianFile = (scalef < 0);
+    int littleEndianMachine = littleendian();
+    int needSwap = (littleEndianFile != littleEndianMachine);
+    //printf("endian file = %d, endian machine = %d, need swap = %d\n", 
+    //       littleEndianFile, littleEndianMachine, needSwap);
+
+    for (int y = height-1; y >= 0; y--) { // PFM stores rows top-to-bottom!!!!
+	int n = width;
+	float* ptr = (float *) img.PixelAddress(0, y, 0);
+	if ((int)fread(ptr, sizeof(float), n, fp) != n)
+	    throw CError("ReadFilePFM(%s): file is too short", filename);
+	
+	if (needSwap) { // if endianness doesn't agree, swap bytes
+	    uchar* ptr = (uchar *) img.PixelAddress(0, y, 0);
+	    int x = 0;
+	    uchar tmp = 0;
+	    while (x < n) {
+		tmp = ptr[0]; ptr[0] = ptr[3]; ptr[3] = tmp;
+		tmp = ptr[1]; ptr[1] = ptr[2]; ptr[2] = tmp;
+		ptr += 4;
+		x++;
+	    }
+	}
+    }
+    if (fclose(fp))
+        throw CError("ReadFilePGM(%s): error closing file", filename);
+}
+
+// 1-band PFM image, see http://netpbm.sourceforge.net/doc/pfm.html
+// 3-band not yet supported
+void ftl::middlebury::writeFilePFM(const Mat &img, const char* filename, float scalefactor=1/255.0)
+{
+    // Write a PFM file
+    CShape sh = img.Shape();
+    int nBands = sh.nBands;
+    if (nBands != 1)
+	throw CError("WriteFilePFM(%s): can only write 1-band image as pfm for now", filename);
+	
+    // Open the file
+    FILE *stream = fopen(filename, "wb");
+    if (stream == 0)
+        throw CError("WriteFilePFM: could not open %s", filename);
+
+    // sign of scalefact indicates endianness, see pfms specs
+    if (littleendian())
+	scalefactor = -scalefactor;
+
+    // write the header: 3 lines: Pf, dimensions, scale factor (negative val == little endian)
+    fprintf(stream, "Pf\n%d %d\n%f\n", sh.width, sh.height, scalefactor);
+
+    int n = sh.width;
+    // write rows -- pfm stores rows in inverse order!
+    for (int y = sh.height-1; y >= 0; y--) {
+	float* ptr = (float *)img.PixelAddress(0, y, 0);
+	if ((int)fwrite(ptr, sizeof(float), n, stream) != n)
+	    throw CError("WriteFilePFM(%s): file is too short", filename);
+    }
+    
+    // close file
+    if (fclose(stream))
+        throw CError("WriteFilePFM(%s): error closing file", filename);
+}
+
+void ftl::middlebury::evaldisp(const Mat &disp, const Mat &gtdisp, const Mat &mask, float badthresh, int maxdisp, int rounddisp)
+{
+    Size sh = gtdisp.size();
+    Size sh2 = disp.size();
+    Size msh = mask.size();
+    int width = sh.width, height = sh.height;
+    int width2 = sh2.width, height2 = sh2.height;
+    int scale = width / width2;
+
+    if ((!(scale == 1 || scale == 2 || scale == 4))
+	|| (scale * width2 != width)
+	|| (scale * height2 != height)) {
+	printf("   disp size = %4d x %4d\n", width2, height2);
+	printf("GT disp size = %4d x %4d\n", width,  height);
+	LOG(ERROR) << "GT disp size must be exactly 1, 2, or 4 * disp size";
+    }
+
+    int usemask = (msh.width > 0 && msh.height > 0);
+    if (usemask && (msh != sh))
+	LOG(ERROR) << "mask image must have same size as GT";
+
+    int n = 0;
+    int bad = 0;
+    int invalid = 0;
+    float serr = 0;
+    for (int y = 0; y < height; y++) {
+	for (int x = 0; x < width; x++) {
+	    float gt = gtdisp.at(x, y, 0);
+	    if (gt == INFINITY) // unknown
+		continue;
+	    float d = scale * disp.at(x / scale, y / scale, 0);
+	    int valid = (d != INFINITY);
+	    if (valid) {
+		float maxd = scale * maxdisp; // max disp range
+		d = __max(0, __min(maxd, d)); // clip disps to max disp range
+	    }
+	    if (valid && rounddisp)
+		d = round(d);
+	    float err = fabs(d - gt);
+	    if (usemask && mask.at(x, y, 0) != 255) { // don't evaluate pixel
+	    } else {
+		n++;
+		if (valid) {
+		    serr += err;
+		    if (err > badthresh) {
+			bad++;
+		    }
+		} else {// invalid (i.e. hole in sparse disp map)
+		    invalid++;
+		}
+	    }
+	}
+    }
+    float badpercent =  100.0*bad/n;
+    float invalidpercent =  100.0*invalid/n;
+    float totalbadpercent =  100.0*(bad+invalid)/n;
+    float avgErr = serr / (n - invalid); // CHANGED 10/14/2014 -- was: serr / n
+    //printf("mask  bad%.1f  invalid  totbad   avgErr\n", badthresh);
+    printf("%4.1f  %6.2f  %6.2f   %6.2f  %6.2f\n",   100.0*n/(width * height), 
+	   badpercent, invalidpercent, totalbadpercent, avgErr);
+}
+
+void ftl::middlebury::test(nlohmann::json &config) {
+	// Load dataset images
+	Mat l = imread((string)config["middlebury"]["dataset"] + "/im0.png");
+	Mat r = imread((string)config["middlebury"]["dataset"] + "/im1.png");
+	
+	// Load ground truth
+	Mat gt;
+	readFilePFM(gt, (string)config["middlebury"]["dataset"] + "disp0.pfm");
+	
+	// Run algorithm
+	auto disparity = Disparity::create(config["disparity"]);
+	cvtColor(l,  l, COLOR_BGR2GRAY);
+    cvtColor(r, r, COLOR_BGR2GRAY);
+        
+    Mat disp;
+    disparity->compute(l,r,disp);
+	disp.convertTo(disp, CV_32F);
+	
+	// Display results
+	evaldisp(disp, gt, Mat(), (float)config["middlebury"]["threshold"], (int)config["disparity"]["maximum"], 0);
+	
+	imshow("Ground Truth", gt);
+	imshow("Disparity", disp);
+	
+	/*cv::putText(yourImageMat, 
+            "Here is some text",
+            cv::Point(5,5), // Coordinates
+            cv::FONT_HERSHEY_COMPLEX_SMALL, // Font
+            1.0, // Scale. 2.0 = 2x bigger
+            cv::Scalar(255,255,255), // BGR Color
+            1, // Line Thickness (Optional)
+            cv::CV_AA); // Anti-alias (Optional)*/
+}
+