Add BiCubic interpolation.

This bi-cubic interpolation implementation is based
on the cubic convolution algorithm of keys, which allows
us to implement a bi-cubic spline like interpolation scheme
using five one dimensional cubic spline operations.

Change-Id: I116aa8036191c3e654af788323fc8298ae8252a6
diff --git a/include/ceres/cubic_interpolation.h b/include/ceres/cubic_interpolation.h
index 5d18b59..2ade679 100644
--- a/include/ceres/cubic_interpolation.h
+++ b/include/ceres/cubic_interpolation.h
@@ -37,9 +37,10 @@
 
 // This class takes as input a one dimensional array of values that is
 // assumed to be integer valued samples from a function f(x),
-// evaluated at x = 0, ... , n - 1 and uses Catmull-Rom splines to
+// evaluated at x = 0, ... , n - 1 and uses cubic Hermite splines to
 // produce a smooth approximation to it that can be used to evaluate
-// the f(x) and f'(x) at any point in the interval [0, n-1].
+// the f(x) and f'(x) at any fractional point in the interval [0,
+// n-1].
 //
 // Besides this, the reason this class is included with Ceres is that
 // the Evaluate method is overloaded so that the user can use it as
@@ -50,7 +51,6 @@
 // For more details on cubic interpolation see
 //
 // http://en.wikipedia.org/wiki/Cubic_Hermite_spline
-// http://www.paulinternet.nl/?page=bicubic
 //
 // Example usage:
 //
@@ -60,6 +60,9 @@
 //  CHECK(interpolator.Evaluator(1.5, &f, &dfdx));
 class CERES_EXPORT CubicInterpolator {
  public:
+  // values is an array containing the values of the function to be
+  // interpolated on the integer lattice [0, num_values - 1].
+  //
   // values should be a valid pointer for the lifetime of this object.
   CubicInterpolator(const double* values, int num_values);
 
@@ -84,6 +87,57 @@
   const int num_values_;
 };
 
+// This class takes as input a row-major array of values that is
+// assumed to be integer valued samples from a function f(x),
+// evaluated on the integer lattice [0, num_rows - 1] x [0, num_cols -
+// 1]; and uses the cubic convolution interpolation algorithm of
+// R. Keys, to produce a smooth approximation to it that can be used
+// to evaluate the f(r,c), df(r, c)/dr and df(r,c)/dc at any
+// fractional point inside this lattice.
+//
+// For more details on cubic interpolation see
+//
+// "Cubic convolution interpolation for digital image processing".
+// IEEE Transactions on Acoustics, Speech, and Signal Processing
+// 29 (6): 1153–1160.
+//
+// http://en.wikipedia.org/wiki/Cubic_Hermite_spline
+// http://en.wikipedia.org/wiki/Bicubic_interpolation
+class CERES_EXPORT BiCubicInterpolator {
+ public:
+  // values is a row-major array containing the values of the function
+  // to be interpolated on the integer lattice [0, num_rows - 1] x [0,
+  // num_cols - 1];
+  //
+  // values should be a valid pointer for the lifetime of this object.
+  BiCubicInterpolator(const double* values, int num_rows, int num_cols);
+
+  // Evaluate the interpolated function value and/or its
+  // derivative. Returns false if r or c is out of bounds.
+  bool Evaluate(double r, double c,
+                double* f, double* dfdr, double* dfdc) const;
+
+  // Overload for Jets, which automatically accounts for the chain rule.
+  template<typename JetT> bool Evaluate(const JetT& r,
+                                        const JetT& c,
+                                        JetT* f) const {
+    double dfdr, dfdc;
+    if (!Evaluate(r.a, c.a, &f->a, &dfdr, &dfdc)) {
+      return false;
+    }
+    f->v = dfdr * r.v + dfdc * c.v;
+    return true;
+  }
+
+  int num_rows() const { return num_rows_; }
+  int num_cols() const { return num_cols_; }
+
+ private:
+  const double* values_;
+  const int num_rows_;
+  const int num_cols_;
+};
+
 }  // namespace ceres
 
 #endif  // CERES_PUBLIC_CUBIC_INTERPOLATOR_H_
diff --git a/internal/ceres/cubic_interpolation.cc b/internal/ceres/cubic_interpolation.cc
index 9dd6194..b202579 100644
--- a/internal/ceres/cubic_interpolation.cc
+++ b/internal/ceres/cubic_interpolation.cc
@@ -27,9 +27,6 @@
 // POSSIBILITY OF SUCH DAMAGE.
 //
 // Author: sameeragarwal@google.com (Sameer Agarwal)
-//
-// This implementation was inspired by the description at
-// http://www.paulinternet.nl/?page=bicubic
 
 #include "ceres/cubic_interpolation.h"
 
@@ -39,13 +36,35 @@
 namespace ceres {
 namespace {
 
-inline void CatmullRomSpline(const double p0,
-                             const double p1,
-                             const double p2,
-                             const double p3,
-                             const double x,
-                             double* f,
-                             double* dfdx) {
+// Given samples from a function sampled at four equally spaced points,
+//
+//   p0 = f(-1)
+//   p1 = f(0)
+//   p2 = f(1)
+//   p3 = f(2)
+//
+// Evaluate the cubic Hermite spline (also known as the Catmull-Rom
+// spline) at a point x that lies in the interval [0, 1].
+//
+// This is also the interpolation kernel proposed by R. Keys, in:
+//
+// "Cubic convolution interpolation for digital image processing".
+// IEEE Transactions on Acoustics, Speech, and Signal Processing
+// 29 (6): 1153–1160.
+//
+// For the case of a = -0.5.
+//
+// For more details see
+//
+// http://en.wikipedia.org/wiki/Cubic_Hermite_spline
+// http://en.wikipedia.org/wiki/Bicubic_interpolation
+inline void CubicHermiteSpline(const double p0,
+                               const double p1,
+                               const double p2,
+                               const double p3,
+                               const double x,
+                               double* f,
+                               double* dfdx) {
   const double a = 0.5 * (-p0 + 3.0 * p1 - 3.0 * p2 + p3);
   const double b = 0.5 * (2.0 * p0 - 5.0 * p1 + 4.0 * p2 - p3);
   const double c = 0.5 * (-p0 + p2);
@@ -77,6 +96,8 @@
                                  double* f,
                                  double* dfdx) const {
   if (x < 0 || x > num_values_ - 1) {
+    LOG(ERROR) << "x =  " << x
+               << " is not in the interval [0, " << num_values_ - 1 << "].";
     return false;
   }
 
@@ -91,8 +112,148 @@
   const double p2 = values_[n + 1];
   const double p0 = (n > 0) ? values_[n - 1] : (2.0 * p1 - p2);
   const double p3 = (n < (num_values_ - 2)) ? values_[n + 2] : (2.0 * p2 - p1);
-  CatmullRomSpline(p0, p1, p2, p3, x - n, f, dfdx);
+  CubicHermiteSpline(p0, p1, p2, p3, x - n, f, dfdx);
   return true;
 }
 
+BiCubicInterpolator::BiCubicInterpolator(const double* values,
+                                         const int num_rows,
+                                         const int num_cols)
+    : values_(CHECK_NOTNULL(values)),
+      num_rows_(num_rows),
+      num_cols_(num_cols) {
+  CHECK_GT(num_rows, 1);
+  CHECK_GT(num_cols, 1);
+}
+
+bool BiCubicInterpolator::Evaluate(const double r,
+                                   const double c,
+                                   double* f,
+                                   double* dfdr,
+                                   double* dfdc) const {
+  if (r < 0 || r > num_rows_ - 1 || c < 0 || c > num_cols_ - 1) {
+    LOG(ERROR) << "(r, c) =  " << r << ", " << c
+               << " is not in the square defined by [0, 0] "
+               << " and [" << num_rows_ - 1 << ", " << num_cols_ - 1 << "]";
+    return false;
+  }
+
+  int row = floor(r);
+  // Handle the case where the point sits exactly on the bottom
+  // boundary.
+  if (row == num_rows_ - 1) {
+    row -= 1;
+  }
+
+  int col = floor(c);
+  // Handle the case where the point sits exactly on the right
+  // boundary.
+  if (col == num_cols_ - 1) {
+    col -= 1;
+  }
+
+#define v(n, m) values_[(n) * num_cols_ + m]
+
+  // BiCubic interpolation requires 16 values around the point being
+  // evaluated.  We will use pij, to indicate the elements of the 4x4
+  // array of values.
+  //
+  //          col
+  //      p00 p01 p02 p03
+  // row  p10 p11 p12 p13
+  //      p20 p21 p22 p23
+  //      p30 p31 p32 p33
+  //
+  // The point (r,c) being evaluated is assumed to lie in the square
+  // defined by p11, p12, p22 and p21.
+
+  // These four entries are guaranteed to be in the values_ array.
+  const double p11 = v(row, col);
+  const double p12 = v(row, col + 1);
+  const double p21 = v(row + 1, col);
+  const double p22 = v(row + 1, col + 1);
+
+  // If we are in rows >= 1, then choose the element from the row - 1,
+  // otherwise linearly interpolate from row and row + 1.
+  const double p01 = (row > 0) ? v(row - 1, col) : 2 * p11 - p21;
+  const double p02 = (row > 0) ? v(row - 1, col + 1) : 2 * p12 - p22;
+
+  // If we are in row < num_rows_ - 2, then pick the element from the
+  // row + 2, otherwise linearly interpolate from row and row + 1.
+  const double p31 = (row < num_rows_ - 2) ? v(row + 2, col) : 2 * p21 - p11;
+  const double p32 = (row < num_rows_ - 2) ? v(row + 2, col + 1) : 2 * p22 - p12;  // NOLINT
+
+  // Same logic as above, applies to the columns instead of rows.
+  const double p10 = (col > 0) ? v(row, col - 1) : 2 * p11 - p12;
+  const double p20 = (col > 0) ? v(row + 1, col - 1) : 2 * p21 - p22;
+  const double p13 = (col < num_cols_ - 2) ? v(row, col + 2) : 2 * p12 - p11;
+  const double p23 = (col < num_cols_ - 2) ? v(row + 1, col + 2) : 2 * p22 - p21;  // NOLINT
+
+  // The four corners of the block require a bit more care.  Let us
+  // consider the evaluation of p00, the other four corners follow in
+  // the same manner.
+  //
+  // There are four cases in which we need to evaluate p00.
+  //
+  // row > 0, col > 0 : v(row, col)
+  // row = 0, col > 1 : Interpolate p10 & p20
+  // row > 1, col = 0 : Interpolate p01 & p02
+  // row = 0, col = 0 : Interpolate p10 & p20, or p01 & p02.
+  double p00, p03;
+  if (row > 0) {
+    if (col > 0) {
+      p00 = v(row - 1, col - 1);
+    } else {
+      p00 = 2 * p01 - p02;
+    }
+
+    if (col < num_cols_ - 2) {
+      p03 = v(row - 1, col + 2);
+    } else {
+      p03 = 2 * p02 - p01;
+    }
+  } else {
+    p00 = 2 * p10 - p20;
+    p03 = 2 * p13 - p23;
+  }
+
+  double p30, p33;
+  if (row < num_rows_ - 2) {
+    if (col > 0) {
+      p30 = v(row + 2, col - 1);
+    } else {
+      p30 = 2 * p31 - p32;
+    }
+
+    if (col < num_cols_ - 2) {
+      p33 = v(row + 2, col + 2);
+    } else {
+      p33 = 2 * p32 - p31;
+    }
+  } else {
+    p30 = 2 * p20 - p10;
+    p33 = 2 * p23 - p13;
+  }
+
+  // Interpolate along each of the four rows, evaluating the function
+  // value and the horizontal derivative in each row.
+  double f0, f1, f2, f3;
+  double df0dc, df1dc, df2dc, df3dc;
+  CubicHermiteSpline(p00, p01, p02, p03, c - col, &f0, &df0dc);
+  CubicHermiteSpline(p10, p11, p12, p13, c - col, &f1, &df1dc);
+  CubicHermiteSpline(p20, p21, p22, p23, c - col, &f2, &df2dc);
+  CubicHermiteSpline(p30, p31, p32, p33, c - col, &f3, &df3dc);
+
+  // Interpolate vertically the interpolated value from each row and
+  // compute the derivative along the columns.
+  CubicHermiteSpline(f0, f1, f2, f3, r - row, f, dfdr);
+  if (dfdc != NULL) {
+    // Interpolate vertically the derivative along the columns.
+    CubicHermiteSpline(df0dc, df1dc, df2dc, df3dc, r - row, dfdc, NULL);
+  }
+
+  return true;
+#undef v
+}
+
 }  // namespace ceres
diff --git a/internal/ceres/cubic_interpolation_test.cc b/internal/ceres/cubic_interpolation_test.cc
index 04aacc0..d3b5b49 100644
--- a/internal/ceres/cubic_interpolation_test.cc
+++ b/internal/ceres/cubic_interpolation_test.cc
@@ -47,7 +47,6 @@
 
 class CubicInterpolatorTest : public ::testing::Test {
  public:
-
   void RunPolynomialInterpolationTest(const double a,
                                       const double b,
                                       const double c,
@@ -83,6 +82,7 @@
     }
   }
 
+ private:
   static const int kNumSamples = 10;
   static const int kNumTestSamples = 100;
   double values_[kNumSamples];
@@ -109,22 +109,188 @@
 
   // Create a Jet with the same scalar part as x, so that the output
   // Jet will be evaluate at x.
-  Jet<double, 4> input_jet;
-  input_jet.a = x;
-  input_jet.v(0) = 1.0;
-  input_jet.v(1) = 1.1;
-  input_jet.v(2) = 1.2;
-  input_jet.v(3) = 1.3;
+  Jet<double, 4> x_jet;
+  x_jet.a = x;
+  x_jet.v(0) = 1.0;
+  x_jet.v(1) = 1.1;
+  x_jet.v(2) = 1.2;
+  x_jet.v(3) = 1.3;
 
-  Jet<double, 4> output_jet;
-  EXPECT_TRUE(interpolator.Evaluate(input_jet, &output_jet));
+  Jet<double, 4> f_jet;
+  EXPECT_TRUE(interpolator.Evaluate(x_jet, &f_jet));
 
   // Check that the scalar part of the Jet is f(x).
-  EXPECT_EQ(output_jet.a, f);
+  EXPECT_EQ(f_jet.a, f);
 
-  // Check that the derivative part of the Jet is dfdx * input_jet.v
+  // Check that the derivative part of the Jet is dfdx * x_jet.v
   // by the chain rule.
-  EXPECT_EQ((output_jet.v - dfdx * input_jet.v).norm(), 0.0);
+  EXPECT_EQ((f_jet.v - dfdx * x_jet.v).norm(), 0.0);
+}
+
+class BiCubicInterpolatorTest : public ::testing::Test {
+ public:
+  void RunPolynomialInterpolationTest(const Eigen::Matrix3d& coeff) {
+    coeff_ = coeff;
+    double* v = values_;
+    for (int r = 0; r < kNumRows; ++r) {
+      for (int c = 0; c < kNumCols; ++c) {
+        *v++ = EvaluateF(r, c);
+      }
+    }
+    BiCubicInterpolator interpolator(values_, kNumRows, kNumCols);
+
+    for (int j = 0; j < kNumRowSamples; ++j) {
+      const double r = 1.0 + 7.0 / (kNumRowSamples - 1) * j;
+      for (int k = 0; k < kNumColSamples; ++k) {
+        const double c = 1.0 + 7.0 / (kNumColSamples - 1) * k;
+        const double expected_f = EvaluateF(r, c);
+        const double expected_dfdr = EvaluatedFdr(r, c);
+        const double expected_dfdc = EvaluatedFdc(r, c);
+        double f, dfdr, dfdc;
+
+        EXPECT_TRUE(interpolator.Evaluate(r, c, &f, &dfdr, &dfdc));
+        EXPECT_NEAR(f, expected_f, kTolerance);
+        EXPECT_NEAR(dfdr, expected_dfdr, kTolerance);
+        EXPECT_NEAR(dfdc, expected_dfdc, kTolerance);
+      }
+    }
+  }
+
+ private:
+  double EvaluateF(double r, double c) {
+    Eigen::Vector3d x;
+    x(0) = r;
+    x(1) = c;
+    x(2) = 1;
+    return x.transpose() * coeff_ * x;
+  }
+
+  double EvaluatedFdr(double r, double c) {
+    Eigen::Vector3d x;
+    x(0) = r;
+    x(1) = c;
+    x(2) = 1;
+    return (coeff_.row(0) + coeff_.col(0).transpose()) * x;
+  }
+
+  double EvaluatedFdc(double r, double c) {
+    Eigen::Vector3d x;
+    x(0) = r;
+    x(1) = c;
+    x(2) = 1;
+    return (coeff_.row(1) + coeff_.col(1).transpose()) * x;
+  }
+
+
+  Eigen::Matrix3d coeff_;
+  static const int kNumRows = 10;
+  static const int kNumCols = 10;
+  static const int kNumRowSamples = 100;
+  static const int kNumColSamples = 100;
+  double values_[kNumRows * kNumCols];
+};
+
+TEST_F(BiCubicInterpolatorTest, ZeroFunction) {
+  Eigen::Matrix3d coeff = Eigen::Matrix3d::Zero();
+  RunPolynomialInterpolationTest(coeff);
+}
+
+TEST_F(BiCubicInterpolatorTest, Degree00Function) {
+  Eigen::Matrix3d coeff = Eigen::Matrix3d::Zero();
+  coeff(2, 2) = 1.0;
+  RunPolynomialInterpolationTest(coeff);
+}
+
+TEST_F(BiCubicInterpolatorTest, Degree01Function) {
+  Eigen::Matrix3d coeff = Eigen::Matrix3d::Zero();
+  coeff(2, 2) = 1.0;
+  coeff(0, 2) = 0.1;
+  coeff(2, 0) = 0.1;
+  RunPolynomialInterpolationTest(coeff);
+}
+
+TEST_F(BiCubicInterpolatorTest, Degree10Function) {
+  Eigen::Matrix3d coeff = Eigen::Matrix3d::Zero();
+  coeff(2, 2) = 1.0;
+  coeff(0, 1) = 0.1;
+  coeff(1, 0) = 0.1;
+  RunPolynomialInterpolationTest(coeff);
+}
+
+TEST_F(BiCubicInterpolatorTest, Degree11Function) {
+  Eigen::Matrix3d coeff = Eigen::Matrix3d::Zero();
+  coeff(2, 2) = 1.0;
+  coeff(0, 1) = 0.1;
+  coeff(1, 0) = 0.1;
+  coeff(0, 2) = 0.2;
+  coeff(2, 0) = 0.2;
+  RunPolynomialInterpolationTest(coeff);
+}
+
+TEST_F(BiCubicInterpolatorTest, Degree12Function) {
+  Eigen::Matrix3d coeff = Eigen::Matrix3d::Zero();
+  coeff(2, 2) = 1.0;
+  coeff(0, 1) = 0.1;
+  coeff(1, 0) = 0.1;
+  coeff(0, 2) = 0.2;
+  coeff(2, 0) = 0.2;
+  coeff(1, 1) = 0.3;
+  RunPolynomialInterpolationTest(coeff);
+}
+
+TEST_F(BiCubicInterpolatorTest, Degree21Function) {
+  Eigen::Matrix3d coeff = Eigen::Matrix3d::Zero();
+  coeff(2, 2) = 1.0;
+  coeff(0, 1) = 0.1;
+  coeff(1, 0) = 0.1;
+  coeff(0, 2) = 0.2;
+  coeff(2, 0) = 0.2;
+  coeff(0, 0) = 0.3;
+  RunPolynomialInterpolationTest(coeff);
+}
+
+TEST_F(BiCubicInterpolatorTest, Degree22Function) {
+  Eigen::Matrix3d coeff = Eigen::Matrix3d::Zero();
+  coeff(2, 2) = 1.0;
+  coeff(0, 1) = 0.1;
+  coeff(1, 0) = 0.1;
+  coeff(0, 2) = 0.2;
+  coeff(2, 0) = 0.2;
+  coeff(0, 0) = 0.3;
+  coeff(0, 1) = -0.4;
+  coeff(1, 0) = -0.4;
+  RunPolynomialInterpolationTest(coeff);
+}
+
+TEST(BiCubicInterpolator, JetEvaluation) {
+  const double values[] = {1.0, 2.0, 2.0, 3.0,
+                           1.0, 2.0, 2.0, 3.0};
+  BiCubicInterpolator interpolator(values, 2, 4);
+  double f, dfdr, dfdc;
+  const double r = 0.5;
+  const double c = 2.5;
+  EXPECT_TRUE(interpolator.Evaluate(r, c, &f, &dfdr, &dfdc));
+
+  // Create a Jet with the same scalar part as x, so that the output
+  // Jet will be evaluate at x.
+  Jet<double, 4> r_jet;
+  r_jet.a = r;
+  r_jet.v(0) = 1.0;
+  r_jet.v(1) = 1.1;
+  r_jet.v(2) = 1.2;
+  r_jet.v(3) = 1.3;
+
+  Jet<double, 4> c_jet;
+  c_jet.a = c;
+  c_jet.v(0) = 2.0;
+  c_jet.v(1) = 3.1;
+  c_jet.v(2) = 4.2;
+  c_jet.v(3) = 5.3;
+
+  Jet<double, 4> f_jet;
+  EXPECT_TRUE(interpolator.Evaluate(r_jet, c_jet, &f_jet));
+  EXPECT_EQ(f_jet.a, f);
+  EXPECT_EQ((f_jet.v - dfdr * r_jet.v - dfdc * c_jet.v).norm(), 0.0);
 }
 
 }  // namespace internal