Sundry tweaks to aos/vision libs

Change-Id: Ia5578dcf2d42ac53b81af239bf329eb084fcf1d9
diff --git a/aos/common/util/global_factory.h b/aos/common/util/global_factory.h
index 148fc1e..aac49bb 100644
--- a/aos/common/util/global_factory.h
+++ b/aos/common/util/global_factory.h
@@ -44,7 +44,7 @@
 class GlobalFactory {
  public:
   using FactoryFunction =
-      std::function<std::unique_ptr<BaseClass>(FactoryArgs&&...)>;
+      std::function<std::unique_ptr<BaseClass>(FactoryArgs &&...)>;
 
   // Gets the factory function by named. This will return a null factory
   // std::function if the factory is not available, so one would be wise
@@ -67,13 +67,18 @@
   class SubClassRegisterer {
    public:
     explicit SubClassRegisterer(const char *name) {
-      (*GetMap())[name] = [](FactoryArgs&&... args) {
+      (*GetMap())[name] = [](FactoryArgs &&... args) {
         return std::unique_ptr<BaseClass>(
             new SubClass(std::forward<FactoryArgs>(args)...));
       };
     }
   };
 
+  // Fetch all factory functions.
+  static const std::unordered_map<std::string, FactoryFunction> &GetAll() {
+    return *GetMap();
+  }
+
  private:
   // Actual map. (Protected by static from concurrent construction
   // if there is nothing registered at static linkage time).
diff --git a/aos/vision/blob/move_scale.h b/aos/vision/blob/move_scale.h
index c113bca..e45e14d 100644
--- a/aos/vision/blob/move_scale.h
+++ b/aos/vision/blob/move_scale.h
@@ -1,8 +1,8 @@
 #ifndef AOS_VISION_BLOB_MOVE_SCALE_H_
 #define AOS_VISION_BLOB_MOVE_SCALE_H_
 
-#include <vector>
 #include <limits>
+#include <vector>
 
 #include "aos/vision/blob/range_image.h"
 
diff --git a/aos/vision/blob/range_image.cc b/aos/vision/blob/range_image.cc
index 5f2f29f..946859d 100644
--- a/aos/vision/blob/range_image.cc
+++ b/aos/vision/blob/range_image.cc
@@ -75,7 +75,7 @@
       if (span.ed > maxx) maxx = span.ed;
     }
   }
-  LOG(INFO, "maxx: %d minx: %d\n", maxx, minx);
+  printf("maxx: %d minx: %d\n", maxx, minx);
   char buf[maxx - minx];
   for (const auto &range : rimg.ranges()) {
     int i = minx;
@@ -84,7 +84,7 @@
       for (; i < span.ed; ++i) buf[i - minx] = '#';
     }
     for (; i < maxx; ++i) buf[i - minx] = ' ';
-    LOG(INFO, "%.*s\n", maxx - minx, buf);
+    printf("%.*s\n", maxx - minx, buf);
   }
 }
 
diff --git a/aos/vision/blob/stream_view.h b/aos/vision/blob/stream_view.h
index 2ae56ac..a6a661a 100644
--- a/aos/vision/blob/stream_view.h
+++ b/aos/vision/blob/stream_view.h
@@ -29,56 +29,51 @@
     memset(image_.data(), 0, fmt.ImgSize() * sizeof(PixelRef));
   }
 
-  inline void DrawBlobList(const BlobList &blob_list, PixelRef color) {
+  template <typename PixelCallback>
+  inline void ForPxInBlobList(const BlobList &blob_list,
+                              PixelCallback pixel_callback) {
     ImagePtr ptr = img();
-    for (const RangeImage &blob : blob_list) {
+    auto fmt = ptr.fmt();
+    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) {
-            ptr.get_px(j, i + blob.min_y()) = color;
+        int y = blob.min_y() + i;
+        if (y >= 0 && y < fmt.h) {
+          for (const auto &range : blob.ranges()[i]) {
+            for (int j = std::max(0, range.st); j < std::min(fmt.w, range.ed);
+                 ++j) {
+              pixel_callback(ptr.get_px(j, y));
+            }
           }
         }
       }
     }
   }
 
+  inline void DrawBlobList(const BlobList &blob_list, PixelRef color) {
+    ForPxInBlobList(blob_list, [&](PixelRef &px) { px = color; });
+  }
+
   inline void DrawSecondBlobList(const BlobList &blob_list, PixelRef color1,
                                  PixelRef color2) {
-    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 == 0 && px.g == 0 && px.b == 0) {
-              ptr.get_px(j, i + blob.min_y()) = color1;
-            } else {
-              ptr.get_px(j, i + blob.min_y()) = color2;
-            }
-          }
-        }
+    ForPxInBlobList(blob_list, [&](PixelRef &px) {
+      if (px.r == 0 && px.g == 0 && px.b == 0) {
+        px = color1;
+      } else {
+        px = color2;
       }
-    }
+    });
   }
 
   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;
-            }
-          }
-        }
+    ForPxInBlobList(blob_list, [&](PixelRef &px) {
+      if (px.r == prev_color.r && px.g == prev_color.g &&
+          px.b == prev_color.b) {
+        px = color2;
+      } else {
+        px = color1;
       }
-    }
+    });
   }
 
   // Backwards compatible.
diff --git a/aos/vision/blob/transpose.cc b/aos/vision/blob/transpose.cc
index 19ac965..72e1cc1 100644
--- a/aos/vision/blob/transpose.cc
+++ b/aos/vision/blob/transpose.cc
@@ -1,6 +1,7 @@
 #include "aos/vision/blob/transpose.h"
 
 #include <algorithm>
+#include <limits>
 
 namespace aos {
 namespace vision {
@@ -14,27 +15,30 @@
     kPointAdd = 3,
     kPointDel = 2,
   };
+  int min_y = std::numeric_limits<int>::max();
+  for (const std::vector<ImageRange> &row : img) {
+    if (!row.empty()) min_y = std::min(row[0].st, min_y);
+  }
+
   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);
+      if (range.ed - min_y >= static_cast<int>(events.size())) {
+        events.resize(range.ed - min_y + 1);
+      }
+      events[range.st - min_y].emplace_back(y, kPointAdd);
+      events[range.ed - min_y].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 (int dy = 0; dy < static_cast<int>(events.size()) - 1; ++dy) {
+    auto row_events = std::move(events[dy]);
     for (const auto &range : prev_ranges) {
       row_events.emplace_back(range.st, kRangeStart);
       row_events.emplace_back(range.ed, kRangeEnd);
diff --git a/aos/vision/debug/debug_viewer.cc b/aos/vision/debug/debug_viewer.cc
index 331f733..b0cf295 100644
--- a/aos/vision/debug/debug_viewer.cc
+++ b/aos/vision/debug/debug_viewer.cc
@@ -101,6 +101,10 @@
     window_height_ = h;
     window_width_ = w;
   }
+  if (!shown_yet_) {
+    gtk_widget_show_all(self->window);
+    shown_yet_ = true;
+  }
 }
 
 void DebugViewer::MoveTo(int x, int y) {
@@ -149,7 +153,6 @@
                               window_height_ * scale_factor);
 
   gtk_container_add(GTK_CONTAINER(window), drawing_area);
-  gtk_widget_show_all(window);
 }
 DebugViewer::~DebugViewer() {}
 
diff --git a/aos/vision/debug/debug_viewer.h b/aos/vision/debug/debug_viewer.h
index 3bada28..ab2c716 100644
--- a/aos/vision/debug/debug_viewer.h
+++ b/aos/vision/debug/debug_viewer.h
@@ -66,6 +66,7 @@
   std::function<void(uint32_t)> key_press_event;
 
  private:
+  bool shown_yet_ = false;
   double scale_factor = 1.0;
   int window_width_ = 100;
   int window_height_ = 100;
diff --git a/aos/vision/events/BUILD b/aos/vision/events/BUILD
index 2b5b9e7..9c55a1c 100644
--- a/aos/vision/events/BUILD
+++ b/aos/vision/events/BUILD
@@ -1,5 +1,5 @@
 load('/tools/build_rules/gtk_dependent', 'gtk_dependent_cc_binary', 'gtk_dependent_cc_library')
-package(default_visibility = ["//visibility:public"])
+package(default_visibility = ['//visibility:public'])
 
 cc_library(
   name = 'epoll_events',
@@ -12,12 +12,14 @@
   ],
 )
 
-cc_library(name = "socket_types",
-    hdrs = ["socket_types.h"],
-    deps = [
-        "//aos/vision/events:tcp_server",
-        "//aos/vision/image:image_types",
-    ],
+cc_library(
+  name = 'socket_types',
+  hdrs = ['socket_types.h'],
+  deps = [
+    '//aos/vision/events:tcp_server',
+    '//aos/vision/image:image_types',
+    '//third_party/protobuf:protobuf',
+  ],
 )
 
 cc_library(
@@ -59,10 +61,10 @@
 )
 
 gtk_dependent_cc_library(
-  name = "gtk_event",
-  srcs = ["gtk_event.cc"],
+  name = 'gtk_event',
+  srcs = ['gtk_event.cc'],
   deps = [
-    ":epoll_events",
+    ':epoll_events',
     '@usr_repo//:gtk+-3.0',
   ],
 )
diff --git a/aos/vision/events/epoll_events.cc b/aos/vision/events/epoll_events.cc
index e4f789f..d49043c 100644
--- a/aos/vision/events/epoll_events.cc
+++ b/aos/vision/events/epoll_events.cc
@@ -36,7 +36,7 @@
 
     for (int i = 0; i < number_events; i++) {
       EpollEvent *event = static_cast<EpollEvent *>(events[i].data.ptr);
-      if ((events[i].events & ~(EPOLLIN | EPOLLPRI)) != 0) {
+      if ((events[i].events & ~(EPOLLIN | EPOLLPRI | EPOLLERR)) != 0) {
         LOG(FATAL, "unexpected epoll events set in %x on %d\n",
             events[i].events, event->fd());
       }
diff --git a/aos/vision/events/epoll_events.h b/aos/vision/events/epoll_events.h
index ee288c4..3b7d75a 100644
--- a/aos/vision/events/epoll_events.h
+++ b/aos/vision/events/epoll_events.h
@@ -44,7 +44,7 @@
     // Duplicate above to allow Done to change itself.
     if (time_ < monotonic_clock::epoch()) return -1;
     if (time_ <= now) {
-      return -1;// Recalculate(now);
+      return -1;
     }
 
     if (time_ - now > ::std::chrono::milliseconds(INT_MAX)) {
diff --git a/aos/vision/events/gtk_event.cc b/aos/vision/events/gtk_event.cc
index e8b43aa..0c518e0 100644
--- a/aos/vision/events/gtk_event.cc
+++ b/aos/vision/events/gtk_event.cc
@@ -1,9 +1,9 @@
-#include <gtk/gtk.h>
 #include <gdk/gdk.h>
-#include <thread>
+#include <gtk/gtk.h>
 #include <sys/epoll.h>
-#include <mutex>
 #include <condition_variable>
+#include <mutex>
+#include <thread>
 
 #include "aos/vision/events/epoll_events.h"
 
@@ -43,16 +43,19 @@
   std::thread t([&]() {
     std::unique_lock<std::mutex> lk(m);
     while (true) {
-      cv.wait(lk, [&all_events_handled]{return all_events_handled;});
+      cv.wait(lk, [&all_events_handled] { return all_events_handled; });
       // Wait for handle_cb to be done.
-      number_events = PCHECK(epoll_wait(epoll_fd(), events, kNumberOfEvents, timeout));
+      number_events =
+          PCHECK(epoll_wait(epoll_fd(), events, kNumberOfEvents, timeout));
       all_events_handled = false;
       // Trigger handle_cb on main_thread to avoid concurrency.
-      gdk_threads_add_idle(+[](gpointer user_data) -> gboolean {
-        auto& handle_cb = *reinterpret_cast<HandleCBType*>(user_data);
-        handle_cb();
-        return G_SOURCE_REMOVE;
-      }, &handle_cb);
+      gdk_threads_add_idle(
+          +[](gpointer user_data) -> gboolean {
+            auto &handle_cb = *reinterpret_cast<HandleCBType *>(user_data);
+            handle_cb();
+            return G_SOURCE_REMOVE;
+          },
+          &handle_cb);
     }
   });
   gtk_main();
diff --git a/aos/vision/events/socket_types.h b/aos/vision/events/socket_types.h
index 2432cb9..d377214 100644
--- a/aos/vision/events/socket_types.h
+++ b/aos/vision/events/socket_types.h
@@ -3,6 +3,8 @@
 
 #include <poll.h>
 #include <stdint.h>
+#include <sys/socket.h>
+#include <sys/types.h>
 
 #include "aos/vision/events/tcp_server.h"
 #include "aos/vision/image/image_types.h"
@@ -32,7 +34,12 @@
     char buf[512];
     while (true) {
       count = read(fd(), &buf, sizeof buf);
-      if (count <= 0) return;
+      if (count <= 0) {
+        if (errno != EAGAIN) {
+          CloseConnection();
+          return;
+        }
+      }
     }
   }
 
@@ -46,14 +53,15 @@
   void Emit(vision::DataRef data) {
     data_len len;
     len.len = data.size();
-    int res = write(fd(), len.buf, sizeof len.buf);
+    int res = send(fd(), len.buf, sizeof len.buf, MSG_NOSIGNAL);
     if (res == -1) {
-      printf("Emit Error on write\n");
+      CloseConnection();
+      return;
     }
     size_t write_count = 0;
     while (write_count < data.size()) {
-      int len =
-          write(fd(), &data.data()[write_count], data.size() - write_count);
+      int len = send(fd(), &data.data()[write_count], data.size() - write_count,
+                     MSG_NOSIGNAL);
       if (len == -1) {
         if (errno == EAGAIN) {
           struct pollfd waiting;
@@ -61,7 +69,7 @@
           waiting.events = POLLOUT;
           poll(&waiting, 1, -1);
         } else {
-          close(fd());
+          CloseConnection();
           return;
         }
       } else {
@@ -70,6 +78,13 @@
       if (write_count != data.size()) printf("wrote: %d\n", len);
     }
   }
+
+ private:
+  void CloseConnection() {
+    loop()->Delete(this);
+    close(fd());
+    delete this;
+  }
 };
 
 }  // namespace events
diff --git a/aos/vision/events/tcp_client.cc b/aos/vision/events/tcp_client.cc
index 41485f9..4ac45af 100644
--- a/aos/vision/events/tcp_client.cc
+++ b/aos/vision/events/tcp_client.cc
@@ -30,7 +30,7 @@
   return 0;
 }
 
-int OpenClient(const char *hostname, int portno) {
+int OpenClient(const std::string &hostname, int portno) {
   int sockfd;
   struct sockaddr_in serveraddr;
   struct hostent *server;
@@ -38,9 +38,9 @@
   PCHECK(sockfd = socket(AF_INET, SOCK_STREAM, 0));
 
   /* gethostbyname: get the server's DNS entry */
-  server = gethostbyname(hostname);
+  server = gethostbyname(hostname.c_str());
   if (server == NULL) {
-    fprintf(stderr, "ERROR, no such host as %s\n", hostname);
+    fprintf(stderr, "ERROR, no such host as %s\n", hostname.c_str());
     exit(-1);
   }
 
@@ -60,7 +60,7 @@
 }
 }  // namespace
 
-TcpClient::TcpClient(const char *hostname, int portno)
+TcpClient::TcpClient(const std::string &hostname, int portno)
     : EpollEvent(OpenClient(hostname, portno)) {}
 
 }  // namespace events
diff --git a/aos/vision/events/tcp_client.h b/aos/vision/events/tcp_client.h
index 7ce01f7..74f1418 100644
--- a/aos/vision/events/tcp_client.h
+++ b/aos/vision/events/tcp_client.h
@@ -4,6 +4,7 @@
 #include "aos/vision/events/epoll_events.h"
 
 #include <memory>
+#include <string>
 
 namespace aos {
 namespace events {
@@ -11,7 +12,7 @@
 // Handles the client connection logic to hostname:portno
 class TcpClient : public EpollEvent {
  public:
-  TcpClient(const char *hostname, int portno);
+  TcpClient(const std::string &hostname, int portno);
 
   // Implement ReadEvent from EpollEvent to use this class.
 };
diff --git a/aos/vision/events/udp.cc b/aos/vision/events/udp.cc
index b5367f6..4c0437b 100644
--- a/aos/vision/events/udp.cc
+++ b/aos/vision/events/udp.cc
@@ -22,7 +22,8 @@
 }
 
 int TXUdpSocket::Send(const char *data, int size) {
-  return PCHECK(send(fd_.get(), data, size, 0));
+  // Don't fail on send. If no one is connected that is fine.
+  return send(fd_.get(), data, size, 0);
 }
 
 RXUdpSocket::RXUdpSocket(int port)
diff --git a/aos/vision/image/image_stream.h b/aos/vision/image/image_stream.h
index ac25394..5a1ad21 100644
--- a/aos/vision/image/image_stream.h
+++ b/aos/vision/image/image_stream.h
@@ -18,8 +18,8 @@
       camera::CameraParams params) {
     using namespace std::placeholders;
     std::unique_ptr<::camera::Reader> camread(new ::camera::Reader(
-        fname,
-        std::bind(&ImageStreamEvent::ProcessHelper, obj, _1, _2), params));
+        fname, std::bind(&ImageStreamEvent::ProcessHelper, obj, _1, _2),
+        params));
     camread->StartAsync();
     return camread;
   }
@@ -33,12 +33,13 @@
 
   void ProcessHelper(DataRef data, aos::monotonic_clock::time_point timestamp) {
     if (data.size() < 300) {
-      LOG(INFO, "got bad img of size(%zu)\n", data.size());
+      LOG(INFO, "got bad img of size(%d)\n", static_cast<int>(data.size()));
       return;
     }
     ProcessImage(data, timestamp);
   }
-  virtual void ProcessImage(DataRef data, aos::monotonic_clock::time_point timestamp) = 0;
+  virtual void ProcessImage(DataRef data,
+                            aos::monotonic_clock::time_point timestamp) = 0;
 
   void ReadEvent() override { reader_->HandleFrame(); }
 
diff --git a/aos/vision/image/image_types.h b/aos/vision/image/image_types.h
index 2c9ba56..68e6d67 100644
--- a/aos/vision/image/image_types.h
+++ b/aos/vision/image/image_types.h
@@ -19,7 +19,7 @@
 struct ImageFormat {
   ImageFormat() : w(0), h(0) {}
   ImageFormat(int nw, int nh) : w(nw), h(nh) {}
-  std::string ToString() {
+  std::string ToString() const {
     std::ostringstream s;
     s << "ImageFormat {" << w << ", " << h << "}";
     return s.str();
diff --git a/aos/vision/image/reader.cc b/aos/vision/image/reader.cc
index 95729da..3a4b349 100644
--- a/aos/vision/image/reader.cc
+++ b/aos/vision/image/reader.cc
@@ -29,6 +29,9 @@
   }
 
   Init();
+
+  InitMMap();
+  LOG(INFO, "Bat Vision Successfully Initialized.\n");
 }
 
 void Reader::QueueBuffer(v4l2_buffer *buf) {
@@ -57,6 +60,18 @@
   }
   --queued_;
 
+  if (tick_id_ % 10 == 0) {
+    if (!SetCameraControl(V4L2_CID_EXPOSURE_AUTO, "V4L2_CID_EXPOSURE_AUTO",
+                          V4L2_EXPOSURE_MANUAL)) {
+      LOG(FATAL, "Failed to set exposure\n");
+    }
+
+    if (!SetCameraControl(V4L2_CID_EXPOSURE_ABSOLUTE,
+                          "V4L2_CID_EXPOSURE_ABSOLUTE", params_.exposure)) {
+      LOG(FATAL, "Failed to set exposure\n");
+    }
+  }
+  ++tick_id_;
   // Get a timestamp now as proxy for when the image was taken
   // TODO(ben): the image should come with a timestamp, parker
   // will know how to get it.
@@ -66,6 +81,7 @@
                reinterpret_cast<const char *>(buffers_[buf.index].start),
                buf.bytesused),
            time);
+
   QueueBuffer(&buf);
 }
 
@@ -105,7 +121,7 @@
     }
   }
   queued_ = kNumBuffers;
-  if (req.count < kNumBuffers) {
+  if (req.count != kNumBuffers) {
     LOG(FATAL, "Insufficient buffer memory on %s\n", dev_name_.c_str());
   }
 }
@@ -236,9 +252,6 @@
       setfps->parm.capture.timeperframe.numerator,
       setfps->parm.capture.timeperframe.denominator);
   // #endif
-
-  InitMMap();
-  LOG(INFO, "Bat Vision Successfully Initialized.\n");
 }
 
 aos::vision::ImageFormat Reader::get_format() {
diff --git a/aos/vision/image/reader.h b/aos/vision/image/reader.h
index eabb80a..28735fb 100644
--- a/aos/vision/image/reader.h
+++ b/aos/vision/image/reader.h
@@ -41,8 +41,8 @@
 
   void HandleFrame();
   void StartAsync() {
-    Start();
     MMapBuffers();
+    Start();
   }
   int fd() { return fd_; }
 
@@ -60,6 +60,7 @@
 
   ProcessCb process_;
 
+  int tick_id_ = 0;
   // The number of buffers currently queued in v4l2.
   uint32_t queued_;
   struct Buffer;
@@ -67,7 +68,10 @@
   // because the buffers are not ummapped.
   Buffer *buffers_;
 
-  static const unsigned int kNumBuffers = 10;
+  // TODO(parker): The timestamps should be queue insertion timestamps
+  // which will remove the impact of kNumBuffers.
+  // TODO(parker): Flush the queue (or tweak the FPS) if we fall behind.
+  static const unsigned int kNumBuffers = 5;
 
   // set only at initialize
   CameraParams params_;
diff --git a/aos/vision/tools/jpeg_vision_test.cc b/aos/vision/tools/jpeg_vision_test.cc
index 37d9ffe..52dd22f 100644
--- a/aos/vision/tools/jpeg_vision_test.cc
+++ b/aos/vision/tools/jpeg_vision_test.cc
@@ -2,42 +2,64 @@
 // should not placed on a robot. This is okay as it is a utility of limited use
 // only.
 
-#include <stdio.h>
-#include <stdlib.h>
+#include <gtk/gtk.h>
 #include <netdb.h>
 #include <poll.h>
+#include <stdio.h>
+#include <stdlib.h>
 #include <string.h>
-#include <gtk/gtk.h>
-#include <vector>
-#include <memory>
 #include <fstream>
+#include <memory>
+#include <vector>
 
-#include "aos/common/logging/logging.h"
 #include "aos/common/logging/implementations.h"
-#include "aos/vision/math/vector.h"
-#include "aos/vision/image/reader.h"
-#include "aos/vision/image/jpeg_routines.h"
-#include "aos/vision/blob/threshold.h"
+#include "aos/common/logging/logging.h"
 #include "aos/vision/blob/range_image.h"
 #include "aos/vision/blob/stream_view.h"
+#include "aos/vision/blob/threshold.h"
 #include "aos/vision/events/epoll_events.h"
-#include "aos/vision/image/image_stream.h"
 #include "aos/vision/events/tcp_server.h"
+#include "aos/vision/image/image_stream.h"
+#include "aos/vision/image/jpeg_routines.h"
+#include "aos/vision/image/reader.h"
+#include "aos/vision/math/vector.h"
 
 namespace aos {
 namespace vision {
 
+void DrawVLine(ImagePtr ptr, int x, PixelRef color = {255, 0, 0}) {
+  for (int y = 0; y < ptr.fmt().h; ++y) {
+    ptr.get_px(x, y) = color;
+  }
+}
+void DrawHLine(ImagePtr ptr, int y, PixelRef color = {255, 0, 0}) {
+  for (int x = 0; x < ptr.fmt().w; ++x) {
+    ptr.get_px(x, y) = color;
+  }
+}
+
 // Connects up a camera with our processing.
 class ChannelImageStream : public ImageStreamEvent {
  public:
   ChannelImageStream(const std::string &fname,
                      const camera::CameraParams &params)
-      : ImageStreamEvent(fname, params), view_(true) {
+      : ImageStreamEvent(fname, params), view_(false) {
     // Lambda to record image data to a file on key press.
-    view_.view()->key_press_event = [this](uint32_t /*keyval*/) {
-      std::ofstream ofs("/tmp/test.jpg", std::ofstream::out);
-      ofs << prev_data_;
-      ofs.close();
+    view_.view()->key_press_event = [this](uint32_t keyval) {
+      if (keyval == 'j') {
+        std::ofstream ofs("/tmp/test.jpg", std::ofstream::out);
+        ofs << prev_data_;
+        ofs.close();
+      } else if (keyval == 'a') {
+        ++dx;
+      } else if (keyval == 'd') {
+        --dx;
+      } else if (keyval == 'w') {
+        ++dy;
+      } else if (keyval == 's') {
+        --dy;
+      }
+      fprintf(stderr, "dx: %d dy: %d\n", dx, dy);
     };
   }
 
@@ -51,7 +73,6 @@
     ImagePtr img_ptr = view_.img();
     prev_data_ = data.to_string();
 
-
     // Threshold the image with the given lambda.
     RangeImage rimg = DoThreshold(img_ptr, [](PixelRef &px) {
       if (px.g > 88) {
@@ -65,6 +86,13 @@
       return false;
     });
 
+    DrawVLine(img_ptr, fmt.w / 4);
+    DrawVLine(img_ptr, fmt.w / 2 + dy, {0, 255, 0});
+    DrawVLine(img_ptr, fmt.w - fmt.w / 4);
+
+    DrawHLine(img_ptr, fmt.h / 4);
+    DrawHLine(img_ptr, fmt.h / 2 + dx, {0, 255, 0});
+    DrawHLine(img_ptr, fmt.h - fmt.h / 4);
     view_.DrawBlobList({rimg}, {255, 255, 255});
 
     view_.Redraw();
@@ -75,6 +103,9 @@
 
   // responsible for handling drawing
   BlobStreamViewer view_;
+
+  int dx = 0;
+  int dy = 0;
 };
 }  // namespace aos
 }  // namespace vision
@@ -91,7 +122,7 @@
                                  .exposure = 10,
                                  .brightness = 128,
                                  .gain = 0,
-                                 .fps = 10};
+                                 .fps = 25};
 
   aos::vision::ChannelImageStream strm1("/dev/video1", params);