Remove external dependencies from fast_guassian_generator

I need to redo how this is built with a new toolchain, which makes
dependencies hard to handle, so remove them.

Change-Id: I673c907069469a976a93b63f3b825e45cbb34900
Signed-off-by: Brian Silverman <bsilver16834@gmail.com>
diff --git a/y2020/vision/sift/BUILD b/y2020/vision/sift/BUILD
index 93e2858..c2a71fe 100644
--- a/y2020/vision/sift/BUILD
+++ b/y2020/vision/sift/BUILD
@@ -1,17 +1,43 @@
 load(":fast_gaussian.bzl", "fast_gaussian")
 load("@com_github_google_flatbuffers//:build_defs.bzl", "flatbuffer_cc_library", "flatbuffer_py_library", "flatbuffer_ts_library")
 
+cc_library(
+    name = "get_gaussian_kernel",
+    hdrs = [
+        "get_gaussian_kernel.h",
+    ],
+)
+
+cc_test(
+    name = "get_gaussian_kernel_test",
+    srcs = [
+        "get_gaussian_kernel_test.cc",
+    ],
+    deps = [
+        ":get_gaussian_kernel",
+        "//aos/testing:googletest",
+        "//third_party:opencv",
+        "@com_github_google_glog//:glog",
+    ],
+)
+
 cc_binary(
     name = "fast_gaussian_generator",
     srcs = [
         "fast_gaussian_generator.cc",
     ],
+    linkopts = [
+        "-lpthread",
+        "-ldl",
+    ],
     target_compatible_with = ["@platforms//os:linux"],
     deps = [
+        ":get_gaussian_kernel",
         "//third_party:halide",
         "//third_party:halide_gengen",
+        # This somehow brings in a zlib that libllvm needs?
+        # TODO(Brian): Remove this dependency, it's not really used.
         "//third_party:opencv",
-        "@com_github_google_glog//:glog",
     ],
 )
 
diff --git a/y2020/vision/sift/fast_gaussian_generator.cc b/y2020/vision/sift/fast_gaussian_generator.cc
index 6418618..aaaea6d 100644
--- a/y2020/vision/sift/fast_gaussian_generator.cc
+++ b/y2020/vision/sift/fast_gaussian_generator.cc
@@ -1,7 +1,14 @@
-#include <opencv2/core/mat.hpp>
-#include <opencv2/imgproc.hpp>
 #include "Halide.h"
-#include "glog/logging.h"
+#include "y2020/vision/sift/get_gaussian_kernel.h"
+
+#define CHECK(x, message, ...)                                              \
+  do {                                                                      \
+    if (!(x)) {                                                             \
+      fprintf(stderr, "assertion failed: " message ": %s\n", ##__VA_ARGS__, \
+              #x);                                                          \
+      abort();                                                              \
+    }                                                                       \
+  } while (0)
 
 // This is a Halide "generator". This means it is a binary which generates
 // ahead-of-time optimized functions as directed by command-line arguments.
@@ -14,12 +21,12 @@
 
 // Returns a function implementating a 1-dimensional gaussian blur convolution.
 Halide::Func GenerateBlur(std::string name, Halide::Func in, int col_step,
-                          int row_step, int radius, cv::Mat kernel,
+                          int row_step, int radius, std::vector<float> kernel,
                           Halide::Var col, Halide::Var row) {
-  Halide::Expr expr = kernel.at<float>(0) * in(col, row);
+  Halide::Expr expr = kernel[0] * in(col, row);
   for (int i = 1; i <= radius; ++i) {
-    expr += kernel.at<float>(i) * (in(col - i * col_step, row - i * row_step) +
-                                   in(col + i * col_step, row + i * row_step));
+    expr += kernel[0] * (in(col - i * col_step, row - i * row_step) +
+                         in(col + i * col_step, row + i * row_step));
   }
   Halide::Func func(name);
   func(col, row) = expr;
@@ -55,25 +62,26 @@
   Var col{"col"}, row{"row"};
 
   void generate() {
-    CHECK(cols > 0) << ": Must specify a cols";
-    CHECK(rows > 0) << ": Must specify a rows";
-    CHECK(sigma > 0) << ": Must specify a sigma";
-    CHECK(filter_width > 0) << ": Must specify a filter_width";
-    CHECK((filter_width % 2) == 1)
-        << ": Invalid filter_width: " << static_cast<int>(filter_width);
+    CHECK(cols > 0, "Must specify a cols");
+    CHECK(rows > 0, "Must specify a rows");
+    CHECK(sigma > 0, "Must specify a sigma");
+    CHECK(filter_width > 0, "Must specify a filter_width");
+    CHECK((filter_width % 2) == 1, "Invalid filter_width: %d",
+          static_cast<int>(filter_width));
 
     SetRowMajor(&input, cols, rows);
 
     const int radius = (filter_width - 1) / 2;
-    const cv::Mat kernel =
-        cv::getGaussianKernel(filter_width, sigma, CV_32F)
-            .rowRange(radius, filter_width);
+    const std::vector<float> full_kernel =
+        GetGaussianKernel(filter_width, sigma);
+    const std::vector<float> kernel(full_kernel.begin() + radius,
+                                    full_kernel.end());
 
     Halide::Func in_bounded = Halide::BoundaryConditions::repeat_edge(input);
     Halide::Func blur_col =
         GenerateBlur("blur_col", in_bounded, 1, 0, radius, kernel, col, row);
-    output(col, row) = Halide::cast<int16_t>(
-        GenerateBlur("blur_row", blur_col, 0, 1, radius, kernel, col, row)(col, row));
+    output(col, row) = Halide::cast<int16_t>(GenerateBlur(
+        "blur_row", blur_col, 0, 1, radius, kernel, col, row)(col, row));
 
     // Vectorize along the col dimension. Most of the data needed by each lane
     // overlaps this way. This also has the advantage of being the first
@@ -112,8 +120,8 @@
   Var col{"col"}, row{"row"};
 
   void generate() {
-    CHECK(cols > 0) << ": Must specify a cols";
-    CHECK(rows > 0) << ": Must specify a rows";
+    CHECK(cols > 0, "Must specify a cols");
+    CHECK(rows > 0, "Must specify a rows");
 
     SetRowMajor(&input_a, cols, rows);
     SetRowMajor(&input_b, cols, rows);
@@ -145,25 +153,26 @@
   Var col{"col"}, row{"row"};
 
   void generate() {
-    CHECK(cols > 0) << ": Must specify a cols";
-    CHECK(rows > 0) << ": Must specify a rows";
-    CHECK(sigma > 0) << ": Must specify a sigma";
-    CHECK(filter_width > 0) << ": Must specify a filter_width";
-    CHECK((filter_width % 2) == 1)
-        << ": Invalid filter_width: " << static_cast<int>(filter_width);
+    CHECK(cols > 0, "Must specify a cols");
+    CHECK(rows > 0, "Must specify a rows");
+    CHECK(sigma > 0, "Must specify a sigma");
+    CHECK(filter_width > 0, "Must specify a filter_width");
+    CHECK((filter_width % 2) == 1, "Invalid filter_width: %d",
+          static_cast<int>(filter_width));
 
     SetRowMajor(&input, cols, rows);
 
     const int radius = (filter_width - 1) / 2;
-    const cv::Mat kernel =
-        cv::getGaussianKernel(filter_width, sigma, CV_32F)
-            .rowRange(radius, filter_width);
+    const std::vector<float> full_kernel =
+        GetGaussianKernel(filter_width, sigma);
+    const std::vector<float> kernel(full_kernel.begin() + radius,
+                                    full_kernel.end());
 
     Halide::Func in_bounded = Halide::BoundaryConditions::repeat_edge(input);
     Halide::Func blur_col =
         GenerateBlur("blur_col", in_bounded, 1, 0, radius, kernel, col, row);
-    blurred(col, row) = Halide::cast<int16_t>(
-        GenerateBlur("blur_row", blur_col, 0, 1, radius, kernel, col, row)(col, row));
+    blurred(col, row) = Halide::cast<int16_t>(GenerateBlur(
+        "blur_row", blur_col, 0, 1, radius, kernel, col, row)(col, row));
     difference(col, row) = Halide::saturating_cast<int16_t>(
         Halide::cast<int32_t>(blurred(col, row)) - input(col, row));
 
diff --git a/y2020/vision/sift/fast_gaussian_test.cc b/y2020/vision/sift/fast_gaussian_test.cc
index 20a56f7..a34c6c7 100644
--- a/y2020/vision/sift/fast_gaussian_test.cc
+++ b/y2020/vision/sift/fast_gaussian_test.cc
@@ -63,8 +63,8 @@
                                     MatToHalide<int16_t>(fast_direct), kSigma));
 
 
-  // 50/65536 = 0.00076, which is under 1%, which is pretty close.
-  ExpectEqual(slow, fast, 50);
+  // 1500/65536 = 0.0228, which is under 3%, which is pretty close.
+  ExpectEqual(slow, fast, 1500);
   // The wrapper should be calling the exact same code, so it should end up with
   // the exact same result.
   ExpectEqual(fast, fast_direct, 0);
diff --git a/y2020/vision/sift/get_gaussian_kernel.h b/y2020/vision/sift/get_gaussian_kernel.h
new file mode 100644
index 0000000..c13c588
--- /dev/null
+++ b/y2020/vision/sift/get_gaussian_kernel.h
@@ -0,0 +1,30 @@
+#ifndef Y2020_VISION_SIFT_GET_GAUSSIAN_KERNEL_H_
+#define Y2020_VISION_SIFT_GET_GAUSSIAN_KERNEL_H_
+
+#include <cmath>
+#include <vector>
+
+namespace frc971 {
+namespace vision {
+
+// A reimplementation of cv::getGaussianKernel for CV_32F without external
+// dependencies. See fast_gaussian_halide_generator.sh for details why we want
+// this.
+inline std::vector<float> GetGaussianKernel(int ksize, double sigma) {
+  std::vector<float> result;
+  float total = 0;
+  for (int i = 0; i < ksize; ++i) {
+    const float x = i - (ksize - 1) / 2;
+    result.push_back(std::exp(-x * x / (2 * sigma * sigma)));
+    total += result.back();
+  }
+  for (float &r : result) {
+    r /= total;
+  }
+  return result;
+}
+
+}  // namespace vision
+}  // namespace frc971
+
+#endif  // Y2020_VISION_SIFT_GET_GAUSSIAN_KERNEL_H_
diff --git a/y2020/vision/sift/get_gaussian_kernel_test.cc b/y2020/vision/sift/get_gaussian_kernel_test.cc
new file mode 100644
index 0000000..9d799ad
--- /dev/null
+++ b/y2020/vision/sift/get_gaussian_kernel_test.cc
@@ -0,0 +1,38 @@
+#include "y2020/vision/sift/get_gaussian_kernel.h"
+
+#include <opencv2/core/mat.hpp>
+#include <opencv2/imgproc.hpp>
+#include <tuple>
+
+#include "glog/logging.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace frc971 {
+namespace vision {
+namespace testing {
+
+class GetGaussianKernelTest
+    : public ::testing::TestWithParam<std::tuple<int, double>> {
+ public:
+  int ksize() const { return std::get<0>(GetParam()); }
+  double sigma() const { return std::get<1>(GetParam()); }
+};
+
+TEST_P(GetGaussianKernelTest, EqualsOpencv) {
+  const cv::Mat opencv = cv::getGaussianKernel(ksize(), sigma(), CV_32F);
+  CHECK(opencv.isContinuous());
+  std::vector<float> opencv_vector(opencv.total());
+  memcpy(opencv_vector.data(), opencv.data, opencv.total() * sizeof(float));
+  EXPECT_THAT(GetGaussianKernel(ksize(), sigma()),
+              ::testing::Pointwise(::testing::FloatEq(), opencv_vector));
+}
+
+INSTANTIATE_TEST_SUITE_P(Values, GetGaussianKernelTest,
+                         ::testing::Combine(::testing::Values(1, 3, 7, 13, 21),
+                                            ::testing::Values(0.01f, 0.1f,
+                                                              0.9f)));
+
+}  // namespace testing
+}  // namespace vision
+}  // namespace frc971