Calico
A visual-inertial calibration library designed for rapid problem construction and debugging.
bspline.hpp
1 #ifndef CALICO_BSPLINE_HPP_
2 #define CALICO_BSPLINE_HPP_
3 #include <algorithm>
4 
5 #include "calico/statusor_macros.h"
6 #include "ceres/problem.h"
7 
8 namespace calico {
9 
10 
11 template <int N, typename T>
12 int BSpline<N, T>::AddParametersToProblem(ceres::Problem& problem) {
13  int num_parameters_added = 0;
14  for (Eigen::Vector<T, N>& control_point : control_points_) {
15  problem.AddParameterBlock(control_point.data(), control_point.size());
16  num_parameters_added += control_point.size();
17  }
18  if (!control_points_enabled_) {
19  for (Eigen::Vector<T, N>& control_point : control_points_) {
20  problem.SetParameterBlockConstant(control_point.data());
21  }
22  }
23  return num_parameters_added;
24 }
25 
26 template <int N, typename T>
27 void BSpline<N, T>::EnableControlPointsEstimation(bool enable) {
28  control_points_enabled_ = enable;
29 }
30 
31 template <int N, typename T>
32 absl::Status BSpline<N, T>::FitToData(
33  const std::vector<T>& time,
34  const std::vector<Eigen::Vector<T,N>>& data, int spline_order,
35  T knot_frequency) {
36  RETURN_IF_ERROR(
37  CheckDataForSplineFit(time, data, spline_order, knot_frequency));
38  time_ = time;
39  data_ = data;
40  spline_order_ = spline_order;
41  knot_frequency_ = knot_frequency;
42  // Derived information about the spline.
43  spline_degree_ = spline_order_ - 1;
44  ComputeKnotVector();
45  ComputeBasisMatrices();
46  // Fit the spline.
47  FitSpline();
48  return absl::OkStatus();
49 }
50 
51 template<int N, typename T>
52 Eigen::Vector<T, N> BSpline<N, T>::Evaluate(
53  const Eigen::Ref<const Eigen::MatrixX<T>>& control_points_set, T knot0,
54  T knot1, const Eigen::MatrixX<T>& basis_matrix, T stamp,
55  int derivative) {
56  // Compute u.
57  const T dt = knot1 - knot0;
58  const T dt_inv = static_cast<T>(1.0) / dt;
59  const T u = (stamp - knot0) * dt_inv;
60  // Derivative of u with respect to t raised to n'th derivative power per
61  // chain rule.
62  T dnu_dtn = static_cast<T>(1.0);
63  for (int j = 0; j < derivative; ++j) {
64  dnu_dtn *= dt_inv;
65  }
66  // Construct U vector.
67  Eigen::Matrix<T, 1, Eigen::Dynamic> derivative_coeffs(
68  control_points_set.rows());
69  derivative_coeffs.setOnes();
70  derivative_coeffs.head(derivative).setZero();
71  Eigen::Matrix<T, 1, Eigen::Dynamic> U(control_points_set.rows());
72  U.setOnes();
73  for (int i = derivative; i < derivative_coeffs.size(); ++i) {
74  T coeff = static_cast<T>(1.0);
75  for (int j = i - derivative; j < i; ++j) {
76  coeff *= (j + 1);
77  }
78  derivative_coeffs(i) = coeff;
79  U(i) = (i > derivative) ? (u * U(i-1)) : U(i);
80  }
81  U = U.array() * derivative_coeffs.array() * dnu_dtn;
82  // Evaluate spline.
83  return (U * basis_matrix * control_points_set).transpose();
84 }
85 
86 template <int N, typename T>
87 absl::StatusOr<std::vector<Eigen::Vector<T, N>>> BSpline<N, T>::Interpolate(
88  const std::vector<T>& times, int derivative) const {
89  if (derivative < 0 || derivative > spline_degree_) {
90  return absl::InvalidArgumentError("Invalid derivative for interpolation.");
91  }
92  for (const T& t : times) {
93  if (t < valid_knots_.front() || t > valid_knots_.back()) {
94  return absl::InvalidArgumentError(absl::StrCat(
95  "Cannot interpolate ", t, ". Value is not within valid knots."));
96  }
97  }
98  int num_interp = times.size();
99  std::vector<Eigen::Vector<T, N>> y(num_interp);
100  for (int i = 0; i < num_interp; ++i) {
101  const int spline_idx = GetSplineIndex(times.at(i));
102  const int knot_idx = GetKnotIndexFromSplineIndex(spline_idx);
103  const Eigen::MatrixX<T> UM = GetSplineBasis(spline_idx, times.at(i), derivative);
104  Eigen::MatrixX<T> control_points(spline_order_, N);
105  for (int j = 0; j < spline_order_; ++j) {
106  control_points.row(j) = control_points_.at(spline_idx + j).transpose();
107  }
108  y[i] = Evaluate(control_points, knots_[knot_idx], knots_[knot_idx + 1],
109  Mi_[spline_idx], times[i], derivative);
110  }
111  return y;
112 }
113 
114 template <int N, typename T>
115 Eigen::MatrixX<T> BSpline<N, T>::GetSplineBasis(
116  int spline_idx, T stamp, int derivative) const {
117  // Compute u.
118  const int knot_idx = GetKnotIndexFromSplineIndex(spline_idx);
119  const T& knot0 = knots_.at(knot_idx);
120  const T& knot1 = knots_.at(knot_idx + 1);
121  const T dt = knot1 - knot0;
122  const T dt_inv = static_cast<T>(1.0) / dt;
123  const T u = (stamp - knot0) * dt_inv;
124  // Derivative of u with respect to t raised to n'th derivative power per
125  // chain rule.
126  T dnu_dtn = static_cast<T>(1.0);
127  for (int j = 0; j < derivative; ++j) {
128  dnu_dtn *= dt_inv;
129  }
130  // Construct U vector.
131  Eigen::Matrix<T, 1, Eigen::Dynamic> derivative_coeffs(spline_order_);
132  derivative_coeffs.setOnes();
133  derivative_coeffs.head(derivative).setZero();
134  Eigen::Matrix<T, 1, Eigen::Dynamic> U(spline_order_);
135  U.setOnes();
136  for (int i = derivative; i < derivative_coeffs.size(); ++i) {
137  T coeff = static_cast<T>(1.0);
138  for (int j = i - derivative; j < i; ++j) {
139  coeff *= (j + 1);
140  }
141  derivative_coeffs(i) = coeff;
142  U(i) = (i > derivative) ? (u * U(i-1)) : U(i);
143  }
144  U = U.array() * derivative_coeffs.array() * dnu_dtn;
145  const Eigen::MatrixX<T>& basis_matrix = Mi_.at(spline_idx);
146  return U * basis_matrix;
147 }
148 
149 
150 template <int N, typename T>
151 int BSpline<N, T>::GetSplineIndex(T query_time) const {
152  int spline_idx = -1;
153  if (query_time == valid_knots_.back()) {
154  spline_idx = valid_knots_.size() - 2;
155  }
156  else if (query_time < valid_knots_.back()) {
157  auto lower =
158  std::upper_bound(valid_knots_.begin(), valid_knots_.end(), query_time);
159  spline_idx = lower - valid_knots_.begin() - 1;
160  }
161  return spline_idx;
162 }
163 
164 template <int N, typename T>
165 int BSpline<N, T>::GetSplineOrder() const {
166  return spline_order_;
167 }
168 
169 template <int N, typename T>
170 int BSpline<N, T>::GetKnotIndexFromSplineIndex(
171  int control_point_index) const {
172  return control_point_index + spline_degree_;
173 }
174 
175 template <int N, typename T>
176 void BSpline<N, T>::ComputeKnotVector() {
177  const T duration = time_.back() - time_.front();
178  const T dt = 1.0 / knot_frequency_;
179  const int num_valid_knots = 1 + std::ceil(duration * knot_frequency_);
180  const int num_knots = num_valid_knots + 2 * spline_degree_;
181 
182  knots_.resize(num_knots);
183  valid_knots_.resize(num_valid_knots);
184  for (int i = -spline_degree_; i < num_knots - spline_degree_; ++i) {
185  const T knot_value = time_.front() + dt * i;
186  knots_[i + spline_degree_] = knot_value;
187  int valid_knot_index = i;
188  if (valid_knot_index > -1 && valid_knot_index < num_valid_knots) {
189  valid_knots_[valid_knot_index] = knot_value;
190  }
191  }
192 }
193 
194 template <int N, typename T>
195 void BSpline<N, T>::ComputeBasisMatrices() {
196  const int num_valid_segments = valid_knots_.size() - 1;
197  Mi_.resize(num_valid_segments);
198  for (int i = 0; i < num_valid_segments; ++i) {
199  Mi_[i] = M(spline_order_, i + spline_degree_);
200  }
201 }
202 
203 template<int N, typename T>
204 Eigen::MatrixX<T> BSpline<N, T>::M(int k, int i) {
205  Eigen::MatrixX<T> M_k;
206  if (k == 1) {
207  M_k.resize(k, k);
208  M_k(0, 0) = k;
209  return M_k;
210  }
211 
212  Eigen::MatrixX<T> M_km1 = M(k - 1, i);
213  const int num_rows = M_km1.rows();
214  const int num_cols = M_km1.cols();
215  Eigen::MatrixX<T> M1(num_rows + 1, num_cols), M2(num_rows + 1, num_cols);
216  M1.setZero();
217  M2.setZero();
218  M1.block(0, 0, num_rows, num_cols) = M_km1;
219  M2.block(1, 0, num_rows, num_cols) = M_km1;
220 
221  Eigen::MatrixX<T> A(k - 1,k);
222  Eigen::MatrixX<T> B(k - 1,k);
223  A.setZero();
224  B.setZero();
225  for (int index = 0; index < k - 1; ++index) {
226  int j = i - k + 2 + index;
227  const T d0 = d_0(k, i, j);
228  const T d1 = d_1(k, i, j);
229  A(index, index) = 1.0 - d0;
230  A(index, index + 1) = d0;
231  B(index, index) = -d1;
232  B(index, index + 1) = d1;
233  }
234  M_k = M1 * A + M2 * B;
235  return M_k;
236 }
237 
238 template <int N, typename T>
239 T BSpline<N, T>::d_0(int k, int i, int j) {
240  const T den = knots_[j + k - 1] - knots_[j];
241  if (den <= 0.0) {
242  return 0.0;
243  }
244  const T num = knots_[i] - knots_[j];
245  return num / den;
246 }
247 
248 template <int N, typename T>
249 T BSpline<N, T>::d_1(int k, int i, int j) {
250  const T den = knots_[j + k - 1] - knots_[j];
251  if (den <= 0.0) {
252  return 0.0;
253  }
254  const T num = knots_[i+1] - knots_[i];
255  return num / den;
256 }
257 
258 template <int N, typename T>
259 void BSpline<N, T>::FitSpline() {
260  const int num_data = time_.size();
261  int num_control_points = knots_.size() - spline_order_;
262  Eigen::MatrixX<T> X(num_data, num_control_points);
263  X.setZero();
264  for (int j = 0; j < num_data; j++) {
265  const T t = time_[j];
266  int spline_index = -1;
267 
268  if (t == valid_knots_.back()) {
269  spline_index = Mi_.size() - 1;
270  }
271  else if (t == valid_knots_.front()) {
272  spline_index = 0;
273  }
274  else if (t < valid_knots_.back()) {
275  auto lower = std::upper_bound(valid_knots_.begin(), valid_knots_.end(), t);
276  spline_index = lower - valid_knots_.begin() - 1;
277  }
278  const int knot_index = GetKnotIndexFromSplineIndex(spline_index);
279  // Grab the intermediate knot values
280  const T ti = knots_[knot_index];
281  const T tii = knots_[knot_index + 1];
282  // Construct U vector
283  Eigen::VectorX<T> U(spline_order_);
284  U.setOnes();
285  const T u = (t - ti) / (tii - ti);
286  for (int i = 1; i < spline_order_; ++i) {
287  U(i) = u * U(i - 1);
288  }
289  // Construct U*M
290  Eigen::MatrixX<T> M = Mi_[spline_index];
291  Eigen::MatrixX<T> UM = U.transpose() * M;
292  X.block(j, spline_index, UM.rows(), UM.cols()) = UM;
293  }
294 
295  Eigen::Matrix<T,Eigen::Dynamic,N> data(num_data, N);
296  for (int i = 0; i < num_data; ++i) {
297  data.row(i) = data_[i].transpose();
298  }
299  // TODO(yangjames): X is highly sparse, and X'X is banded, symmetric, positive
300  // definite. Figure out how to sparsify the solve step for the control points
301  // as this gets very expensive with more knots.
302  const Eigen::MatrixX<T> XtX = X.transpose() * X;
303  const Eigen::MatrixX<T> Xtd = X.transpose() * data;
304  const Eigen::MatrixX<T> control_points =
305  XtX.colPivHouseholderQr().solve(Xtd);
306  for (int i = 0; i < control_points.rows(); ++i) {
307  control_points_.push_back(control_points.row(i).transpose());
308  }
309 }
310 
311 template <int N, typename T>
312 absl::Status BSpline<N, T>::CheckDataForSplineFit(
313  const std::vector<T>& time,
314  const std::vector<Eigen::Vector<T, N>>& data,
315  int spline_order, T knot_frequency) {
316  // Assert that data and time are properly sized
317  if (!time.size()) {
318  return absl::InvalidArgumentError("Attempted to fit data on empty time vector.");
319  }
320  if (data.empty()) {
321  return absl::InvalidArgumentError("Attempted to fit on empty data.");
322  }
323  if (time.size() != data.size()) {
324  return absl::InvalidArgumentError("Data and time vectors are not the same size.");
325  }
326  // Check that time is strictly increasing
327  auto iter = std::adjacent_find(time_.begin(), time_.end(), std::greater<T>());
328  if (iter != time_.end()) {
329  return absl::InvalidArgumentError("Time vector is not monotonically increasing.");
330  }
331  // Check that spline order is greater than 2
332  if (spline_order < 2) {
333  return absl::InvalidArgumentError(absl::StrCat(
334  "Spline order must be greater than 2. Got ", spline_order));
335  }
336  // Check that knot frequency is greater than 0
337  if (knot_frequency <= 0) {
338  return absl::InvalidArgumentError("Knot frequency must be greater than 0.");
339  }
340  return absl::OkStatus();
341 }
342 
343 } // namespace calico
344 #endif // CALICO_BSPLINE_HPP_
Primary calico namespace.
Definition: __init__.py:1