1 #ifndef CALICO_BSPLINE_HPP_
2 #define CALICO_BSPLINE_HPP_
5 #include "calico/statusor_macros.h"
6 #include "ceres/problem.h"
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();
18 if (!control_points_enabled_) {
19 for (Eigen::Vector<T, N>& control_point : control_points_) {
20 problem.SetParameterBlockConstant(control_point.data());
23 return num_parameters_added;
26 template <
int N,
typename T>
27 void BSpline<N, T>::EnableControlPointsEstimation(
bool enable) {
28 control_points_enabled_ = enable;
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,
37 CheckDataForSplineFit(time, data, spline_order, knot_frequency));
40 spline_order_ = spline_order;
41 knot_frequency_ = knot_frequency;
43 spline_degree_ = spline_order_ - 1;
45 ComputeBasisMatrices();
48 return absl::OkStatus();
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,
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;
62 T dnu_dtn =
static_cast<T
>(1.0);
63 for (
int j = 0; j < derivative; ++j) {
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());
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) {
78 derivative_coeffs(i) = coeff;
79 U(i) = (i > derivative) ? (u * U(i-1)) : U(i);
81 U = U.array() * derivative_coeffs.array() * dnu_dtn;
83 return (U * basis_matrix * control_points_set).transpose();
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.");
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."));
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();
108 y[i] = Evaluate(control_points, knots_[knot_idx], knots_[knot_idx + 1],
109 Mi_[spline_idx], times[i], derivative);
114 template <
int N,
typename T>
115 Eigen::MatrixX<T> BSpline<N, T>::GetSplineBasis(
116 int spline_idx, T stamp,
int derivative)
const {
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;
126 T dnu_dtn =
static_cast<T
>(1.0);
127 for (
int j = 0; j < derivative; ++j) {
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_);
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) {
141 derivative_coeffs(i) = coeff;
142 U(i) = (i > derivative) ? (u * U(i-1)) : U(i);
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;
150 template <
int N,
typename T>
151 int BSpline<N, T>::GetSplineIndex(T query_time)
const {
153 if (query_time == valid_knots_.back()) {
154 spline_idx = valid_knots_.size() - 2;
156 else if (query_time < valid_knots_.back()) {
158 std::upper_bound(valid_knots_.begin(), valid_knots_.end(), query_time);
159 spline_idx = lower - valid_knots_.begin() - 1;
164 template <
int N,
typename T>
165 int BSpline<N, T>::GetSplineOrder()
const {
166 return spline_order_;
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_;
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_;
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;
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_);
203 template<
int N,
typename T>
204 Eigen::MatrixX<T> BSpline<N, T>::M(
int k,
int i) {
205 Eigen::MatrixX<T> M_k;
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);
218 M1.block(0, 0, num_rows, num_cols) = M_km1;
219 M2.block(1, 0, num_rows, num_cols) = M_km1;
221 Eigen::MatrixX<T> A(k - 1,k);
222 Eigen::MatrixX<T> B(k - 1,k);
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;
234 M_k = M1 * A + M2 * B;
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];
244 const T num = knots_[i] - knots_[j];
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];
254 const T num = knots_[i+1] - knots_[i];
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);
264 for (
int j = 0; j < num_data; j++) {
265 const T t = time_[j];
266 int spline_index = -1;
268 if (t == valid_knots_.back()) {
269 spline_index = Mi_.size() - 1;
271 else if (t == valid_knots_.front()) {
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;
278 const int knot_index = GetKnotIndexFromSplineIndex(spline_index);
280 const T ti = knots_[knot_index];
281 const T tii = knots_[knot_index + 1];
283 Eigen::VectorX<T> U(spline_order_);
285 const T u = (t - ti) / (tii - ti);
286 for (
int i = 1; i < spline_order_; ++i) {
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;
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();
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());
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) {
318 return absl::InvalidArgumentError(
"Attempted to fit data on empty time vector.");
321 return absl::InvalidArgumentError(
"Attempted to fit on empty data.");
323 if (time.size() != data.size()) {
324 return absl::InvalidArgumentError(
"Data and time vectors are not the same size.");
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.");
332 if (spline_order < 2) {
333 return absl::InvalidArgumentError(absl::StrCat(
334 "Spline order must be greater than 2. Got ", spline_order));
337 if (knot_frequency <= 0) {
338 return absl::InvalidArgumentError(
"Knot frequency must be greater than 0.");
340 return absl::OkStatus();
Primary calico namespace.
Definition: __init__.py:1