Skip to content

File plugin.cpp

File List > plugins > rk4_integrator > plugin.cpp

Go to the documentation of this file

// This entire IMU integrator has been ported almost as-is from the original OpenVINS integrator, which
// can be found here: https://github.com/rpng/open_vins/blob/master/ov_msckf/src/state/Propagator.cpp

#include "plugin.hpp"

#include "illixr/runge-kutta.hpp"

#include 
#ifdef __ANDROID__
#    include 
#else
#    include 
#endif
#include 
#include 
#include 

using namespace ILLIXR;
using namespace ILLIXR::data_format;

constexpr duration IMU_SAMPLE_LIFETIME{std::chrono::seconds{5}};

[[maybe_unused]] rk4_integrator::rk4_integrator(const std::string& name, phonebook* pb)
    : plugin{name, pb}
    , switchboard_{phonebook_->lookup_impl<switchboard>()}
    , imu_integrator_input_{switchboard_->get_reader<imu_integrator_input>("imu_integrator_input")}
    , imu_raw_{switchboard_->get_writer<imu_raw_type>("imu_raw")} {
    switchboard_->schedule<imu_type>(id_, "imu", [&](const switchboard::ptr<const imu_type>& datum, size_t) {
        callback(datum);
    });
}

void rk4_integrator::callback(const switchboard::ptr<const imu_type>& datum) {
    imu_vec_.emplace_back(datum->time, datum->angular_v, datum->linear_a);

    clean_imu_vec(datum->time);
    propagate_imu_values(datum->time);

    RAC_ERRNO_MSG("rk4_integrator");
}

// Clean IMU values older than IMU_SAMPLE_LIFETIME seconds
void rk4_integrator::clean_imu_vec(time_point timestamp) {
    auto it0 = imu_vec_.begin();
    while (it0 != imu_vec_.end()) {
        if (timestamp - it0->time < IMU_SAMPLE_LIFETIME) {
            break;
        }
        it0 = imu_vec_.erase(it0);
    }
}

// Timestamp we are propagating the biases to (new IMU reading time)
void rk4_integrator::propagate_imu_values(time_point real_time) {
    auto input_values = imu_integrator_input_.get_ro_nullable();
    if (input_values == nullptr) {
        return;
    }

    if (!has_last_offset_) {
        last_imu_offset_ = input_values->t_offset;
        has_last_offset_ = true;
    }

    proper_quaterniond          curr_quat = {input_values->quat.w(), input_values->quat.x(), input_values->quat.y(),
                                             input_values->quat.z()};
    Eigen::Matrix<double, 3, 1> curr_pos  = input_values->position;
    Eigen::Matrix<double, 3, 1> curr_vel  = input_values->velocity;

    // Uncomment this for some helpful prints
    // total_imu_++;
    // if (input_values->last_cam_integration_time > last_cam_time_) {
    //  cam_count_++;
    //  last_cam_time_ = input_values->last_cam_integration_time;
    //  std::cout << "Num IMUs received since last cam: " << counter_ << " Diff between new cam and latest IMU: "
    //            << timestamp - last_cam_time_ << " Expected IMUs received VS Actual: " << cam_count_*10 << ", " <<
    // total_imu_
    // << std::endl;    counter_ = 0;
    // }
    // counter_++;

    // Get what our IMU-camera offset should be (t_imu = t_cam + calib_dt)
    duration t_off_new = input_values->t_offset;

    // This is the last CAM time
    time_point time0 = input_values->last_cam_integration_time + last_imu_offset_;
    time_point time1 = real_time + t_off_new;

    std::vector<imu_type>       prop_data = select_imu_readings(imu_vec_, time0, time1);
    Eigen::Matrix<double, 3, 1> w_hat;
    Eigen::Matrix<double, 3, 1> a_hat;
    Eigen::Matrix<double, 3, 1> w_hat2;
    Eigen::Matrix<double, 3, 1> a_hat2;

    // Loop through all IMU messages, and use them to move the state forward in time
    // This uses the zero'th order quat, and then constant acceleration discrete
    if (prop_data.size() > 1) {
        for (size_t i = 0; i < prop_data.size() - 1; i++) {
            // Time elapsed over interval
            double dt = duration_to_double(prop_data[i + 1].time - prop_data[i].time);

            // Corrected imu measurements
            w_hat  = prop_data[i].angular_v - input_values->bias_gyro;
            a_hat  = prop_data[i].linear_a - input_values->bias_acc;
            w_hat2 = prop_data[i + 1].angular_v - input_values->bias_gyro;
            a_hat2 = prop_data[i + 1].linear_a - input_values->bias_acc;

            // Compute the new state mean value
            state_plus sp =
                ::ILLIXR::predict_mean_rk4(dt, state_plus(curr_quat, curr_vel, curr_pos), w_hat, a_hat, w_hat2, a_hat2);

            curr_quat = sp.orientation;
            curr_pos  = sp.position;
            curr_vel  = sp.velocity;
        }
    }

    imu_raw_.put(imu_raw_.allocate(w_hat, a_hat, w_hat2, a_hat2, curr_pos, curr_vel, curr_quat, real_time));
}

// Select IMU readings based on timestamp similar to how OpenVINS selects IMU values to propagate
std::vector<imu_type> rk4_integrator::select_imu_readings(const std::vector<imu_type>& imu_data, time_point time_begin,
                                                          time_point time_end) {
    std::vector<imu_type> prop_data;
    if (imu_data.size() < 2) {
        return prop_data;
    }

    for (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 zero dt values
    // This would cause the noise covariance to be Infinity
    for (int i = 0; i < int(prop_data.size()) - 1; i++) {
        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--; // i can be negative, so use type int
        }
    }

    return prop_data;
}

// For when an integration time ever falls inbetween two imu measurements (modeled after OpenVINS)
imu_type rk4_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(rk4_integrator)