File plugin.cpp
File List > gtsam_integrator > plugin.cpp
Go to the documentation of this file
#include "plugin.hpp"
#include
#include
#include
#include
#include
#include
#include
using namespace ILLIXR;
using namespace ILLIXR::data_format;
// IMU sample time to live in seconds
constexpr duration IMU_TTL{std::chrono::seconds{5}};
using ImuBias = gtsam::imuBias::ConstantBias;
[[maybe_unused]] gtsam_integrator::gtsam_integrator(const std::string& name, phonebook* pb)
: plugin{name, pb}
, switchboard_{phonebook_->lookup_impl<switchboard>()}
, clock_{phonebook_->lookup_impl<relative_clock>()}
, imu_integrator_input_{switchboard_->get_reader<imu_integrator_input>("imu_integrator_input")}
, imu_raw_{switchboard_->get_writer<imu_raw_type>("imu_raw")} {
spdlogger(switchboard_->get_env_char("GTSAM_INTEGRATOR_LOG_LEVEL"));
switchboard_->schedule<imu_type>(id_, "imu", [&](const switchboard::ptr<const imu_type>& datum, size_t) {
callback(datum);
});
const double frequency = 200.;
const double min_cutoff = 10.;
const double beta = 1.;
const double d_cutoff = 10.;
for (int i = 0; i < 8; ++i) {
filters_.emplace_back(frequency, Eigen::Array<double, 3, 1>{min_cutoff, min_cutoff, min_cutoff},
Eigen::Array<double, 3, 1>{beta, beta, beta},
Eigen::Array<double, 3, 1>{d_cutoff, d_cutoff, d_cutoff}, Eigen::Array<double, 3, 1>::Zero(),
Eigen::Array<double, 3, 1>::Ones(), [](auto& in) {
return in.abs();
});
}
}
void gtsam_integrator::callback(const switchboard::ptr<const imu_type>& datum) {
imu_vector_.emplace_back(datum->time, datum->angular_v.cast<double>(), datum->linear_a.cast<double>());
clean_imu_vec(datum->time);
propagate_imu_values(datum->time);
RAC_ERRNO_MSG("gtsam_integrator");
}
gtsam_integrator::pim_object::pim_object(const imu_int_t& imu_int_input)
: imu_bias_{imu_int_input.bias_acc, imu_int_input.bias_gyro}
, pim_{nullptr} {
pim_t::Params params{imu_int_input.params.n_gravity};
params.setGyroscopeCovariance(std::pow(imu_int_input.params.gyro_noise, 2.0) * Eigen::Matrix3d::Identity());
params.setAccelerometerCovariance(std::pow(imu_int_input.params.acc_noise, 2.0) * Eigen::Matrix3d::Identity());
params.setIntegrationCovariance(std::pow(imu_int_input.params.imu_integration_sigma, 2.0) * Eigen::Matrix3d::Identity());
params.setBiasAccCovariance(std::pow(imu_int_input.params.acc_walk, 2.0) * Eigen::Matrix3d::Identity());
params.setBiasOmegaCovariance(std::pow(imu_int_input.params.gyro_walk, 2.0) * Eigen::Matrix3d::Identity());
pim_ = new pim_t{std::make_shared<pim_t::Params>(std::move(params)), imu_bias_};
reset_integration_and_set_bias(imu_int_input);
}
gtsam_integrator::pim_object::~pim_object() {
assert(pim_ != nullptr && "pim_ should not be null");
}
void gtsam_integrator::pim_object::reset_integration_and_set_bias(const imu_int_t& imu_int_input) noexcept {
assert(pim_ != nullptr && "pim_ should not be null");
imu_bias_ = bias_t{imu_int_input.bias_acc, imu_int_input.bias_gyro};
pim_->resetIntegrationAndSetBias(imu_bias_);
nav_state_lkf_ = nav_t{gtsam::Pose3{gtsam::Rot3{imu_int_input.quat}, imu_int_input.position}, imu_int_input.velocity};
}
void gtsam_integrator::pim_object::integrate_measurement(const imu_t& imu_input, const imu_t& imu_input_next) noexcept {
assert(pim_ != nullptr && "pim_ shuold not be null");
const gtsam::Vector3 measured_acc{imu_input.linear_a};
const gtsam::Vector3 measured_omega{imu_input.angular_v};
duration delta_t = imu_input_next.time - imu_input.time;
pim_->integrateMeasurement(measured_acc, measured_omega, duration_to_double(delta_t));
}
[[nodiscard]] bias_t gtsam_integrator::pim_object::bias_hat() const noexcept {
assert(pim_ != nullptr && "pim_ shuold not be null");
return pim_->biasHat();
}
[[nodiscard]] nav_t gtsam_integrator::pim_object::predict() const noexcept {
assert(pim_ != nullptr && "pim_ should not be null");
return pim_->predict(nav_state_lkf_, imu_bias_);
}
// Remove IMU values older than 'IMU_TTL' from the imu buffer
void gtsam_integrator::clean_imu_vec(time_point timestamp) {
auto imu_iterator = imu_vector_.begin();
// Since the vector is ordered oldest to latest, keep deleting until you
// hit a value less than 'IMU_TTL' seconds old
while (imu_iterator != imu_vector_.end()) {
if (timestamp - imu_iterator->time < IMU_TTL) {
break;
}
imu_iterator = imu_vector_.erase(imu_iterator);
}
}
// Timestamp we are propagating the biases to (new IMU reading time)
void gtsam_integrator::propagate_imu_values(time_point real_time) {
auto input_values = imu_integrator_input_.get_ro_nullable();
if (input_values == nullptr) {
return;
}
#ifndef NDEBUG
if (input_values->last_cam_integration_time > last_cam_time_) {
spdlog::get(name_)->debug("New slow pose has arrived!");
last_cam_time_ = input_values->last_cam_integration_time;
}
#endif
if (pim_obj_ == nullptr) {
pim_obj_ = std::make_unique<pim_object>(*input_values);
last_imu_offset_ = input_values->t_offset;
} else {
pim_obj_->reset_integration_and_set_bias(*input_values);
}
assert(pim_obj_ != nullptr && "pim_obj_ should not be null");
// TODO last_imu_offset_ is 0, t_offset only take effects when it's negative.
// However, why would we want to integrate to a past time point rather than the current time point?
time_point time_begin = input_values->last_cam_integration_time + last_imu_offset_;
time_point time_end = real_time;
const std::vector<imu_type> prop_data = select_imu_readings(imu_vector_, time_begin, time_end);
if (prop_data.size() < 2) {
return;
}
ImuBias prev_bias = pim_obj_->bias_hat();
ImuBias bias = pim_obj_->bias_hat();
spdlog::get(name_)->debug("Integrating over {} IMU samples", prop_data.size());
for (std::size_t i = 0; i < prop_data.size() - 1; i++) {
pim_obj_->integrate_measurement(prop_data[i], prop_data[i + 1]);
prev_bias = bias;
bias = pim_obj_->bias_hat();
}
gtsam::NavState navstate_k = pim_obj_->predict();
gtsam::Pose3 out_pose = navstate_k.pose();
spdlog::get(name_)->debug("Base Position (x, y, z) = {}, {}, {}", input_values->position(0), input_values->position(1),
input_values->position(2));
spdlog::get(name_)->debug("New Position (x, y, z) = {}, {}, {}", out_pose.x(), out_pose.y(), out_pose.z());
auto seconds_since_epoch = std::chrono::duration<double>(real_time.time_since_epoch()).count();
auto original_quaternion = out_pose.rotation().toQuaternion();
Eigen::Matrix<double, 3, 1> rotation_angles = original_quaternion.toRotationMatrix().eulerAngles(0, 1, 2).cast<double>();
Eigen::Matrix<double, 3, 1> filtered_sins = filters_[6](rotation_angles.array().sin(), seconds_since_epoch);
Eigen::Matrix<double, 3, 1> filtered_cosines = filters_[7](rotation_angles.array().cos(), seconds_since_epoch);
Eigen::Matrix<double, 3, 1> filtered_angles{atan2(filtered_sins[0], filtered_cosines[0]),
atan2(filtered_sins[1], filtered_cosines[1]),
atan2(filtered_sins[2], filtered_cosines[2])};
if (has_prev_ &&
(abs(rotation_angles[0] - prev_euler_angles_[0]) > M_PI / 2 ||
abs(rotation_angles[1] - prev_euler_angles_[1]) > M_PI / 2 ||
abs(rotation_angles[2] - prev_euler_angles_[2]) > M_PI / 2)) {
filters_[6].clear();
filters_[7].clear();
filtered_sins = filters_[6](rotation_angles.array().sin(), seconds_since_epoch);
filtered_cosines = filters_[7](rotation_angles.array().cos(), seconds_since_epoch);
filtered_angles = {atan2(filtered_sins[0], filtered_cosines[0]), atan2(filtered_sins[1], filtered_cosines[1]),
atan2(filtered_sins[2], filtered_cosines[2])};
} else {
has_prev_ = true;
}
prev_euler_angles_ = std::move(rotation_angles);
[[maybe_unused]] auto new_quaternion = Eigen::AngleAxisd(filtered_angles(0, 0), Eigen::Vector3d::UnitX()) *
Eigen::AngleAxisd(filtered_angles(1, 0), Eigen::Vector3d::UnitY()) *
Eigen::AngleAxisd(filtered_angles(2, 0), Eigen::Vector3d::UnitZ());
Eigen::MatrixWrapper<Eigen::Array<double, 3, 1, 0, 3, 1>> filtered_pos{
filters_[4](out_pose.translation().array(), seconds_since_epoch).matrix()};
imu_raw_.put(imu_raw_.allocate<imu_raw_type>(
imu_raw_type{prev_bias.gyroscope(), prev_bias.accelerometer(), bias.gyroscope(), bias.accelerometer(),
filtered_pos,
filters_[5](navstate_k.velocity().array(), seconds_since_epoch),
new_quaternion,
real_time}));
}
// Select IMU readings based on timestamp similar to how OpenVINS selects IMU values to propagate
std::vector<imu_type> gtsam_integrator::select_imu_readings(const std::vector<imu_type>& imu_data, const time_point time_begin,
const time_point time_end) {
std::vector<imu_type> prop_data;
if (imu_data.size() < 2) {
return prop_data;
}
for (std::size_t i = 0; i < imu_data.size() - 1; i++) {
// If time_begin comes inbetween two IMUs (A and B), interpolate A forward to time_begin
if (imu_data[i + 1].time > time_begin && imu_data[i].time < time_begin) {
imu_type data = interpolate_imu(imu_data[i], imu_data[i + 1], time_begin);
prop_data.push_back(data);
continue;
}
// IMU is within time_begin and time_end
if (imu_data[i].time >= time_begin && imu_data[i + 1].time <= time_end) {
prop_data.push_back(imu_data[i]);
continue;
}
// IMU is past time_end
if (imu_data[i + 1].time > time_end) {
imu_type data = interpolate_imu(imu_data[i], imu_data[i + 1], time_end);
prop_data.push_back(data);
break;
}
}
// Loop through and ensure we do not have a zero dt values
// This would cause the noise covariance to be Infinity
for (int i = 0; i < int(prop_data.size()) - 1; i++) {
// I need prop_data.size() - 1 to be signed, because it might equal -1.
if (std::chrono::abs(prop_data[i + 1].time - prop_data[i].time) < std::chrono::nanoseconds{1}) {
prop_data.erase(prop_data.begin() + i);
i--;
}
}
return prop_data;
}
// For when an integration time ever falls inbetween two imu measurements (modeled after OpenVINS)
imu_type gtsam_integrator::interpolate_imu(const imu_type& imu_1, const imu_type& imu_2, time_point timestamp) {
double lambda = duration_to_double(timestamp - imu_1.time) / duration_to_double(imu_2.time - imu_1.time);
return imu_type{timestamp, (1 - lambda) * imu_1.linear_a + lambda * imu_2.linear_a,
(1 - lambda) * imu_1.angular_v + lambda * imu_2.angular_v};
}
PLUGIN_MAIN(gtsam_integrator)