Some additions to aos/vision/blob (transpose and move_scale).

Change-Id: I927144e27d0494f3ea879ebbd1647741f82e3c7b
diff --git a/aos/vision/blob/BUILD b/aos/vision/blob/BUILD
index f823cc0..cf728c5 100644
--- a/aos/vision/blob/BUILD
+++ b/aos/vision/blob/BUILD
@@ -94,6 +94,43 @@
   ],
 )
 
+cc_library(
+  name = 'move_scale',
+  hdrs = ['move_scale.h'],
+  srcs = ['move_scale.cc'],
+  deps = [
+    ':range_image',
+  ],
+)
+
+cc_library(
+  name = 'test_utils',
+  hdrs = ['test_utils.h'],
+  srcs = ['test_utils.cc'],
+  deps = [
+    ':range_image',
+  ],
+)
+
+cc_library(
+  name = 'transpose',
+  hdrs = ['transpose.h'],
+  srcs = ['transpose.cc'],
+  deps = [
+    ':range_image',
+  ],
+)
+
+cc_test(
+  name = 'transpose_test',
+  srcs = ['transpose_test.cc'],
+  deps = [
+    ':transpose',
+    ':test_utils',
+    '//aos/testing:googletest',
+  ],
+)
+
 gtk_dependent_cc_library(
   name = 'stream_view',
   hdrs = ['stream_view.h'],
diff --git a/aos/vision/blob/codec.h b/aos/vision/blob/codec.h
index c2bc30d..b8a8089 100644
--- a/aos/vision/blob/codec.h
+++ b/aos/vision/blob/codec.h
@@ -12,11 +12,12 @@
 struct IntCodec {
   static constexpr size_t kSize = sizeof(T);
   static inline char *Write(char *data, T ival) {
-    *(reinterpret_cast<T *>(data)) = ival;
+    memcpy(data, &ival, sizeof(T));
     return data + kSize;
   }
   static inline T Read(const char **data) {
-    auto datum = *(reinterpret_cast<const T *>(*data));
+    T datum;
+    memcpy(&datum, *data, sizeof(T));
     *data += kSize;
     return datum;
   }
diff --git a/aos/vision/blob/find_blob.cc b/aos/vision/blob/find_blob.cc
index 0e2586b..3153fcb 100644
--- a/aos/vision/blob/find_blob.cc
+++ b/aos/vision/blob/find_blob.cc
@@ -52,7 +52,7 @@
 };
 
 // Uses disjoint set class to track range images.
-// Joins in the disjoint set are done at the same time as joins in the 
+// Joins in the disjoint set are done at the same time as joins in the
 // range image.
 class BlobDisjointSet {
  public:
diff --git a/aos/vision/blob/hierarchical_contour_merge.cc b/aos/vision/blob/hierarchical_contour_merge.cc
index b3d9f41..ec617ca 100644
--- a/aos/vision/blob/hierarchical_contour_merge.cc
+++ b/aos/vision/blob/hierarchical_contour_merge.cc
@@ -22,7 +22,7 @@
 
   // This is an exclusive range lookup into a modulo ring.
   // The integral is precomputed in items_ and is inclusive even though
-  // the input is [a, b). 
+  // the input is [a, b).
   T Get(int a, int b) {
     a = Mod(a, items_.size());
     b = Mod(b, items_.size());
diff --git a/aos/vision/blob/move_scale.cc b/aos/vision/blob/move_scale.cc
new file mode 100644
index 0000000..1caa479
--- /dev/null
+++ b/aos/vision/blob/move_scale.cc
@@ -0,0 +1,42 @@
+#include "aos/vision/blob/move_scale.h"
+
+namespace aos {
+namespace vision {
+
+RangeImage MoveScale(const RangeImage &img, int dx, int dy, int scale) {
+  std::vector<std::vector<ImageRange>> out_range_list;
+  for (const auto &range_list : img) {
+    std::vector<ImageRange> ranges;
+    for (const auto &range : range_list) {
+      ranges.emplace_back((range.st + dx) * scale, (range.ed + dx) * scale);
+    }
+    for (int i = 1; i < scale; ++i) {
+      out_range_list.push_back(ranges);
+    }
+    out_range_list.emplace_back(std::move(ranges));
+  }
+  return RangeImage((img.mini() + dy) * scale, std::move(out_range_list));
+}
+
+std::vector<RangeImage> MoveScale(const std::vector<RangeImage> &imgs, int dx,
+                                  int dy, int scale) {
+  std::vector<RangeImage> out;
+  for (const auto &img : imgs) {
+    out.emplace_back(MoveScale(img, dx, dy, scale));
+  }
+  return out;
+}
+
+void GetBBox(const RangeImage &img, ImageBBox *bbox) {
+  bbox->miny = std::min(img.min_y(), bbox->miny);
+  bbox->maxy = std::max(img.min_y() + img.size(), bbox->maxy);
+  for (const auto &range_lst : img) {
+    if (range_lst.size() > 0) {
+      bbox->maxx = std::max(range_lst[range_lst.size() - 1].ed, bbox->maxx);
+      bbox->minx = std::min(range_lst[0].st, bbox->minx);
+    }
+  }
+}
+
+}  // namespace vision
+}  // namespace aos
diff --git a/aos/vision/blob/move_scale.h b/aos/vision/blob/move_scale.h
new file mode 100644
index 0000000..c113bca
--- /dev/null
+++ b/aos/vision/blob/move_scale.h
@@ -0,0 +1,35 @@
+#ifndef AOS_VISION_BLOB_MOVE_SCALE_H_
+#define AOS_VISION_BLOB_MOVE_SCALE_H_
+
+#include <vector>
+#include <limits>
+
+#include "aos/vision/blob/range_image.h"
+
+namespace aos {
+namespace vision {
+
+// Bounding box for a RangeImage.
+struct ImageBBox {
+  int minx = std::numeric_limits<int>::max();
+  int maxx = std::numeric_limits<int>::min();
+  int miny = std::numeric_limits<int>::max();
+  int maxy = std::numeric_limits<int>::min();
+};
+
+// Sums img into bbox. bbox is constructed empty and grows with each call
+// to GetBBox.
+void GetBBox(const RangeImage &img, ImageBBox *bbox);
+inline void GetBBox(const std::vector<RangeImage> &imgs, ImageBBox *bbox) {
+  for (const auto &img : imgs) GetBBox(img, bbox);
+}
+
+std::vector<RangeImage> MoveScale(const std::vector<RangeImage> &imgs, int dx,
+                                  int dy, int scale);
+
+RangeImage MoveScale(const RangeImage &img, int dx, int dy, int scale);
+
+}  // namespace vision
+}  // namespace aos
+
+#endif  // AOS_VISION_BLOB_MOVE_SCALE_H_
diff --git a/aos/vision/blob/range_image.h b/aos/vision/blob/range_image.h
index 109a100..3647890 100644
--- a/aos/vision/blob/range_image.h
+++ b/aos/vision/blob/range_image.h
@@ -53,6 +53,8 @@
 
   int mini() const { return min_y_; }
 
+  int height() const { return min_y_ + ranges_.size(); }
+
  private:
   // minimum index in y where the blob starts
   int min_y_ = 0;
diff --git a/aos/vision/blob/stream_view.h b/aos/vision/blob/stream_view.h
index bee1dc9..2ae56ac 100644
--- a/aos/vision/blob/stream_view.h
+++ b/aos/vision/blob/stream_view.h
@@ -61,6 +61,26 @@
     }
   }
 
+  inline void DrawSecondBlobList(const BlobList &blob_list, PixelRef color1,
+                                 PixelRef color2, PixelRef prev_color) {
+    ImagePtr ptr = img();
+    for (const auto &blob : blob_list) {
+      for (int i = 0; i < (int)blob.ranges().size(); ++i) {
+        for (const auto &range : blob.ranges()[i]) {
+          for (int j = range.st; j < range.ed; ++j) {
+            auto px = ptr.get_px(j, i + blob.min_y());
+            if (px.r == prev_color.r && px.g == prev_color.g &&
+                px.b == prev_color.b) {
+              ptr.get_px(j, i + blob.min_y()) = color2;
+            } else {
+              ptr.get_px(j, i + blob.min_y()) = color1;
+            }
+          }
+        }
+      }
+    }
+  }
+
   // Backwards compatible.
   DebugViewer *view() { return this; }
 
diff --git a/aos/vision/blob/test_utils.cc b/aos/vision/blob/test_utils.cc
new file mode 100644
index 0000000..7664f8a
--- /dev/null
+++ b/aos/vision/blob/test_utils.cc
@@ -0,0 +1,43 @@
+#include "aos/vision/blob/test_utils.h"
+
+namespace aos {
+namespace vision {
+
+RangeImage LoadFromTestData(int mini, const char *data) {
+  // Consume initial return.
+  if (*data) ++data;
+  std::vector<std::vector<ImageRange>> rows;
+  int x = 0;
+  bool p_score = false;
+  int pstart = -1;
+  std::vector<ImageRange> out_ranges;
+
+  for (; *data; ++data) {
+    char cell = *data;
+
+    if (cell == '\n') {
+      if (p_score) {
+        out_ranges.emplace_back(ImageRange(pstart, x));
+      }
+      rows.emplace_back(out_ranges);
+      out_ranges = {};
+      x = 0;
+      pstart = -1;
+      p_score = false;
+    } else {
+      if ((cell != ' ') != p_score) {
+        if (p_score) {
+          out_ranges.emplace_back(ImageRange(pstart, x));
+        } else {
+          pstart = x;
+        }
+        p_score = !p_score;
+      }
+      ++x;
+    }
+  }
+  return RangeImage(mini, std::move(rows));
+}
+
+}  // namespace vision
+}  // namespace aos
diff --git a/aos/vision/blob/test_utils.h b/aos/vision/blob/test_utils.h
new file mode 100644
index 0000000..e0c706c
--- /dev/null
+++ b/aos/vision/blob/test_utils.h
@@ -0,0 +1,24 @@
+#ifndef AOS_VISION_BLOB_TEST_UTILS_H_
+#define AOS_VISION_BLOB_TEST_UTILS_H_
+
+#include "aos/vision/blob/range_image.h"
+
+namespace aos {
+namespace vision {
+
+// For tests. Loads a RangeImage from a constant string.
+//
+// For example this should look something like this:
+//  first and final returns will be stripped.
+//
+// R"(
+//  ---  ---
+//   -----
+//     --
+// )"
+RangeImage LoadFromTestData(int mini, const char *data);
+
+}  // namespace vision
+}  // namespace aos
+
+#endif  // AOS_VISION_BLOB_TEST_UTILS_H_
diff --git a/aos/vision/blob/transpose.cc b/aos/vision/blob/transpose.cc
new file mode 100644
index 0000000..19ac965
--- /dev/null
+++ b/aos/vision/blob/transpose.cc
@@ -0,0 +1,87 @@
+#include "aos/vision/blob/transpose.h"
+
+#include <algorithm>
+
+namespace aos {
+namespace vision {
+
+RangeImage Transpose(const RangeImage &img) {
+  enum EventT {
+    // Must happen before point adds and deletes.
+    kRangeStart = 0,
+    kRangeEnd = 1,
+    // Non-overlapping
+    kPointAdd = 3,
+    kPointDel = 2,
+  };
+  std::vector<std::vector<std::pair<int, EventT>>> events;
+  int y = img.min_y();
+  for (const std::vector<ImageRange> &row : img) {
+    for (const ImageRange &range : row) {
+      if (range.ed >= static_cast<int>(events.size()))
+        events.resize(range.ed + 1);
+      events[range.st].emplace_back(y, kPointAdd);
+      events[range.ed].emplace_back(y, kPointDel);
+    }
+    ++y;
+  }
+
+  int min_y = 0;
+  while (min_y < (int)events.size() && events[min_y].empty()) ++min_y;
+
+  std::vector<ImageRange> prev_ranges;
+  std::vector<ImageRange> cur_ranges;
+
+  std::vector<std::vector<ImageRange>> rows;
+  for (int y = min_y; y < static_cast<int>(events.size()) - 1; ++y) {
+    auto row_events = std::move(events[y]);
+    for (const auto &range : prev_ranges) {
+      row_events.emplace_back(range.st, kRangeStart);
+      row_events.emplace_back(range.ed, kRangeEnd);
+    }
+    std::sort(row_events.begin(), row_events.end());
+    cur_ranges.clear();
+
+    bool has_cur_range = false;
+    ImageRange cur_range{0, 0};
+    auto add_range = [&](ImageRange range) {
+      if (range.st == range.ed) return;
+      if (has_cur_range) {
+        if (cur_range.ed == range.st) {
+          range = ImageRange{cur_range.st, range.ed};
+        } else {
+          cur_ranges.emplace_back(cur_range);
+        }
+      }
+      cur_range = range;
+      has_cur_range = true;
+    };
+
+    int prev_start;
+    for (const auto &pt : row_events) {
+      switch (pt.second) {
+        case kRangeStart:
+          prev_start = pt.first;
+          break;
+        case kPointAdd:
+          add_range(ImageRange{pt.first, pt.first + 1});
+          break;
+        case kRangeEnd:
+          add_range(ImageRange{prev_start, pt.first});
+          break;
+        case kPointDel:
+          add_range(ImageRange{prev_start, pt.first});
+          prev_start = pt.first + 1;
+          break;
+      }
+    }
+
+    if (has_cur_range) cur_ranges.emplace_back(cur_range);
+    rows.emplace_back(cur_ranges);
+    std::swap(cur_ranges, prev_ranges);
+  }
+  return RangeImage(min_y, std::move(rows));
+}
+
+}  // namespace vision
+}  // namespace aos
diff --git a/aos/vision/blob/transpose.h b/aos/vision/blob/transpose.h
new file mode 100644
index 0000000..632f270
--- /dev/null
+++ b/aos/vision/blob/transpose.h
@@ -0,0 +1,20 @@
+#ifndef _y2017_VISION_BLOB_TRANSPOSE_H_
+#define _y2017_VISION_BLOB_TRANSPOSE_H_
+
+#include "aos/vision/blob/range_image.h"
+
+namespace aos {
+namespace vision {
+
+RangeImage Transpose(const RangeImage &img);
+inline std::vector<RangeImage> Transpose(const std::vector<RangeImage> &imgs) {
+  std::vector<RangeImage> out;
+  out.reserve(imgs.size());
+  for (const auto &img : imgs) out.push_back(Transpose(img));
+  return out;
+}
+
+}  // namespace vision
+}  // namespace aos
+
+#endif  // _y2017_VISION_ROT90_H_
diff --git a/aos/vision/blob/transpose_test.cc b/aos/vision/blob/transpose_test.cc
new file mode 100644
index 0000000..a5c2964
--- /dev/null
+++ b/aos/vision/blob/transpose_test.cc
@@ -0,0 +1,29 @@
+#include "aos/vision/blob/transpose.h"
+#include "aos/vision/blob/test_utils.h"
+
+#include <algorithm>
+#include <string>
+#include "gtest/gtest.h"
+
+namespace aos {
+namespace vision {
+
+TEST(TransposeTest, Tranpspose) {
+  RangeImage img = LoadFromTestData(20, R"(
+    -----------
+    -----  ----
+   ------------
+   -------------
+   ------------
+    ----------
+   ------------
+     ---------
+)");
+
+  auto b = Transpose(img);
+  auto c = Transpose(b);
+  EXPECT_EQ(ShortDebugPrint({img}), ShortDebugPrint({c}));
+}
+
+}  // namespace vision
+}  // namespace aos