diff --git a/CMakeLists.txt b/CMakeLists.txt index 42622856d59417ed225d4950f9bb0a87f62e5ae7..b9f3ad7358ad2dad416d6cf0c8923f23d5a90ece 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -227,6 +227,7 @@ SET(HDRS_FACTOR include/core/factor/factor_relative_pose_2d.h include/core/factor/factor_relative_pose_2d_with_extrinsics.h include/core/factor/factor_relative_pose_3d.h + include/core/factor/factor_pose_3d_with_extrinsics.h ) SET(HDRS_FEATURE include/core/feature/feature_base.h @@ -250,6 +251,7 @@ SET(HDRS_PROCESSOR include/core/processor/processor_motion.h include/core/processor/processor_odom_2d.h include/core/processor/processor_odom_3d.h + include/core/processor/processor_pose.h include/core/processor/processor_tracker.h include/core/processor/processor_tracker_feature.h include/core/processor/processor_tracker_landmark.h @@ -261,6 +263,7 @@ SET(HDRS_SENSOR include/core/sensor/factory_sensor.h include/core/sensor/sensor_odom_2d.h include/core/sensor/sensor_odom_3d.h + include/core/sensor/sensor_pose.h ) SET(HDRS_SOLVER include/core/solver/solver_manager.h @@ -344,6 +347,7 @@ SET(SRCS_PROCESSOR src/processor/processor_motion.cpp src/processor/processor_odom_2d.cpp src/processor/processor_odom_3d.cpp + src/processor/processor_pose.cpp src/processor/processor_tracker.cpp src/processor/processor_tracker_feature.cpp src/processor/processor_tracker_landmark.cpp @@ -354,6 +358,7 @@ SET(SRCS_SENSOR src/sensor/sensor_diff_drive.cpp src/sensor/sensor_odom_2d.cpp src/sensor/sensor_odom_3d.cpp + src/sensor/sensor_pose.cpp ) SET(SRCS_SOLVER src/solver/solver_manager.cpp @@ -367,6 +372,7 @@ SET(SRCS_YAML src/yaml/processor_odom_3d_yaml.cpp src/yaml/sensor_odom_2d_yaml.cpp src/yaml/sensor_odom_3d_yaml.cpp + src/yaml/sensor_pose_yaml.cpp ) #OPTIONALS #optional HDRS and SRCS diff --git a/include/core/capture/capture_pose.h b/include/core/capture/capture_pose.h index 79298fe4d63b03b3c0ad830f4265c106ca30ae74..07db9e0351ddb1e73ea8877c1a31ddc456e7dacd 100644 --- a/include/core/capture/capture_pose.h +++ b/include/core/capture/capture_pose.h @@ -28,6 +28,8 @@ class CapturePose : public CaptureBase ~CapturePose() override; virtual void emplaceFeatureAndFactor(); + Eigen::VectorXd getData(){ return data_;} + Eigen::MatrixXd getDataCovariance(){ return data_covariance_;} }; diff --git a/include/core/factor/factor_pose_3d_with_extrinsics.h b/include/core/factor/factor_pose_3d_with_extrinsics.h new file mode 100644 index 0000000000000000000000000000000000000000..f726291a9a0480dd2edc71d137511c543d4d3886 --- /dev/null +++ b/include/core/factor/factor_pose_3d_with_extrinsics.h @@ -0,0 +1,88 @@ +#ifndef FACTOR_POSE_3D_WITH_EXTRINSICS_THETA_H_ +#define FACTOR_POSE_3D_WITH_EXTRINSICS_THETA_H_ + +//Wolf includes +#include "core/factor/factor_autodiff.h" +#include "core/frame/frame_base.h" +#include "core/math/rotations.h" + +//#include "ceres/jet.h" + +namespace wolf { + +WOLF_PTR_TYPEDEFS(FactorPose3dWithExtrinsics); + +//class +class FactorPose3dWithExtrinsics : public FactorAutodiff<FactorPose3dWithExtrinsics, 6, 3, 4, 3, 4> +{ + public: + FactorPose3dWithExtrinsics(const FeatureBasePtr& _ftr_ptr, + const ProcessorBasePtr& _processor_ptr, + bool _apply_loss_function, + const FactorTopology& _top, + FactorStatus _status = FAC_ACTIVE) : + FactorAutodiff<FactorPose3dWithExtrinsics, 6, 3, 4, 3, 4>("FactorPose3dWithExtrinsics", + _top, + _ftr_ptr, + nullptr, nullptr, nullptr, nullptr, + _processor_ptr, + _apply_loss_function, + _status, + _ftr_ptr->getFrame()->getP(), + _ftr_ptr->getFrame()->getO(), + _ftr_ptr->getCapture()->getSensorP(), + _ftr_ptr->getCapture()->getSensorO()) + { + assert(_ftr_ptr->getCapture()->getSensorP() != nullptr && "No extrinsics found!"); + assert(_ftr_ptr->getCapture()->getSensorO() != nullptr && "No extrinsics found!"); + // + } + + ~FactorPose3dWithExtrinsics() override = default; + + template<typename T> + bool operator ()(const T* const _p, + const T* const _q, + const T* const _sp, + const T* const _sq, + T* _residuals) const; +}; + +template<typename T> +inline bool FactorPose3dWithExtrinsics::operator ()(const T* const _p, + const T* const _q, + const T* const _sp, + const T* const _sq, + T* _residuals) const +{ + // MAPS + Eigen::Map<const Eigen::Matrix<T,3,1> > w_p_wb(_p); + Eigen::Map<const Eigen::Quaternion<T> > w_q_b(_q); + Eigen::Map<const Eigen::Matrix<T,3,1> > b_p_bm(_sp); + Eigen::Map<const Eigen::Quaternion<T> > b_q_m(_sq); + + // measurements + Eigen::Vector3d w_p_wm(getMeasurement().data() + 0); // measurements + Eigen::Quaterniond w_q_m (getMeasurement().data() + 3); // measurements + + // Transformation definitions: + // w_p_wm, w_q_m: world to measurement frame (robot attached mocap frame) + // w_p_wb, w_q_b: world to robot base frame + // b_p_bm, b_q_m: base to measurement frame (extrinsics) + // Error function: + // err = w_T_m |-| (w_T_b*b_T_m) + + Eigen::Matrix<T, 6, 1> err; // error + err.head(3) = w_p_wm - (w_p_wb + w_q_b*b_p_bm); + err.tail(3) = q2v((w_q_b * b_q_m).conjugate() * w_q_m.cast<T>()); + + // Residuals + Eigen::Map<Eigen::Matrix<T,6,1> > res(_residuals); + res = getMeasurementSquareRootInformationUpper().cast<T>() * err; + + return true; +} + +} // namespace wolf + +#endif diff --git a/include/core/processor/processor_base.h b/include/core/processor/processor_base.h index c8945c8036ff54dc2e0fe2243b3ec0919c66eba4..bbb9d109f4ea76919f4c9ddac9c5960eb94de141 100644 --- a/include/core/processor/processor_base.h +++ b/include/core/processor/processor_base.h @@ -549,11 +549,11 @@ T Buffer<T>::selectLastAfter(const TimeStamp& _time_stamp, const double& _time_t return nullptr; // Checking on rbegin() since packs are ordered in time - // Return last pack if is older than time stamp + // Return last pack if is newer than time stamp if (container_.rbegin()->first > _time_stamp) return container_.rbegin()->second; - // Return last pack if despite being newer, it is within the time tolerance + // Return last pack if despite being older, it is within the time tolerance if (simpleCheckTimeTolerance(container_.rbegin()->first, _time_stamp, _time_tolerance)) return container_.rbegin()->second; diff --git a/include/core/processor/processor_pose.h b/include/core/processor/processor_pose.h new file mode 100644 index 0000000000000000000000000000000000000000..f8d4833807714dd5303db309e4f13e4710f24e7f --- /dev/null +++ b/include/core/processor/processor_pose.h @@ -0,0 +1,93 @@ +#ifndef PROCESSOR_POSE_NOMOVE_H +#define PROCESSOR_POSE_NOMOVE_H + +// Wolf +#include "core/sensor/sensor_base.h" +#include "core/processor/processor_base.h" + +#include "core/capture/capture_pose.h" +#include "core/sensor/sensor_pose.h" +#include "core/factor/factor_pose_3d_with_extrinsics.h" + +namespace wolf { +WOLF_STRUCT_PTR_TYPEDEFS(ParamsProcessorPose); + +struct ParamsProcessorPose : public ParamsProcessorBase +{ + ParamsProcessorPose() = default; + ParamsProcessorPose(std::string _unique_name, const ParamsServer& _server): + ParamsProcessorBase(_unique_name, _server) + { + } + ~ParamsProcessorPose() override = default; + std::string print() const override + { + return "\n" + ParamsProcessorBase::print() + "\n"; + } +}; + +WOLF_PTR_TYPEDEFS(ProcessorPose); + +//class +class ProcessorPose : public ProcessorBase{ + public: + ProcessorPose(ParamsProcessorPosePtr _params_pfnomove); + ~ProcessorPose() override = default; + WOLF_PROCESSOR_CREATE(ProcessorPose, ParamsProcessorPose); + + void configure(SensorBasePtr _sensor) override; + + void processCapture(CaptureBasePtr _incoming) override; + void processKeyFrame(FrameBasePtr _keyframe_ptr, const double& _time_tolerance) override; + bool triggerInCapture(CaptureBasePtr) const override; + bool triggerInKeyFrame(FrameBasePtr _keyframe_ptr, const double& _time_tolerance) const override; + bool storeKeyFrame(FrameBasePtr) override; + bool storeCapture(CaptureBasePtr) override; + bool voteForKeyFrame() const override; + + void createFactorIfNecessary(); + + protected: + ParamsProcessorPosePtr params_pfnomove_; +}; + + + +inline bool ProcessorPose::triggerInKeyFrame(FrameBasePtr _keyframe_ptr, + const double& _time_tolerance) const +{ + return true; +} + +inline bool ProcessorPose::triggerInCapture(CaptureBasePtr _capture) const +{ + return true; +} + +inline bool ProcessorPose::storeKeyFrame(FrameBasePtr) +{ + return true; +} + +inline bool ProcessorPose::storeCapture(CaptureBasePtr) +{ + return true; +} + +inline bool ProcessorPose::voteForKeyFrame() const +{ + return false; +} + +} /* namespace wolf */ + +///////////////////////////////////////////////////////// +// IMPLEMENTATION. Put your implementation includes here +///////////////////////////////////////////////////////// + + +namespace wolf{ + +} // namespace wolf + +#endif // PROCESSOR_POSE_NOMOVE_H diff --git a/include/core/sensor/sensor_pose.h b/include/core/sensor/sensor_pose.h new file mode 100644 index 0000000000000000000000000000000000000000..63f16d1e42b1994632478f293bd2d9c0a9ba2fe1 --- /dev/null +++ b/include/core/sensor/sensor_pose.h @@ -0,0 +1,79 @@ +/** + * \file sensor_pose.h + * + * Created on: Feb 18, 2020 + * \author: mfourmy + */ + +#ifndef SRC_SENSOR_POSE_H_ +#define SRC_SENSOR_POSE_H_ + +//wolf includes +#include "core/sensor/sensor_base.h" +#include "core/utils/params_server.h" + +namespace wolf { + +WOLF_STRUCT_PTR_TYPEDEFS(ParamsSensorPose); + +struct ParamsSensorPose : public ParamsSensorBase +{ + double std_p = 1; // m + double std_o = 1; // rad + ParamsSensorPose() + { + //DEFINED FOR COMPATIBILITY PURPOSES. TO BE REMOVED IN THE FUTURE. + } + ParamsSensorPose(std::string _unique_name, const ParamsServer& _server): + ParamsSensorBase(_unique_name, _server) + { + std_p = _server.getParam<double>(prefix + _unique_name + "/std_p"); + std_o = _server.getParam<double>(prefix + _unique_name + "/std_o"); + } + std::string print() const override + { + return ParamsSensorBase::print() + "\n" + + "std_p: " + std::to_string(std_p) + "\n" + + "std_o: " + std::to_string(std_o) + "\n"; + } + ~ParamsSensorPose() override = default; +}; + +WOLF_PTR_TYPEDEFS(SensorPose); + +class SensorPose : public SensorBase +{ + protected: + double std_p_; // standard deviation of translation measurements + double std_o_; // standard deviation of orientation measurements + + public: + SensorPose(const Eigen::VectorXd& _extrinsics_pq, const ParamsSensorPose& params); + SensorPose(const Eigen::VectorXd& _extrinsics_pq, ParamsSensorPosePtr params); + WOLF_SENSOR_CREATE(SensorPose, ParamsSensorPose, 7); + + ~SensorPose() override; + + double getStdP() const; + double getStdO() const; + +}; + +inline double SensorPose::getStdP() const +{ + return std_p_; +} + +inline double SensorPose::getStdO() const +{ + return std_o_; +} + +// inline Matrix6d SensorPose::computeDataCov() const +// { +// return (Vector6d() << std_p_*std_p_, std_p_*std_p_, std_p_*std_p_, std_o_*std_o_, std_o_*std_o_, std_o_*std_o_).finished().asDiagonal(); +// } + +} /* namespace wolf */ + +#endif /* SRC_SENSOR_POSE_H_ */ diff --git a/src/processor/processor_pose.cpp b/src/processor/processor_pose.cpp new file mode 100644 index 0000000000000000000000000000000000000000..806021722dd5ad3b25220315a71cc578f02ed0bf --- /dev/null +++ b/src/processor/processor_pose.cpp @@ -0,0 +1,113 @@ +/** + * \file processor_pose.cpp + * + * Created on: Feb 19, 2020 + * \author: mfourmy + */ + +#include "core/processor/processor_pose.h" + + +namespace wolf{ + + +inline ProcessorPose::ProcessorPose(ParamsProcessorPosePtr _params_pfnomove) : + ProcessorBase("ProcessorPose", 3, _params_pfnomove), + params_pfnomove_(std::make_shared<ParamsProcessorPose>(*_params_pfnomove)) +{ + +} + +void ProcessorPose::configure(SensorBasePtr _sensor) +{ +} + +void ProcessorPose::createFactorIfNecessary(){ + auto sensor_pose = std::static_pointer_cast<SensorPose>(getSensor()); + + while (buffer_pack_kf_.size() >= 1) + { + auto kf_it = buffer_pack_kf_.getContainer().begin(); + TimeStamp t = kf_it->first; + double time_tolerance = std::min(getTimeTolerance(), kf_it->second->time_tolerance); + if (getTimeTolerance() == 0.0){ + WOLF_WARN("Time tolerance set to zero -> value not used"); + time_tolerance = kf_it->second->time_tolerance; + } + auto cap_it = buffer_capture_.selectIterator(t, time_tolerance); + + // if capture with corresponding timestamp is not found, stop and assume you will get it later + if (cap_it == buffer_capture_.getContainer().end()) + { + return; + } + else + { + // if a corresponding capture exists, link it to the KF and create a factor + auto cap = std::static_pointer_cast<CapturePose>(cap_it->second); + cap->link(kf_it->second->key_frame); + FeatureBasePtr feat = FeatureBase::emplace<FeatureBase>(cap, "Pose", cap->getData(), cap->getDataCovariance()); + FactorPose3dWithExtrinsicsPtr fac = FactorBase::emplace<FactorPose3dWithExtrinsics>(feat, feat, nullptr, false, TOP_MOTION); + + // erase removes range [first, last): it does not removes last + // so increment the iterator so that it points to the next element in the container + buffer_pack_kf_.getContainer().erase(buffer_pack_kf_.getContainer().begin(), std::next(kf_it)); + buffer_capture_.getContainer().erase(buffer_capture_.getContainer().begin(), std::next(cap_it)); + } + } +} + + +inline void ProcessorPose::processCapture(CaptureBasePtr _capture) +{ + if (!_capture) + { + WOLF_ERROR("Received capture is nullptr."); + return; + } + // nothing to do if any of the two buffer is empty + if(buffer_pack_kf_.empty()){ + WOLF_DEBUG("PInertialKinematic: KF pack buffer empty, time ", _capture->getTimeStamp()); + return; + } + if(buffer_pack_kf_.empty()){ + WOLF_DEBUG("PInertialKinematics: Capture buffer empty, time ", _capture->getTimeStamp()); + return; + } + + createFactorIfNecessary(); + +} + +inline void ProcessorPose::processKeyFrame(FrameBasePtr _keyframe_ptr, const double& _time_tolerance) +{ + if (!_keyframe_ptr) + { + WOLF_ERROR("Received KF is nullptr."); + return; + } + // nothing to do if any of the two buffer is empty + if(buffer_pack_kf_.empty()){ + WOLF_DEBUG("ProcessorPose: KF pack buffer empty, time ", _keyframe_ptr->getTimeStamp()); + return; + } + if(buffer_pack_kf_.empty()){ + WOLF_DEBUG("ProcessorPose: Capture buffer empty, time ", _keyframe_ptr->getTimeStamp()); + return; + } + + createFactorIfNecessary(); +} + + + + + +} /* namespace wolf */ + +// Register in the FactoryProcessor +#include "core/processor/factory_processor.h" +namespace wolf { +WOLF_REGISTER_PROCESSOR(ProcessorPose); +WOLF_REGISTER_PROCESSOR_AUTO(ProcessorPose); +} // namespace wolf diff --git a/src/sensor/sensor_pose.cpp b/src/sensor/sensor_pose.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9cd65ccc8b5e95b88234ccd5ef5e2a5f4683fca0 --- /dev/null +++ b/src/sensor/sensor_pose.cpp @@ -0,0 +1,46 @@ +/** + * \file sensor_pose.cpp + * + * Created on: Feb 18, 2020 + * \author: mfourmy + */ + +#include "core/sensor/sensor_pose.h" + +#include "core/state_block/state_block.h" +#include "core/state_block/state_quaternion.h" +#include "core/math/rotations.h" + +namespace wolf { + +SensorPose::SensorPose(const Eigen::VectorXd& _extrinsics_pq, const ParamsSensorPose& _intrinsics) : + SensorBase("SensorPose", std::make_shared<StateBlock>(_extrinsics_pq.head(3), true), std::make_shared<StateQuaternion>(_extrinsics_pq.tail(4), true), nullptr, 6), + std_p_(_intrinsics.std_p), + std_o_(_intrinsics.std_o) +{ + assert(_extrinsics_pq.size() == 7 && "Bad extrinsics vector size! Should be 7 for 3d."); + + noise_cov_ = Matrix6d::Identity(); + // noise_cov_ = (Eigen::Vector6d() << std_p_*std_p_, std_p_*std_p_, std_p_*std_p_, std_o_*std_o_, std_o_*std_o_, std_o_*std_o_).finished().asDiagonal(); + setNoiseCov(noise_cov_); // sets also noise_std_ +} + +SensorPose::SensorPose(const Eigen::VectorXd& _extrinsics_pq, ParamsSensorPosePtr _intrinsics) : + SensorPose(_extrinsics_pq, *_intrinsics) +{ + // +} + +SensorPose::~SensorPose() +{ + // +} + +} // namespace wolf + +// Register in the FactorySensor +#include "core/sensor/factory_sensor.h" +namespace wolf { +WOLF_REGISTER_SENSOR(SensorPose); +WOLF_REGISTER_SENSOR_AUTO(SensorPose); +} diff --git a/src/yaml/sensor_pose_yaml.cpp b/src/yaml/sensor_pose_yaml.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4b9521595c7d5ac100e7d6dd72b266dc884d7336 --- /dev/null +++ b/src/yaml/sensor_pose_yaml.cpp @@ -0,0 +1,47 @@ +/** + * \file sensor_pose_yaml.cpp + * + * Created on: Feb 18, 2020 + * \author: mfourmy + */ + +// wolf yaml +#include "core/yaml/yaml_conversion.h" + +// wolf +#include "core/sensor/sensor_pose.h" +#include "core/common/factory.h" + +// yaml-cpp library +#include <yaml-cpp/yaml.h> + +namespace wolf +{ + +namespace +{ +static ParamsSensorBasePtr createParamsSensorPose(const std::string & _filename_dot_yaml) +{ + YAML::Node config = YAML::LoadFile(_filename_dot_yaml); + + if (config["type"].as<std::string>() == "SensorPose") + { + ParamsSensorPosePtr params = std::make_shared<ParamsSensorPose>(); + + params->std_p = config["std_p"].as<double>(); + params->std_o = config["std_o"].as<double>(); + + return params; + } + + std::cout << "Bad configuration file. No sensor type found." << std::endl; + return nullptr; +} + +// Register in the FactorySensor +const bool WOLF_UNUSED registered_odom_3d_intr = FactoryParamsSensor::registerCreator("SensorPose", createParamsSensorPose); + +} // namespace [unnamed] + +} // namespace wolf + diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 8deec25dbf4bbb80a78298e77301a4c24ecfac35..885c1f53a456deb4b74392d011d560cb7216bafe 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -241,6 +241,11 @@ target_link_libraries(gtest_odom_2d ${PLUGIN_NAME}) wolf_add_gtest(gtest_processor_odom_3d gtest_processor_odom_3d.cpp) target_link_libraries(gtest_processor_odom_3d ${PLUGIN_NAME}) +# FactorPose3dWithExtrinsics class test +wolf_add_gtest(gtest_processor_and_factor_pose_3d_with_extrinsics gtest_processor_and_factor_pose_3d_with_extrinsics.cpp) +target_link_libraries(gtest_processor_and_factor_pose_3d_with_extrinsics ${PLUGIN_NAME}) + + # ProcessorTrackerFeatureDummy class test wolf_add_gtest(gtest_processor_tracker_feature_dummy gtest_processor_tracker_feature_dummy.cpp) target_link_libraries(gtest_processor_tracker_feature_dummy ${PLUGIN_NAME} dummy) @@ -253,6 +258,10 @@ target_link_libraries(gtest_processor_tracker_landmark_dummy ${PLUGIN_NAME} dumm wolf_add_gtest(gtest_sensor_diff_drive gtest_sensor_diff_drive.cpp) target_link_libraries(gtest_sensor_diff_drive ${PLUGIN_NAME}) +# SensorDiffDriveSelfcalib class test +wolf_add_gtest(gtest_sensor_pose gtest_sensor_pose.cpp) +target_link_libraries(gtest_sensor_pose ${PLUGIN_NAME}) + IF (Ceres_FOUND) # SolverCeres test wolf_add_gtest(gtest_solver_ceres gtest_solver_ceres.cpp) diff --git a/test/gtest_processor_and_factor_pose_3d_with_extrinsics.cpp b/test/gtest_processor_and_factor_pose_3d_with_extrinsics.cpp new file mode 100644 index 0000000000000000000000000000000000000000..444fc643caa29559441c9c64911048285a344989 --- /dev/null +++ b/test/gtest_processor_and_factor_pose_3d_with_extrinsics.cpp @@ -0,0 +1,318 @@ +/** + * \file gtest_factor_pose_with_extrinsics.cpp + * + * Created on: Feb 19, 2020 + * \author: mfourmy + */ + +#include "core/utils/utils_gtest.h" +#include "core/ceres_wrapper/solver_ceres.h" +#include "core/capture/capture_base.h" +#include "core/capture/capture_pose.h" +#include "core/sensor/sensor_pose.h" +#include "core/processor/processor_pose.h" +#include "core/factor/factor_relative_pose_3d.h" +#include "core/capture/capture_odom_3d.h" +#include "core/factor/factor_pose_3d_with_extrinsics.h" + + +using namespace Eigen; +using namespace wolf; +using std::cout; +using std::endl; + +const Vector3d zero3 = Vector3d::Zero(); + + + +class FactorPose3dWithExtrinsicsBase_Test : public testing::Test +{ + /** + Factor graph implemented common for all the tests + 2 KF is not enough since the SensorPose extrinsics translation would be unobservable along the first KF1-KF2 transformation axis of rotation + (KF1)----|Odom3d|----(KF2)----|Odom3d|----(KF3) + | | | + | | | + |Pose3dWE| |Pose3dWE| |Pose3dWE| + \ / + \ / + \____________(Extrinsics)_______________/ + */ + + // In this class, the poses of the 3 KF are chosen randomly and then measurement data for the factors are + // derived from random extrinsics + // The problem and Pose Sensor/Processor are implemented + + public: + + ProblemPtr problem_; + SolverCeresPtr solver_; + SensorBasePtr sensor_pose_; + FrameBasePtr KF1_; + FrameBasePtr KF2_; + FrameBasePtr KF3_; + + Vector7d pose1_; + Vector7d pose2_; + Vector7d pose3_; + Vector7d pose_12_; + Vector7d pose_23_; + Vector3d b_p_bm_; + Quaterniond b_q_m_; + + Vector7d pose1_meas_; + Vector7d pose2_meas_; + Vector7d pose3_meas_; + + void SetUp() override + { + // 2 random 3d positions + Vector3d w_p_wb1 = Vector3d::Random(); + Quaterniond w_q_b1 = Quaterniond::UnitRandom(); + Vector3d w_p_wb2 = Vector3d::Random(); + Quaterniond w_q_b2 = Quaterniond::UnitRandom(); + Vector3d w_p_wb3 = Vector3d::Random(); + Quaterniond w_q_b3 = Quaterniond::UnitRandom(); + + // pose vectors + pose1_ << w_p_wb1, w_q_b1.coeffs(); + pose2_ << w_p_wb2, w_q_b2.coeffs(); + pose3_ << w_p_wb3, w_q_b3.coeffs(); + + ///////////////////////////////////////// + // extrinsics ground truth and mocap data + // b_p_bm_ << 0.1,0.2,0.3; + b_p_bm_ << Vector3d::Random(); + b_q_m_ = Quaterniond::UnitRandom(); + + Vector3d w_p_wb1_meas = w_p_wb1 + w_q_b1 * b_p_bm_; + Vector3d w_p_wb2_meas = w_p_wb2 + w_q_b2 * b_p_bm_; + Vector3d w_p_wb3_meas = w_p_wb3 + w_q_b3 * b_p_bm_; + Quaterniond w_q_b1_meas = w_q_b1 * b_q_m_; + Quaterniond w_q_b2_meas = w_q_b2 * b_q_m_; + Quaterniond w_q_b3_meas = w_q_b3 * b_q_m_; + pose1_meas_ << w_p_wb1_meas, w_q_b1_meas.coeffs(); + pose2_meas_ << w_p_wb2_meas, w_q_b2_meas.coeffs(); + pose3_meas_ << w_p_wb3_meas, w_q_b3_meas.coeffs(); + + ///////////////////// + // relative odom data + Vector3d b1_p_b1b2 = w_q_b1.conjugate()*(w_p_wb2 - w_p_wb1); + Quaterniond b1_q_b2 = w_q_b1.conjugate()*w_q_b2; + pose_12_ << b1_p_b1b2, b1_q_b2.coeffs(); + + Vector3d b2_p_b2b3 = w_q_b2.conjugate()*(w_p_wb3 - w_p_wb2); + Quaterniond b2_q_b3 = w_q_b2.conjugate()*w_q_b3; + pose_23_ << b2_p_b2b3, b2_q_b3.coeffs(); + + // Problem and solver_ + problem_ = Problem::create("PO", 3); + solver_ = std::make_shared<SolverCeres>(problem_); + + // pose sensor and proc (to get extrinsics in the prb) + auto intr_sp = std::make_shared<ParamsSensorPose>(); + intr_sp->std_p = 1; + intr_sp->std_o = 1; + Vector7d extr; extr << b_p_bm_, b_q_m_.coeffs(); + sensor_pose_ = problem_->installSensor("SensorPose", "pose", extr, intr_sp); + auto params_proc = std::make_shared<ParamsProcessorPose>(); + params_proc->time_tolerance = 0.5; + auto proc_pose = problem_->installProcessor("ProcessorPose", "pose", sensor_pose_, params_proc); + // somehow by default the sensor extrinsics is fixed + sensor_pose_->unfixExtrinsics(); + } + + void TearDown() override{}; +}; + +class FactorPose3dWithExtrinsicsMANUAL_Test : public FactorPose3dWithExtrinsicsBase_Test +{ + public: + + void SetUp() override + { + FactorPose3dWithExtrinsicsBase_Test::SetUp(); + // Two frames + KF1_ = problem_->emplaceFrame(1, pose1_); + KF2_ = problem_->emplaceFrame(2, pose2_); + KF3_ = problem_->emplaceFrame(3, pose3_); + + /////////////////// + // Create factor graph + Matrix6d cov6d = sensor_pose_->getNoiseCov(); + // Odometry capture, feature and factor + auto cap_odom1 = CaptureBase::emplace<CapturePose>(KF2_, 2, nullptr, pose_12_, cov6d); + auto fea_odom1 = FeatureBase::emplace<FeatureBase>(cap_odom1, "odom", pose_12_, cov6d); + FactorRelativePose3dPtr fac_odomA = FactorBase::emplace<FactorRelativePose3d>(fea_odom1, fea_odom1, KF1_, nullptr, false, TOP_MOTION); + + auto cap_odom2 = CaptureBase::emplace<CapturePose>(KF3_, 3, nullptr, pose_23_, cov6d); + auto fea_odom2 = FeatureBase::emplace<FeatureBase>(cap_odom2, "odom", pose_23_, cov6d); + FactorRelativePose3dPtr fac_odom2 = FactorBase::emplace<FactorRelativePose3d>(fea_odom2, fea_odom2, KF2_, nullptr, false, TOP_MOTION); + + // Captures mocap + auto cap_mocap1 = CaptureBase::emplace<CapturePose>(KF1_, 1, sensor_pose_, pose1_meas_, cov6d); + auto fea_mocap1 = FeatureBase::emplace<FeatureBase>(cap_mocap1, "pose", pose1_meas_, cov6d); + FactorPose3dWithExtrinsicsPtr fac_mocap1 = FactorBase::emplace<FactorPose3dWithExtrinsics>(fea_mocap1, fea_mocap1, nullptr, false, TOP_MOTION); + + auto cap_mocap2 = CaptureBase::emplace<CapturePose>(KF2_, 2, sensor_pose_, pose2_meas_, cov6d); + auto fea_mocap2 = FeatureBase::emplace<FeatureBase>(cap_mocap2, "pose", pose2_meas_, cov6d); + FactorPose3dWithExtrinsicsPtr fac_mocap2 = FactorBase::emplace<FactorPose3dWithExtrinsics>(fea_mocap2, fea_mocap2, nullptr, false, TOP_MOTION); + + auto cap_mocap3 = CaptureBase::emplace<CapturePose>(KF3_, 3, sensor_pose_, pose3_meas_, cov6d); + auto fea_mocap3 = FeatureBase::emplace<FeatureBase>(cap_mocap3, "pose", pose3_meas_, cov6d); + FactorPose3dWithExtrinsicsPtr fac_mocap3 = FactorBase::emplace<FactorPose3dWithExtrinsics>(fea_mocap3, fea_mocap3, nullptr, false, TOP_MOTION); + + + } + + void TearDown() override{}; +}; + + +class FactorPose3dWithExtrinsics_ProcessorWithKeyFrameCallbackFirst_Test : public FactorPose3dWithExtrinsicsBase_Test +{ + public: + + void SetUp() override + { + FactorPose3dWithExtrinsicsBase_Test::SetUp(); + // Two frames + KF1_ = problem_->emplaceFrame(1, pose1_); + KF2_ = problem_->emplaceFrame(2, pose2_); + KF3_ = problem_->emplaceFrame(3, pose3_); + problem_->keyFrameCallback(KF1_, nullptr, 0.5); + problem_->keyFrameCallback(KF2_, nullptr, 0.5); + problem_->keyFrameCallback(KF3_, nullptr, 0.5); + + /////////////////// + // Create factor graph + Matrix6d cov6d = sensor_pose_->getNoiseCov(); + // Odometry capture, feature and factor + auto cap_odom1 = CaptureBase::emplace<CapturePose>(KF2_, 2, nullptr, pose_12_, cov6d); + auto fea_odom1 = FeatureBase::emplace<FeatureBase>(cap_odom1, "odom", pose_12_, cov6d); + FactorRelativePose3dPtr fac_odomA = FactorBase::emplace<FactorRelativePose3d>(fea_odom1, fea_odom1, KF1_, nullptr, false, TOP_MOTION); + + auto cap_odom2 = CaptureBase::emplace<CapturePose>(KF3_, 3, nullptr, pose_23_, cov6d); + auto fea_odom2 = FeatureBase::emplace<FeatureBase>(cap_odom2, "odom", pose_23_, cov6d); + FactorRelativePose3dPtr fac_odom2 = FactorBase::emplace<FactorRelativePose3d>(fea_odom2, fea_odom2, KF2_, nullptr, false, TOP_MOTION); + + // process mocap data + auto cap_mocap1 = std::make_shared<CapturePose>(1, sensor_pose_, pose1_meas_, cov6d); + auto cap_mocap2 = std::make_shared<CapturePose>(2, sensor_pose_, pose2_meas_, cov6d); + auto cap_mocap3 = std::make_shared<CapturePose>(3, sensor_pose_, pose3_meas_, cov6d); + cap_mocap1->process(); + cap_mocap2->process(); + cap_mocap3->process(); + + } + + void TearDown() override{}; +}; + + +class FactorPose3dWithExtrinsics_ProcessorWithProcessFirst_Test : public FactorPose3dWithExtrinsicsBase_Test +{ + public: + + void SetUp() override + { + FactorPose3dWithExtrinsicsBase_Test::SetUp(); + // Two frames + KF1_ = problem_->emplaceFrame(1, pose1_); + KF2_ = problem_->emplaceFrame(2, pose2_); + KF3_ = problem_->emplaceFrame(3, pose3_); + + Matrix6d cov6d = sensor_pose_->getNoiseCov(); + /////////////////// + // Create factor graph + // Odometry capture, feature and factor + auto cap_odom1 = CaptureBase::emplace<CapturePose>(KF2_, 2, nullptr, pose_12_, cov6d); + auto fea_odom1 = FeatureBase::emplace<FeatureBase>(cap_odom1, "odom", pose_12_, cov6d); + FactorRelativePose3dPtr fac_odomA = FactorBase::emplace<FactorRelativePose3d>(fea_odom1, fea_odom1, KF1_, nullptr, false, TOP_MOTION); + + auto cap_odom2 = CaptureBase::emplace<CapturePose>(KF3_, 3, nullptr, pose_23_, cov6d); + auto fea_odom2 = FeatureBase::emplace<FeatureBase>(cap_odom2, "odom", pose_23_, cov6d); + FactorRelativePose3dPtr fac_odom2 = FactorBase::emplace<FactorRelativePose3d>(fea_odom2, fea_odom2, KF2_, nullptr, false, TOP_MOTION); + + // process mocap data + auto cap_mocap1 = std::make_shared<CapturePose>(1, sensor_pose_, pose1_meas_, cov6d); + auto cap_mocap2 = std::make_shared<CapturePose>(2, sensor_pose_, pose2_meas_, cov6d); + auto cap_mocap3 = std::make_shared<CapturePose>(3, sensor_pose_, pose3_meas_, cov6d); + cap_mocap1->process(); + cap_mocap2->process(); + cap_mocap3->process(); + + // keyframe callback called after all mocap captures have been processed + problem_->keyFrameCallback(KF1_, nullptr, 0.5); + problem_->keyFrameCallback(KF2_, nullptr, 0.5); + problem_->keyFrameCallback(KF3_, nullptr, 0.5); + } + + void TearDown() override{}; +}; + + + + +TEST_F(FactorPose3dWithExtrinsicsMANUAL_Test, check_tree) +{ + ASSERT_TRUE(problem_->check(0)); +} + +TEST_F(FactorPose3dWithExtrinsicsMANUAL_Test, solve) +{ + problem_->perturb(); + std::string report = solver_->solve(SolverManager::ReportVerbosity::FULL); + + SensorPosePtr sp = std::static_pointer_cast<SensorPose>(sensor_pose_); + ASSERT_MATRIX_APPROX(KF1_->getStateVector(), pose1_, 1e-6); + ASSERT_MATRIX_APPROX(KF2_->getStateVector(), pose2_, 1e-6); + ASSERT_MATRIX_APPROX(sp->getP()->getState(), b_p_bm_, 1e-6); + ASSERT_MATRIX_APPROX(sp->getO()->getState(), b_q_m_.coeffs(), 1e-6); + +} + + +TEST_F(FactorPose3dWithExtrinsics_ProcessorWithKeyFrameCallbackFirst_Test, check_tree) +{ + ASSERT_TRUE(problem_->check(0)); +} + +TEST_F(FactorPose3dWithExtrinsics_ProcessorWithKeyFrameCallbackFirst_Test, solve) +{ + problem_->perturb(); + std::string report = solver_->solve(SolverManager::ReportVerbosity::FULL); + + SensorPosePtr sp = std::static_pointer_cast<SensorPose>(sensor_pose_); + ASSERT_MATRIX_APPROX(KF1_->getStateVector(), pose1_, 1e-6); + ASSERT_MATRIX_APPROX(KF2_->getStateVector(), pose2_, 1e-6); + ASSERT_MATRIX_APPROX(sp->getP()->getState(), b_p_bm_, 1e-6); + ASSERT_MATRIX_APPROX(sp->getO()->getState(), b_q_m_.coeffs(), 1e-6); + +} + + +TEST_F(FactorPose3dWithExtrinsics_ProcessorWithProcessFirst_Test, check_tree) +{ + ASSERT_TRUE(problem_->check(0)); +} + +TEST_F(FactorPose3dWithExtrinsics_ProcessorWithProcessFirst_Test, solve) +{ + problem_->perturb(); + std::string report = solver_->solve(SolverManager::ReportVerbosity::FULL); + + SensorPosePtr sp = std::static_pointer_cast<SensorPose>(sensor_pose_); + ASSERT_MATRIX_APPROX(KF1_->getStateVector(), pose1_, 1e-6); + ASSERT_MATRIX_APPROX(KF2_->getStateVector(), pose2_, 1e-6); + ASSERT_MATRIX_APPROX(sp->getP()->getState(), b_p_bm_, 1e-6); + ASSERT_MATRIX_APPROX(sp->getO()->getState(), b_q_m_.coeffs(), 1e-6); +} + + + +int main(int argc, char **argv) +{ + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} \ No newline at end of file diff --git a/test/gtest_sensor_pose.cpp b/test/gtest_sensor_pose.cpp new file mode 100644 index 0000000000000000000000000000000000000000..64d6b73a1a77ddf4a1ee7a85f72164d4174c8ff6 --- /dev/null +++ b/test/gtest_sensor_pose.cpp @@ -0,0 +1,69 @@ +/** + * \file gtest_sensor_pose.cpp + * + * Created on: Feb 18, 2020 + * \author: mfourmy + */ + +#include "core/sensor/sensor_pose.h" +#include "core/utils/utils_gtest.h" + +#include <cstdio> + +using namespace wolf; +using namespace Eigen; + +TEST(Pose, constructor) +{ + + auto intr = std::make_shared<ParamsSensorPose>(); + Vector7d extr; extr << 1,2,3,4,5,6,7; + + auto sen = std::make_shared<SensorPose>(extr, intr); + + ASSERT_NE(sen, nullptr); + + ASSERT_MATRIX_APPROX(sen->getP()->getState(), extr.head<3>(), 1e-12); + ASSERT_MATRIX_APPROX(sen->getO()->getState(), extr.tail<4>(), 1e-12); +} + +TEST(Pose, getParams) +{ + auto intr = std::make_shared<ParamsSensorPose>(); + intr->std_p = 2; + intr->std_o = 3; + + Vector7d extr; extr << 1,2,3,4,5,6,7; + + auto sen = std::make_shared<SensorPose>(extr, intr); + + ASSERT_EQ(sen->getStdP(), intr->std_p); + ASSERT_EQ(sen->getStdO(), intr->std_o); + // ASSERT_EQ(sen->getNoiseCov()(0,0), (Vector1d() << 4).finished()); + // ASSERT_EQ(sen->getNoiseCov()(3,3), (Vector1d() << 9).finished()); +} + +TEST(Pose, create) +{ + auto intr = std::make_shared<ParamsSensorPose>(); + intr->std_p = 2; + intr->std_o = 3; + + Vector7d extr; extr << 1,2,3,4,5,6,7; + + auto sen_base = SensorPose::create("name", extr, intr); + + auto sen = std::static_pointer_cast<SensorPose>(sen_base); + + ASSERT_EQ(sen->getStdP(), intr->std_p); + ASSERT_EQ(sen->getStdO(), intr->std_o); + // ASSERT_EQ(sen->getNoiseCov()(0,0), (Vector1d() << 4).finished()); + // ASSERT_EQ(sen->getNoiseCov()(3,3), (Vector1d() << 9).finished()); +} + +int main(int argc, char **argv) +{ + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} +