Add a basic test of the thresholding code

This in preparation for adding a new implementation.

Change-Id: I8ef7a25607b48a1649cb2c30e121a72a600eb1e6
diff --git a/aos/vision/blob/BUILD b/aos/vision/blob/BUILD
index d22aa4d..2e77079 100644
--- a/aos/vision/blob/BUILD
+++ b/aos/vision/blob/BUILD
@@ -49,6 +49,19 @@
     ],
 )
 
+cc_test(
+    name = "threshold_test",
+    srcs = [
+        "threshold_test.cc",
+    ],
+    deps = [
+        ":range_image",
+        ":threshold",
+        "//aos/testing:googletest",
+        "//aos/vision/image:image_types",
+    ],
+)
+
 cc_library(
     name = "hierarchical_contour_merge",
     srcs = ["hierarchical_contour_merge.cc"],
diff --git a/aos/vision/blob/range_image.cc b/aos/vision/blob/range_image.cc
index c01a919..613d2ca 100644
--- a/aos/vision/blob/range_image.cc
+++ b/aos/vision/blob/range_image.cc
@@ -88,6 +88,10 @@
   }
 }
 
+void PrintTo(const ImageRange &range, std::ostream *os) {
+  *os << "{" << range.st << ", " << range.ed << "}";
+}
+
 void RangeImage::Flip(int image_width, int image_height) {
   std::reverse(ranges_.begin(), ranges_.end());
   for (std::vector<ImageRange> &range : ranges_) {
@@ -102,6 +106,31 @@
   min_y_ = image_height - static_cast<int>(ranges_.size()) - min_y_;
 }
 
+void PrintTo(const RangeImage &range, std::ostream *os) {
+  *os << "{min_y=" << range.min_y()
+      << ", ranges={";
+  bool first_row = true;
+  for (const auto &row : range) {
+    if (first_row) {
+      first_row = false;
+    } else {
+      *os << ", ";
+    }
+    *os << "{";
+    bool first_value = true;
+    for (const auto &value : row) {
+      if (first_value) {
+        first_value = false;
+      } else {
+        *os << ", ";
+      }
+      *os << "{" << value.st << ", " << value.ed << "}";
+    }
+    *os << "}";
+  }
+  *os << "}}";
+}
+
 int RangeImage::npixels() {
   if (npixelsc_ > 0) {
     return npixelsc_;
diff --git a/aos/vision/blob/range_image.h b/aos/vision/blob/range_image.h
index 3647890..201ffb2 100644
--- a/aos/vision/blob/range_image.h
+++ b/aos/vision/blob/range_image.h
@@ -21,8 +21,14 @@
   int calc_width() const { return ed - st; }
 
   bool operator<(const ImageRange &o) const { return st < o.st; }
+  bool operator==(const ImageRange &other) const {
+    return st == other.st && ed == other.ed;
+  }
+  bool operator!=(const ImageRange &other) const { return !(*this == other); }
 };
 
+void PrintTo(const ImageRange &range, std::ostream *os);
+
 // Image in pre-thresholded run-length encoded format.
 class RangeImage {
  public:
@@ -31,6 +37,13 @@
   explicit RangeImage(int l) { ranges_.reserve(l); }
   RangeImage() {}
 
+  bool operator==(const RangeImage &other) const {
+    if (min_y_ != other.min_y_) { return false; }
+    if (ranges_ != other.ranges_) { return false; }
+    return true;
+  }
+  bool operator!=(const RangeImage &other) const { return !(*this == other); }
+
   int size() const { return ranges_.size(); }
 
   int npixels();
@@ -59,13 +72,16 @@
   // minimum index in y where the blob starts
   int min_y_ = 0;
 
-  // ranges are always sorted in y then x order
+  // Each vector<ImageRange> represents all the matched ranges in a given row.
+  // Each ImageRange within that row represents a run of pixels which matches.
   std::vector<std::vector<ImageRange>> ranges_;
 
   // Cached pixel count.
   int npixelsc_ = -1;
 };
 
+void PrintTo(const RangeImage &range, std::ostream *os);
+
 typedef std::vector<RangeImage> BlobList;
 typedef std::vector<const RangeImage *> BlobLRef;
 
diff --git a/aos/vision/blob/threshold_test.cc b/aos/vision/blob/threshold_test.cc
new file mode 100644
index 0000000..96a2a22
--- /dev/null
+++ b/aos/vision/blob/threshold_test.cc
@@ -0,0 +1,100 @@
+#include "aos/vision/blob/threshold.h"
+
+#include <vector>
+
+#include "aos/vision/blob/range_image.h"
+#include "aos/vision/image/image_types.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace aos {
+namespace vision {
+namespace testing {
+
+class YuyvYThresholdTest : public ::testing::Test {
+};
+
+// Verifies that a simple image is thresholded correctly.
+//
+// Specifically, we want to get this result from the thresholding:
+//  --+--
+//  +---+
+//  -+++-
+//  +++-+
+//  -----
+//  ++-++
+//  +++++
+//  +-+-+
+TEST_F(YuyvYThresholdTest, SimpleImage) {
+  ImageFormat format;
+  format.w = 5;
+  format.h = 8;
+
+  std::vector<std::vector<ImageRange>> expected_ranges;
+  std::vector<char> image;
+  image.resize(5 * 8 * 2);
+  //  --+--
+  image[0 * 2 + 0 * 10] = 0;
+  image[1 * 2 + 0 * 10] = 0;
+  image[2 * 2 + 0 * 10] = 128;
+  image[3 * 2 + 0 * 10] = 127;
+  image[4 * 2 + 0 * 10] = 0;
+  expected_ranges.push_back({{{2, 3}}});
+  //  +---+
+  image[0 * 2 + 1 * 10] = 128;
+  image[1 * 2 + 1 * 10] = 0;
+  image[2 * 2 + 1 * 10] = 0;
+  image[3 * 2 + 1 * 10] = 10;
+  image[4 * 2 + 1 * 10] = 255;
+  expected_ranges.push_back({{{0, 1}, {4, 5}}});
+  //  -+++-
+  image[0 * 2 + 2 * 10] = 73;
+  image[1 * 2 + 2 * 10] = 250;
+  image[2 * 2 + 2 * 10] = 251;
+  image[3 * 2 + 2 * 10] = 252;
+  image[4 * 2 + 2 * 10] = 45;
+  expected_ranges.push_back({{{1, 4}}});
+  //  +++-+
+  image[0 * 2 + 3 * 10] = 128;
+  image[1 * 2 + 3 * 10] = 134;
+  image[2 * 2 + 3 * 10] = 250;
+  image[3 * 2 + 3 * 10] = 0;
+  image[4 * 2 + 3 * 10] = 230;
+  expected_ranges.push_back({{{0, 3}, {4, 5}}});
+  //  -----
+  image[0 * 2 + 4 * 10] = 7;
+  image[1 * 2 + 4 * 10] = 120;
+  image[2 * 2 + 4 * 10] = 127;
+  image[3 * 2 + 4 * 10] = 0;
+  image[4 * 2 + 4 * 10] = 50;
+  expected_ranges.push_back({{}});
+  //  ++-++
+  image[0 * 2 + 5 * 10] = 140;
+  image[1 * 2 + 5 * 10] = 140;
+  image[2 * 2 + 5 * 10] = 0;
+  image[3 * 2 + 5 * 10] = 140;
+  image[4 * 2 + 5 * 10] = 140;
+  expected_ranges.push_back({{{0, 2}, {3, 5}}});
+  //  +++++
+  image[0 * 2 + 6 * 10] = 128;
+  image[1 * 2 + 6 * 10] = 128;
+  image[2 * 2 + 6 * 10] = 128;
+  image[3 * 2 + 6 * 10] = 128;
+  image[4 * 2 + 6 * 10] = 128;
+  expected_ranges.push_back({{{0, 5}}});
+  //  +-+-+
+  image[0 * 2 + 7 * 10] = 200;
+  image[1 * 2 + 7 * 10] = 0;
+  image[2 * 2 + 7 * 10] = 200;
+  image[3 * 2 + 7 * 10] = 0;
+  image[4 * 2 + 7 * 10] = 200;
+  expected_ranges.push_back({{{0, 1}, {2, 3}, {4, 5}}});
+  const RangeImage expected_result(0, std::move(expected_ranges));
+
+  const auto slow_result = SlowYuyvYThreshold(format, image.data(), 127);
+  ASSERT_EQ(expected_result, slow_result);
+}
+
+}  // namespace testing
+}  // namespace vision
+}  // namespace aos