diff --git a/CMakeLists.txt b/CMakeLists.txt index c6c2db62aa3d682dd1c78bdfbb696b837fa07120..599d7d8773e383fe7855a47e026d73743cf0e1fe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -229,6 +229,7 @@ SET(HDRS_FACTOR 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 + include/core/factor/factor_velocity_local_direction_3d.h ) SET(HDRS_FEATURE include/core/feature/feature_base.h @@ -246,6 +247,7 @@ SET(HDRS_PROCESSOR include/core/processor/motion_buffer.h include/core/processor/processor_base.h include/core/processor/processor_diff_drive.h + include/core/processor/processor_fix_wing_model.h include/core/processor/factory_processor.h include/core/processor/processor_logging.h include/core/processor/processor_loop_closure.h @@ -345,6 +347,7 @@ SET(SRCS_PROCESSOR src/processor/motion_buffer.cpp src/processor/processor_base.cpp src/processor/processor_diff_drive.cpp + src/processor/processor_fix_wing_model.cpp src/processor/processor_loop_closure.cpp src/processor/processor_motion.cpp src/processor/processor_odom_2d.cpp diff --git a/include/core/ceres_wrapper/solver_ceres.h b/include/core/ceres_wrapper/solver_ceres.h index 0673207bc47e1873084e3f4d711628a67b322f3c..4bf6f7de661059e7c9f96e7f12bb444f18bed013 100644 --- a/include/core/ceres_wrapper/solver_ceres.h +++ b/include/core/ceres_wrapper/solver_ceres.h @@ -119,6 +119,8 @@ class SolverCeres : public SolverManager double finalCost() override; + double totalTime() override; + ceres::Solver::Options& getSolverOptions(); const Eigen::SparseMatrixd computeHessian() const; diff --git a/include/core/factor/factor_relative_pose_3d.h b/include/core/factor/factor_relative_pose_3d.h index 67d98f7e26d03e360c0a5d846b7d84b54012047b..afcf8daf6884c9c08eb800433fa1f2392a8ecd0c 100644 --- a/include/core/factor/factor_relative_pose_3d.h +++ b/include/core/factor/factor_relative_pose_3d.h @@ -93,6 +93,8 @@ inline FactorRelativePose3d::FactorRelativePose3d(const FeatureBasePtr& _ftr_cur _frame_past_ptr->getP(), // past frame P _frame_past_ptr->getO()) // past frame Q { + MatrixSizeCheck<7,1>::check(_ftr_current_ptr->getMeasurement()); + MatrixSizeCheck<6,6>::check(_ftr_current_ptr->getMeasurementSquareRootInformationUpper()); } template<typename T> diff --git a/include/core/factor/factor_velocity_local_direction_3d.h b/include/core/factor/factor_velocity_local_direction_3d.h new file mode 100644 index 0000000000000000000000000000000000000000..01e635e660a3cbd40d221a495d244241e2db2461 --- /dev/null +++ b/include/core/factor/factor_velocity_local_direction_3d.h @@ -0,0 +1,148 @@ + +#ifndef FACTOR_VELOCITY_LOCAL_DIRECTION_3D_H_ +#define FACTOR_VELOCITY_LOCAL_DIRECTION_3D_H_ + +//Wolf includes +#include "core/factor/factor_autodiff.h" +#include "core/frame/frame_base.h" +#include "core/math/rotations.h" + +namespace wolf { + +WOLF_PTR_TYPEDEFS(FactorVelocityLocalDirection3d); + +//class +class FactorVelocityLocalDirection3d: public FactorAutodiff<FactorVelocityLocalDirection3d,2,3,4> +{ + protected: + double min_vel_sq_norm_; // stored in squared norm for efficiency + int orthogonal_axis_; // 0: X, 1: Y, 2: Z + + public: + FactorVelocityLocalDirection3d(FeatureBasePtr _ftr_ptr, + const double& _min_vel_norm, + const ProcessorBasePtr& _processor_ptr, + bool _apply_loss_function, + FactorStatus _status = FAC_ACTIVE); + + ~FactorVelocityLocalDirection3d() override = default; + + template<typename T> + bool operator ()(const T* const _v, const T* const _o, T* _residuals) const; + + template<typename T> + Eigen::Matrix<T,2,1> computeElevationAzimuth(const Eigen::Matrix<T,3,1> vector) const + { + Eigen::Matrix<T,2,1> elev_azim; + + // plane YZ + if (orthogonal_axis_ == 0) + { + elev_azim(0) = asin(vector(0) / vector.norm()); + elev_azim(1) = atan2(vector(2), vector(1)); + } + // plane XZ + if (orthogonal_axis_ == 1) + { + elev_azim(0) = asin(vector(1) / vector.norm()); + elev_azim(1) = atan2(vector(0), vector(2)); + } + // plane XY + if (orthogonal_axis_ == 2) + { + elev_azim(0) = asin(vector(2) / vector.norm()); + elev_azim(1) = atan2(vector(1), vector(0)); + } + + return elev_azim; + } +}; + +FactorVelocityLocalDirection3d::FactorVelocityLocalDirection3d(FeatureBasePtr _ftr_ptr, + const double& _min_vel_norm, + const ProcessorBasePtr& _processor_ptr, + bool _apply_loss_function, + FactorStatus _status) : + FactorAutodiff<FactorVelocityLocalDirection3d,2,3,4>("FactorVelocityLocalDirection3d", + TOP_ABS, + _ftr_ptr, + nullptr, nullptr, nullptr, nullptr, + _processor_ptr, + _apply_loss_function, + _status, + _ftr_ptr->getFrame()->getV(), + _ftr_ptr->getFrame()->getO()), + min_vel_sq_norm_(_min_vel_norm*_min_vel_norm) +{ + MatrixSizeCheck<3,1>::check(_ftr_ptr->getMeasurement()); + MatrixSizeCheck<1,1>::check(_ftr_ptr->getMeasurementSquareRootInformationUpper()); + + /* Decide the plane (two axis A1, A2 from X, Y, Z in local coordinates) in which compute elevation and azimuth + * - elevation w.r.t. the plane + * - azimuth computed with atan2(A2, A1) + * + * This is done to avoid the degenerated case: elevation = +/-PI/2 + * We select the orthogonal axis as the farthest to the desired velocity in local coordinates, + * so the component one with lower value. + */ + + measurement_.minCoeff(&orthogonal_axis_); + + // measurement: elevation & azimuth (w.r.t. selected plane) + measurement_ = computeElevationAzimuth(Eigen::Vector3d(measurement_)); + measurement_sqrt_information_upper_ = Matrix2d::Identity() * measurement_sqrt_information_upper_(0,0); +} + +template<typename T> +inline bool FactorVelocityLocalDirection3d::operator ()(const T* const _v, const T* const _q, T* _residuals) const +{ + Eigen::Map<const Eigen::Matrix<T,3,1> > v(_v); + Eigen::Map<const Eigen::Quaternion<T> > q(_q); + Eigen::Map<Eigen::Matrix<T,2,1> > residuals(_residuals); + + if (v.squaredNorm() < min_vel_sq_norm_) + { + _residuals[0] = T(0); + return true; + } + +// std::cout << "----------\n"; +// std::cout << "desired elevation: " << getMeasurement()(0) << "\n"; +// std::cout << "desired azimuth: " << getMeasurement()(1) << "\n"; + +// std::cout << "v: \n\t" << v(0) << "\n\t" +// << v(1) << "\n\t" +// << v(2) << "\n"; +// +// std::cout << "q: \n\t" << q.coeffs()(0) << "\n\t" +// << q.coeffs()(1) << "\n\t" +// << q.coeffs()(2) << "\n\t" +// << q.coeffs()(3) << "\n"; + + Eigen::Matrix<T,3,1> v_local = q.conjugate() * v; +// std::cout << "v (local): \n\t" << v_local(0) << "\n\t" +// << v_local(1) << "\n\t" +// << v_local(2) << "\n"; + + // expectation + Eigen::Matrix<T,2,1> exp_elev_azim = computeElevationAzimuth(v_local); +// std::cout << "expected elevation: " << exp_elev_azim(0) << "\n"; +// std::cout << "expected azimuth: " << exp_elev_azim(1) << "\n"; + + // error + Eigen::Matrix<T,2,1> error = getMeasurement() - exp_elev_azim; + pi2pi(error(1)); +// std::cout << "error (corrected): \n\t" << error(0) << "\n\t" +// << error(1) << "\n; + + // residuals + residuals = getMeasurementSquareRootInformationUpper() * error; +// std::cout << "residuals: \n\t" << residuals(0) << "\n\t" +// << residuals(1) << "\n; + + return true; +} + +} // namespace wolf + +#endif diff --git a/include/core/processor/processor_fix_wing_model.h b/include/core/processor/processor_fix_wing_model.h new file mode 100644 index 0000000000000000000000000000000000000000..8c23917354ca1d0e0c3723045ded8d118e3c3abc --- /dev/null +++ b/include/core/processor/processor_fix_wing_model.h @@ -0,0 +1,92 @@ +/* + * processor_fix_wing_model.h + * + * Created on: Sep 6, 2021 + * Author: joanvallve + */ + +#ifndef INCLUDE_CORE_PROCESSOR_PROCESSOR_FIX_WING_MODEL_H_ +#define INCLUDE_CORE_PROCESSOR_PROCESSOR_FIX_WING_MODEL_H_ + +#include "core/processor/processor_base.h" + +namespace wolf +{ + +WOLF_STRUCT_PTR_TYPEDEFS(ParamsProcessorFixWingModel); + +struct ParamsProcessorFixWingModel : public ParamsProcessorBase +{ + Eigen::Vector3d velocity_local; + double angle_stdev; + double min_vel_norm; + + ParamsProcessorFixWingModel() = default; + ParamsProcessorFixWingModel(std::string _unique_name, const wolf::ParamsServer & _server) : + ParamsProcessorBase(_unique_name, _server) + { + velocity_local = _server.getParam<Eigen::Vector3d> (prefix + _unique_name + "/velocity_local"); + angle_stdev = _server.getParam<double> (prefix + _unique_name + "/angle_stdev"); + min_vel_norm = _server.getParam<double> (prefix + _unique_name + "/min_vel_norm"); + + assert(std::abs(velocity_local.norm() - 1.0) < wolf::Constants::EPS && "ParamsProcessorFixWingModel: 'velocity_local' must be normalized"); + } + std::string print() const override + { + return ParamsProcessorBase::print() + "\n" + + "velocity_local: print not implemented\n" + + "angle_stdev: " + std::to_string(angle_stdev) + "\n" + + "min_vel_norm: " + std::to_string(min_vel_norm) + "\n"; + } +}; + +WOLF_PTR_TYPEDEFS(ProcessorFixWingModel); + +class ProcessorFixWingModel : public ProcessorBase +{ + public: + ProcessorFixWingModel(ParamsProcessorFixWingModelPtr _params); + + // Factory method for high level API + WOLF_PROCESSOR_CREATE(ProcessorFixWingModel, ParamsProcessorFixWingModel); + + virtual ~ProcessorFixWingModel() override; + void configure(SensorBasePtr _sensor) override; + + protected: + + /** \brief process an incoming capture NEVER CALLED + */ + virtual void processCapture(CaptureBasePtr) override {}; + + /** \brief process an incoming key-frame: applies the motion model between consecutive keyframes + */ + virtual void processKeyFrame(FrameBasePtr _keyframe_ptr, const double& _time_tolerance) override; + + /** \brief trigger in capture + */ + virtual bool triggerInCapture(CaptureBasePtr) const override {return false;}; + + /** \brief trigger in key-frame + */ + virtual bool triggerInKeyFrame(FrameBasePtr _keyframe_ptr, const double& _time_tolerance) const override {return true;}; + + /** \brief store key frame + */ + virtual bool storeKeyFrame(FrameBasePtr) override {return false;}; + + /** \brief store capture + */ + virtual bool storeCapture(CaptureBasePtr) override {return false;}; + + /** \brief Vote for KeyFrame generation + */ + virtual bool voteForKeyFrame() const override {return false;}; + + // ATTRIBUTES + ParamsProcessorFixWingModelPtr params_processor_; +}; + +} /* namespace wolf */ + +#endif /* INCLUDE_CORE_PROCESSOR_PROCESSOR_FIX_WING_MODEL_H_ */ diff --git a/include/core/solver/solver_manager.h b/include/core/solver/solver_manager.h index 58d21a5cf834cc18372a197b3bbbead9894e46f6..f3fbeefb3dce65e58ad82d3c604ba3e554169624 100644 --- a/include/core/solver/solver_manager.h +++ b/include/core/solver/solver_manager.h @@ -131,6 +131,8 @@ class SolverManager virtual double finalCost() = 0; + virtual double totalTime() = 0; + /** * \brief Updates solver's problem according to the wolf_problem */ diff --git a/src/ceres_wrapper/solver_ceres.cpp b/src/ceres_wrapper/solver_ceres.cpp index a2d7616c58181be35b9aae642e0e8e9f956b4879..5914b40a8bc67e44bbc392b146766331e344ced7 100644 --- a/src/ceres_wrapper/solver_ceres.cpp +++ b/src/ceres_wrapper/solver_ceres.cpp @@ -612,6 +612,11 @@ double SolverCeres::finalCost() return double(summary_.final_cost); } +double SolverCeres::totalTime() +{ + return double(summary_.total_time_in_seconds); +} + ceres::CostFunctionPtr SolverCeres::createCostFunction(const FactorBasePtr& _fac_ptr) { assert(_fac_ptr != nullptr); diff --git a/src/processor/processor_fix_wing_model.cpp b/src/processor/processor_fix_wing_model.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7c12e90f8f08005d2f0086d57ad1633534dbe8ea --- /dev/null +++ b/src/processor/processor_fix_wing_model.cpp @@ -0,0 +1,66 @@ +/* + * processor_fix_wing_model.cpp + * + * Created on: Sep 6, 2021 + * Author: joanvallve + */ + +#include "core/processor/processor_fix_wing_model.h" + +#include "core/capture/capture_base.h" +#include "core/feature/feature_base.h" +#include "core/factor/factor_velocity_local_direction_3d.h" + +namespace wolf +{ + +ProcessorFixWingModel::ProcessorFixWingModel(ParamsProcessorFixWingModelPtr _params) : + ProcessorBase("ProcessorFixWingModel", 3, _params), + params_processor_(_params) +{ +} + +ProcessorFixWingModel::~ProcessorFixWingModel() +{ +} + +void ProcessorFixWingModel::configure(SensorBasePtr _sensor) +{ + assert(_sensor->getProblem()->getFrameStructure().find('V') != std::string::npos && "Processor only works with problems with V"); +} + +void ProcessorFixWingModel::processKeyFrame(FrameBasePtr _keyframe_ptr, const double& _time_tolerance) +{ + if (_keyframe_ptr->getV()->isFixed()) + return; + + if (_keyframe_ptr->getFactorOf(shared_from_this()) != nullptr) + return; + + // emplace capture + auto cap = CaptureBase::emplace<CaptureBase>(_keyframe_ptr, "CaptureBase", + _keyframe_ptr->getTimeStamp(), getSensor()); + + // emplace feature + auto fea = FeatureBase::emplace<FeatureBase>(cap, "FeatureBase", + params_processor_->velocity_local, + Eigen::Matrix1d(params_processor_->angle_stdev * params_processor_->angle_stdev)); + + // emplace factor + auto fac = FactorBase::emplace<FactorVelocityLocalDirection3d>(fea, + fea, + params_processor_->min_vel_norm, + shared_from_this(), + false); +} + +} /* namespace wolf */ + + +// Register in the FactoryProcessor +#include "core/processor/factory_processor.h" +namespace wolf { +WOLF_REGISTER_PROCESSOR(ProcessorFixWingModel); +WOLF_REGISTER_PROCESSOR_AUTO(ProcessorFixWingModel); +} // namespace wolf + diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 4e03c1a2a284f02b2fe26869ad656e5c959a450a..46964ac57b3430b8113edbb7ee23d4923fc0d50b 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -217,6 +217,10 @@ target_link_libraries(gtest_factor_relative_pose_2d_with_extrinsics ${PLUGIN_NAM wolf_add_gtest(gtest_factor_relative_pose_3d gtest_factor_relative_pose_3d.cpp) target_link_libraries(gtest_factor_relative_pose_3d ${PLUGIN_NAME}) +# FactorVelocityLocalDirection3d class test +wolf_add_gtest(gtest_factor_velocity_local_direction_3d gtest_factor_velocity_local_direction_3d.cpp) +target_link_libraries(gtest_factor_velocity_local_direction_3d ${PLUGIN_NAME}) + # MakePosDef function test wolf_add_gtest(gtest_make_posdef gtest_make_posdef.cpp) target_link_libraries(gtest_make_posdef ${PLUGIN_NAME}) @@ -230,6 +234,10 @@ wolf_add_gtest(gtest_param_prior gtest_param_prior.cpp) target_link_libraries(gtest_param_prior ${PLUGIN_NAME}) # ProcessorDiffDriveSelfcalib class test +wolf_add_gtest(gtest_processor_fix_wing_model gtest_processor_fix_wing_model.cpp) +target_link_libraries(gtest_processor_fix_wing_model ${PLUGIN_NAME}) + +# ProcessorFixWingModel class test wolf_add_gtest(gtest_processor_diff_drive gtest_processor_diff_drive.cpp) target_link_libraries(gtest_processor_diff_drive ${PLUGIN_NAME}) diff --git a/test/dummy/solver_manager_dummy.h b/test/dummy/solver_manager_dummy.h index aab56cd1ab532a6a7d3e4b876f2de6dea0ea1b7b..cf45c71c94167f934504e5f97f91080d4d011ce8 100644 --- a/test/dummy/solver_manager_dummy.h +++ b/test/dummy/solver_manager_dummy.h @@ -60,6 +60,7 @@ class SolverManagerDummy : public SolverManager SizeStd iterations() override { return 1; } double initialCost() override { return double(1); } double finalCost() override { return double(0); } + double totalTime() override { return double(0); } protected: diff --git a/test/gtest_factor_velocity_local_direction_3d.cpp b/test/gtest_factor_velocity_local_direction_3d.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0834d95071973788df1aa053baba80adf3e95bf4 --- /dev/null +++ b/test/gtest_factor_velocity_local_direction_3d.cpp @@ -0,0 +1,287 @@ +#include "core/utils/utils_gtest.h" + +#include "core/ceres_wrapper/solver_ceres.h" +#include "core/factor/factor_velocity_local_direction_3d.h" +#include "core/capture/capture_odom_2d.h" +#include "core/math/rotations.h" + + +using namespace Eigen; +using namespace wolf; + +int N_TESTS = 100; + +class FactorVelocityLocalDirection3dTest : public testing::Test +{ + protected: + ProblemPtr problem; + SolverManagerPtr solver; + FrameBasePtr frm; + StateBlockPtr sb_p; + StateBlockPtr sb_o; + StateBlockPtr sb_v; + CaptureBasePtr cap; + + int n_solved; + std::vector<double> convergence, iterations, times, error; + + virtual void SetUp() + { + // create problem + problem = Problem::create("POV", 3); + + // create solver + auto params_solver = std::make_shared<ParamsCeres>(); + params_solver->solver_options.max_num_iterations = 1e3; + solver = std::make_shared<SolverCeres>(problem, params_solver); + + // Frame + frm = problem->emplaceFrame(0.0, (Vector10d() << 0,0,0,0,0,0,1,1,0,0).finished()); + sb_p = frm->getP(); + sb_p->fix(); + sb_o = frm->getO(); + sb_v = frm->getV(); + + // Capture + cap = CaptureBase::emplace<CaptureBase>(frm, "CaptureBase", + frm->getTimeStamp(), nullptr); + } + + FactorBasePtr emplaceFeatureAndFactor(const Vector3d& v_local, + const double& angle_stdev) + { + // emplace feature + FeatureBasePtr fea = FeatureBase::emplace<FeatureBase>(cap, "FeatureBase", + v_local, + Matrix1d(angle_stdev * angle_stdev)); + + // emplace factor + return FactorBase::emplace<FactorVelocityLocalDirection3d>(fea, + fea, + 0, + nullptr, + false); + } + + bool testLocalVelocity(const Quaterniond& o, + const Vector3d& v_local, + bool estimate_o, + bool estimate_v) + { + assert(cap->getFeatureList().empty()); + + // set state + sb_o->setState(o.coeffs()); + sb_v->setState(o * v_local); + + // fix or unfix & perturb + if (not estimate_o) + sb_o->fix(); + else + { + sb_o->unfix(); + sb_o->perturb(); + } + if (not estimate_v) + sb_v->fix(); + else + { + sb_v->unfix(); + sb_v->setState(Vector3d::Random()); + } + + // emplace feature & factor + auto fac = emplaceFeatureAndFactor(v_local, 0.01); + + // solve + std::string report = solver->solve(SolverManager::ReportVerbosity::FULL); + + // local coordinates + auto v_est_local_normalized = (Quaterniond(Vector4d(sb_o->getState())).conjugate() * sb_v->getState()).normalized(); + auto cos_angle_local = v_est_local_normalized.dot(v_local); + if (cos_angle_local > 1) + cos_angle_local = 1; + if (cos_angle_local < -1) + cos_angle_local = -1; + + // global coordinates + auto v_global = Quaterniond(Vector4d(sb_o->getState())) * v_local; + auto v_est_normalized = sb_v->getState().normalized(); + auto cos_angle_global = v_est_normalized.dot(v_global); + if (cos_angle_global > 1) + cos_angle_global = 1; + if (cos_angle_global < -1) + cos_angle_global = -1; + + // Check angle error + if (std::abs(acos(cos_angle_local)) > M_PI/180 or + std::abs(acos(cos_angle_global)) > M_PI/180) + { + WOLF_ERROR("\n\n!!!!!!!!!!!! ERROR TOO BIG iteration: ", iterations.size()); + WOLF_INFO("cos(angle local) = ", cos_angle_local); + WOLF_INFO("angle local = ", acos(cos_angle_local)); + WOLF_INFO("cos(angle global) = ", cos_angle_global); + WOLF_INFO("angle global = ", acos(cos_angle_global)); + + problem->print(4,1,1,1); + WOLF_INFO(report); + + // Reset + fac->getFeature()->remove(); + + return false; + } + + // Reset + fac->getFeature()->remove(); + + // Update performaces + convergence.push_back(solver->hasConverged() ? 1 : 0); + iterations.push_back(solver->iterations()); + times.push_back(solver->totalTime()); + error.push_back(acos(cos_angle_local)); + + return true; + } + + void printAverageResults() + { + WOLF_INFO("/////////////////// Average results: n_solved = ", iterations.size()); + double conv_avg = Eigen::Map<Eigen::VectorXd>(convergence.data(), convergence.size()).mean(); + double iter_avg = Eigen::Map<Eigen::VectorXd>(iterations.data(), iterations.size()).mean(); + double time_avg = Eigen::Map<Eigen::VectorXd>(times.data(), times.size()).mean(); + double err_avg = Eigen::Map<Eigen::VectorXd>(error.data(), error.size()).mean(); + + double iter_stdev = (Eigen::Map<Eigen::ArrayXd>(iterations.data(), iterations.size()) - iter_avg).matrix().norm(); + double time_stdev = (Eigen::Map<Eigen::ArrayXd>(times.data(), times.size()) - time_avg).matrix().norm(); + double err_stdev = (Eigen::Map<Eigen::ArrayXd>(error.data(), error.size()) - err_avg).matrix().norm(); + + WOLF_INFO("\tconvergence: ", 100 * conv_avg, "%"); + WOLF_INFO("\titerations: ", iter_avg, " - stdev: ", iter_stdev); + WOLF_INFO("\ttime: ", 1000 * time_avg, " ms", " - stdev: ", time_stdev); + WOLF_INFO("\tangle error: ", err_avg, " - stdev: ", err_stdev); + } +}; + +// Input odometry data and covariance +Vector3d v_local(1.0, 0.0, 0.0); +double angle_stdev = 0.1; + +TEST_F(FactorVelocityLocalDirection3dTest, check_tree) +{ + ASSERT_TRUE(problem->check(0)); + + emplaceFeatureAndFactor(Vector3d(1.0, 0.0, 0.0), 0.1); + + ASSERT_TRUE(problem->check(0)); +} + +TEST_F(FactorVelocityLocalDirection3dTest, origin_solveV) +{ + for (auto i = 0; i < N_TESTS; i++) + ASSERT_TRUE(testLocalVelocity(Quaterniond::Identity(), + Vector3d::Random().normalized(), + false, true)); + printAverageResults(); +} + +TEST_F(FactorVelocityLocalDirection3dTest, random_solveV) +{ + for (auto i = 0; i < N_TESTS; i++) + ASSERT_TRUE(testLocalVelocity(Quaterniond(Vector4d::Random().normalized()), + Vector3d::Random().normalized(), + false, true)); + printAverageResults(); +} + +TEST_F(FactorVelocityLocalDirection3dTest, origin_solveV_degenerated) +{ + for (auto i = 0; i < N_TESTS; i++) + ASSERT_TRUE(testLocalVelocity(Quaterniond::Identity(), + (Vector3d() << 1, 0, 0).finished() + Vector3d::Random() * 1e-4, + false, true)); + for (auto i = 0; i < N_TESTS; i++) + ASSERT_TRUE(testLocalVelocity(Quaterniond::Identity(), + (Vector3d() << 0, 1, 0).finished() + Vector3d::Random() * 1e-4, + false, true)); + for (auto i = 0; i < N_TESTS; i++) + ASSERT_TRUE(testLocalVelocity(Quaterniond::Identity(), + (Vector3d() << 0, 0, 1).finished() + Vector3d::Random() * 1e-4, + false, true)); + printAverageResults(); +} + +TEST_F(FactorVelocityLocalDirection3dTest, random_solveV_degenerated) +{ + for (auto i = 0; i < N_TESTS; i++) + ASSERT_TRUE(testLocalVelocity(Quaterniond(Vector4d::Random().normalized()), + (Vector3d() << 0, 0, 1).finished() + Vector3d::Random() * 1e-4, + false, true)); + for (auto i = 0; i < N_TESTS; i++) + ASSERT_TRUE(testLocalVelocity(Quaterniond(Vector4d::Random().normalized()), + (Vector3d() << 0, 1, 0).finished() + Vector3d::Random() * 1e-4, + false, true)); + for (auto i = 0; i < N_TESTS; i++) + ASSERT_TRUE(testLocalVelocity(Quaterniond(Vector4d::Random().normalized()), + (Vector3d() << 0, 0, 1).finished() + Vector3d::Random() * 1e-4, + false, true)); + printAverageResults(); +} + +TEST_F(FactorVelocityLocalDirection3dTest, origin_solveO) +{ + for (auto i = 0; i < N_TESTS; i++) + ASSERT_TRUE(testLocalVelocity(Quaterniond::Identity(), + Vector3d::Random().normalized(), + true, false)); + printAverageResults(); +} + +TEST_F(FactorVelocityLocalDirection3dTest, random_solveO) +{ + for (auto i = 0; i < N_TESTS; i++) + ASSERT_TRUE(testLocalVelocity(Quaterniond(Vector4d::Random().normalized()), + Vector3d::Random().normalized(), + true, false)); + printAverageResults(); +} + +TEST_F(FactorVelocityLocalDirection3dTest, origin_solveO_degenerated) +{ + for (auto i = 0; i < N_TESTS; i++) + ASSERT_TRUE(testLocalVelocity(Quaterniond::Identity(), + (Vector3d() << 1, 0, 0).finished() + Vector3d::Random() * 1e-4, + true, false)); + for (auto i = 0; i < N_TESTS; i++) + ASSERT_TRUE(testLocalVelocity(Quaterniond::Identity(), + (Vector3d() << 0, 1, 0).finished() + Vector3d::Random() * 1e-4, + true, false)); + for (auto i = 0; i < N_TESTS; i++) + ASSERT_TRUE(testLocalVelocity(Quaterniond::Identity(), + (Vector3d() << 0, 0, 1).finished() + Vector3d::Random() * 1e-4, + true, false)); + printAverageResults(); +} + +TEST_F(FactorVelocityLocalDirection3dTest, random_solveO_degenerated) +{ + for (auto i = 0; i < N_TESTS; i++) + ASSERT_TRUE(testLocalVelocity(Quaterniond(Vector4d::Random().normalized()), + (Vector3d() << 0, 0, 1).finished() + Vector3d::Random() * 1e-4, + true, false)); + for (auto i = 0; i < N_TESTS; i++) + ASSERT_TRUE(testLocalVelocity(Quaterniond(Vector4d::Random().normalized()), + (Vector3d() << 0, 1, 0).finished() + Vector3d::Random() * 1e-4, + true, false)); + for (auto i = 0; i < N_TESTS; i++) + ASSERT_TRUE(testLocalVelocity(Quaterniond(Vector4d::Random().normalized()), + (Vector3d() << 0, 0, 1).finished() + Vector3d::Random() * 1e-4, + true, false)); + printAverageResults(); +} + +int main(int argc, char **argv) +{ + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/test/gtest_processor_fix_wing_model.cpp b/test/gtest_processor_fix_wing_model.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e817a9c400c53cdd569c72de90c672ac703fdaf4 --- /dev/null +++ b/test/gtest_processor_fix_wing_model.cpp @@ -0,0 +1,136 @@ + +#include "core/utils/utils_gtest.h" +#include "core/problem/problem.h" +#include "core/ceres_wrapper/solver_ceres.h" +#include "core/processor/processor_fix_wing_model.h" +#include "core/state_block/state_quaternion.h" + +// STL +#include <iterator> +#include <iostream> + +using namespace wolf; +using namespace Eigen; + +class ProcessorFixWingModelTest : public testing::Test +{ + protected: + ProblemPtr problem; + SolverManagerPtr solver; + SensorBasePtr sensor; + ProcessorBasePtr processor; + + virtual void SetUp() + { + // create problem + problem = Problem::create("POV", 3); + + // create solver + solver = std::make_shared<SolverCeres>(problem); + + // Emplace sensor + sensor = SensorBase::emplace<SensorBase>(problem->getHardware(), + "SensorBase", + std::make_shared<StateBlock>(Vector3d::Zero()), + std::make_shared<StateQuaternion>((Vector4d() << 0,0,0,1).finished()), + nullptr, + 2); + + // Emplace processor + ParamsProcessorFixWingModelPtr params = std::make_shared<ParamsProcessorFixWingModel>(); + params->velocity_local = (Vector3d() << 1, 0, 0).finished(); + params->angle_stdev = 0.1; + params->min_vel_norm = 0; + processor = ProcessorBase::emplace<ProcessorFixWingModel>(sensor, params); + } + + FrameBasePtr emplaceFrame(TimeStamp ts, const Vector10d& x) + { + // new frame + return problem->emplaceFrame(ts, x); + } +}; + +TEST_F(ProcessorFixWingModelTest, setup) +{ + EXPECT_TRUE(problem->check()); +} + +TEST_F(ProcessorFixWingModelTest, keyFrameCallback) +{ + // new frame + auto frm1 = emplaceFrame(1, (Vector10d() << 0,0,0,0,0,0,1,1,0,0).finished()); + + // keyframecallback + problem->keyFrameCallback(frm1, nullptr, 0.5); + + // check one capture + ASSERT_EQ(frm1->getCapturesOf(sensor).size(), 1); + auto cap = frm1->getCaptureOf(sensor); + ASSERT_TRUE(cap != nullptr); + + // check one feature + ASSERT_EQ(cap->getFeatureList().size(), 1); + + // check one factor + auto fac = frm1->getFactorOf(processor, "FactorVelocityLocalDirection3d"); + ASSERT_TRUE(fac != nullptr); + ASSERT_TRUE(fac->getFeature() != nullptr); + ASSERT_TRUE(fac->getCapture() == cap); +} + +TEST_F(ProcessorFixWingModelTest, keyFrameCallbackRepeated) +{ + // new frame + auto frm1 = emplaceFrame(1, (Vector10d() << 0,0,0,0,0,0,1,1,0,0).finished()); + + // keyframecallback + problem->keyFrameCallback(frm1, nullptr, 0.5); + + // repeated keyframecallback + problem->keyFrameCallback(frm1, nullptr, 0.5); + + // check one capture + ASSERT_EQ(frm1->getCapturesOf(sensor).size(), 1); + auto cap = frm1->getCaptureOf(sensor); + ASSERT_TRUE(cap != nullptr); + + // check one feature + ASSERT_EQ(cap->getFeatureList().size(), 1); + + // check one factor + auto fac = frm1->getFactorOf(processor, "FactorVelocityLocalDirection3d"); + ASSERT_TRUE(fac != nullptr); + ASSERT_TRUE(fac->getFeature() != nullptr); + ASSERT_TRUE(fac->getCapture() == cap); +} + +TEST_F(ProcessorFixWingModelTest, solve_origin) +{ + // new frame + auto frm1 = emplaceFrame(1, (Vector10d() << 0,0,0,0,0,0,1,1,0,0).finished()); + + // keyframecallback + problem->keyFrameCallback(frm1, nullptr, 0.5); + + // perturb + frm1->getP()->fix(); + frm1->getO()->fix(); + frm1->getV()->unfix(); + frm1->getV()->setState(Vector3d::Random()); + + // solve + std::string report = solver->solve(SolverManager::ReportVerbosity::FULL); + + WOLF_INFO(report); + + ASSERT_GT(frm1->getV()->getState()(0), 0); + ASSERT_NEAR(frm1->getV()->getState()(1), 0, Constants::EPS); + ASSERT_NEAR(frm1->getV()->getState()(2), 0, Constants::EPS); +} + +int main(int argc, char **argv) +{ + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +}