Skip to content

File service.cpp

File List > pose_prediction > service.cpp

Go to the documentation of this file

#include "service.hpp"

#include "illixr/runge-kutta.hpp"

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

using namespace ILLIXR;
using namespace ILLIXR::data_format;

pose_prediction_impl::pose_prediction_impl(const phonebook* const pb)
    : switchboard_{pb->lookup_impl<switchboard>()}
    , clock_{pb->lookup_impl<relative_clock>()}
    , slow_pose_{switchboard_->get_reader<pose::head_pose_type>("slow_pose")}
    , imu_raw_{switchboard_->get_reader<imu_raw_type>("imu_raw")}
    , true_pose_{switchboard_->get_reader<pose::head_pose_type>("true_pose")}
    , ground_truth_offset_{switchboard_->get_reader<switchboard::event_wrapper<Eigen::Vector3f>>("ground_truth_offset")}
    , vsync_estimate_{switchboard_->get_reader<switchboard::event_wrapper<time_point>>("vsync_estimate")}
#ifdef __ANDROID__
    , using_lighthouse_{false} {}
#else
    , using_lighthouse_{switchboard_->get_env_bool("ILLIXR_LIGHTHOUSE")} {
}
#endif

    // No parameter get_fast_pose() should just predict to the next vsync
    // However, we don't have vsync estimation yet.
    // So we will predict to `now()`, as a temporary approximation
    pose::fast_head_pose_type pose_prediction_impl::get_fast_pose() const {
    switchboard::ptr<const switchboard::event_wrapper<time_point>> vsync_estimate = vsync_estimate_.get_ro_nullable();

    if (vsync_estimate == nullptr) {
        return get_fast_pose(clock_->now());
    } else {
        return get_fast_pose(vsync_estimate.get()->operator time_point());
    }
}

pose::head_pose_type pose_prediction_impl::get_true_pose() const {
    switchboard::ptr<const pose::head_pose_type>                        pose_ptr   = true_pose_.get_ro_nullable();
    switchboard::ptr<const switchboard::event_wrapper<Eigen::Vector3f>> offset_ptr = ground_truth_offset_.get_ro_nullable();

    pose::head_pose_type offset_pose;

    // Subtract offset if valid pose and offset, otherwise use zero pose.
    // Checking that pose and offset are both valid is safer than just
    // checking one or the other because it assumes nothing about the
    // ordering of writes on the producer's end or about the producer
    // actually writing to both streams.
    if (pose_ptr != nullptr && offset_ptr != nullptr) {
        offset_pose = *pose_ptr;
        offset_pose.position -= **offset_ptr;
    } else {
        offset_pose.sensor_time = clock_->now();
        offset_pose.position    = Eigen::Vector3f{0, 0, 0};
        offset_pose.orientation = Eigen::Quaternionf{1, 0, 0, 0};
    }

    return correct_pose(offset_pose);
}

// future_time: An absolute timepoint in the future
pose::fast_head_pose_type pose_prediction_impl::get_fast_pose(time_point future_timestamp) const {
    switchboard::ptr<const pose::head_pose_type> slow_pose = slow_pose_.get_ro_nullable();
    if (slow_pose == nullptr) {
        // No slow pose, return 0
        return pose::fast_head_pose_type{
            correct_pose(pose::head_pose_type{}),
            clock_->now(),
            future_timestamp,
        };
    }

    switchboard::ptr<const imu_raw_type> imu_raw = imu_raw_.get_ro_nullable();
    if (imu_raw == nullptr) {
        if (!using_lighthouse_)
            spdlog::get("illixr")->debug("[POSEPREDICTION] FAST POSE IS SLOW POSE!");

        // No imu_raw, return slow_pose
        return pose::fast_head_pose_type{
            correct_pose(*slow_pose),
            clock_->now(),
            future_timestamp,
        };
    }

    // slow_pose and imu_raw, do pose prediction

    double     dt      = duration_to_double(future_timestamp - imu_raw->imu_time);
    state_plus state_p = ::ILLIXR::predict_mean_rk4(dt, state_plus(imu_raw->quat, imu_raw->vel, imu_raw->pos), imu_raw->w_hat,
                                                    imu_raw->a_hat, imu_raw->w_hat2, imu_raw->a_hat2);

    // predictor_imu_time is the most recent IMU sample that was used to compute the prediction.
    auto predictor_imu_time = imu_raw->imu_time;

    pose::head_pose_type predicted_pose =
        correct_pose({predictor_imu_time, state_p.position.cast<float>(), state_p.orientation.cast<float>()});

    // Make the first valid fast pose be straight ahead.
    if (first_time_) {
        std::unique_lock lock{offset_mutex_};
        // check again, now that we have mutual exclusion
        if (first_time_) {
            first_time_ = false;
            offset_     = predicted_pose.orientation.inverse();
        }
    }

    // Several timestamps are logged:
    //       - the prediction compute time (time when this prediction was computed, i.e., now)
    //       - the prediction target (the time that was requested for this pose.)
    return pose::fast_head_pose_type{predicted_pose, clock_->now(), future_timestamp};
}

void pose_prediction_impl::set_offset(const Eigen::Quaternionf& raw_o_times_offset) {
    std::unique_lock   lock{offset_mutex_};
    Eigen::Quaternionf raw_o = raw_o_times_offset * offset_.inverse();
    offset_                  = raw_o.inverse();
    /*
      Now, `raw_o` is maps to the identity quaternion.
      Proof:
      apply_offset(raw_o)
          = raw_o * offset_
          = raw_o * raw_o.inverse()
          = Identity.
     */
}

Eigen::Quaternionf pose_prediction_impl::apply_offset(const Eigen::Quaternionf& orientation) const {
    std::shared_lock lock{offset_mutex_};
    return orientation * offset_;
}

bool pose_prediction_impl::fast_pose_reliable() const {
    if (using_lighthouse_)
        return true;

    return slow_pose_.get_ro_nullable() && imu_raw_.get_ro_nullable();
    /*
      SLAM takes some time to initialize, so initially fast_pose
      is unreliable.

  In such cases, we might return a fast_pose based only on the
  IMU data (currently, we just return a zero-pose)., and mark
  it as "unreliable"

  This way, there always a pose coming out of pose_prediction,
  representing our best guess at that time, and we indicate
  how reliable that guess is here.

 */
}

bool pose_prediction_impl::true_pose_reliable() const {
    // return true_pose_.valid();
    /*
      We do not have a "ground truth" available in all cases, such
      as when reading live data.
     */
    return bool(true_pose_.get_ro_nullable());
}

Eigen::Quaternionf pose_prediction_impl::get_offset() {
    return offset_;
}

// Correct the orientation of the pose due to the lopsided IMU in the
// current Dataset we are using (EuRoC)
pose::head_pose_type pose_prediction_impl::correct_pose(const pose::head_pose_type& pose) const {
    if (using_lighthouse_) // The lighthouse plugin should already apply the correct pose.
        return pose;

    pose::head_pose_type swapped_pose = pose;

    // Make any changes to the axes direction below
    // This is a mapping between the coordinate system of the current
    // SLAM (OpenVINS) we are using and the OpenGL system.
    swapped_pose.position.x() = -pose.position.y();
    swapped_pose.position.y() = pose.position.z();
    swapped_pose.position.z() = -pose.position.x();
    // swapped_pose.linear_velocity.x() = -pose.linear_velocity.y();
    // swapped_pose.linear_velocity.y() = pose.linear_velocity.z();
    // swapped_pose.linear_velocity.z() = -pose.linear_velocity.x();
    // swapped_pose.angular_velocity = pose.angular_velocity;

    // Make any changes to orientation of the output below
    // For the dataset were currently using (EuRoC), the output orientation acts as though
    // the "top of the head" is the forward direction, and the "eye direction" is the up direction.
    Eigen::Quaternionf raw_o(pose.orientation.w(), -pose.orientation.y(), pose.orientation.z(), -pose.orientation.x());

    swapped_pose.orientation = apply_offset(raw_o);
    swapped_pose.sensor_time = pose.sensor_time;

    return swapped_pose;
}

class pose_prediction_plugin : public plugin {
public:
    [[maybe_unused]] pose_prediction_plugin(const std::string& name, phonebook* pb)
        : plugin{name, pb} {
        pb->register_impl<pose_prediction>(
            std::static_pointer_cast<pose_prediction>(std::make_shared<pose_prediction_impl>(pb)));
    }
};

PLUGIN_MAIN(pose_prediction_plugin)