diff --git a/CMakeLists.txt b/CMakeLists.txt index 51eb17354b1a4a5bab11f89924d8ee63c47bcc67..c6c2db62aa3d682dd1c78bdfbb696b837fa07120 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -248,7 +248,7 @@ SET(HDRS_PROCESSOR include/core/processor/processor_diff_drive.h include/core/processor/factory_processor.h include/core/processor/processor_logging.h - include/core/processor/processor_loopclosure.h + include/core/processor/processor_loop_closure.h include/core/processor/processor_motion.h include/core/processor/processor_odom_2d.h include/core/processor/processor_odom_3d.h @@ -345,7 +345,7 @@ SET(SRCS_PROCESSOR src/processor/motion_buffer.cpp src/processor/processor_base.cpp src/processor/processor_diff_drive.cpp - src/processor/processor_loopclosure.cpp + src/processor/processor_loop_closure.cpp src/processor/processor_motion.cpp src/processor/processor_odom_2d.cpp src/processor/processor_odom_3d.cpp diff --git a/include/core/processor/processor_loop_closure.h b/include/core/processor/processor_loop_closure.h new file mode 100644 index 0000000000000000000000000000000000000000..bb0c9f17041de6cf80919b046c6a5218eaf05518 --- /dev/null +++ b/include/core/processor/processor_loop_closure.h @@ -0,0 +1,110 @@ +#ifndef _WOLF_PROCESSOR_LOOP_CLOSURE_BASE_H +#define _WOLF_PROCESSOR_LOOP_CLOSURE_BASE_H + +// Wolf related headers +#include "core/processor/processor_base.h" + +namespace wolf{ + +WOLF_STRUCT_PTR_TYPEDEFS(ParamsProcessorLoopClosure); + +struct ParamsProcessorLoopClosure : public ParamsProcessorBase +{ + int max_loops=-1; + + ParamsProcessorLoopClosure() = default; + ParamsProcessorLoopClosure(std::string _unique_name, const ParamsServer& _server): + ParamsProcessorBase(_unique_name, _server) + { + max_loops = _server.getParam<int>(prefix + _unique_name + "/max_loops"); + } + + std::string print() const override + { + return "\n" + ParamsProcessorBase::print() + + "max_loops: " + std::to_string(max_loops) + "\n"; + } +}; + +WOLF_STRUCT_PTR_TYPEDEFS(MatchLoopClosure); + +/** \brief Match between a capture and a capture + * + * Match between a capture and a capture (capture-capture correspondence) + * + */ +struct MatchLoopClosure +{ + CaptureBasePtr capture_reference_ptr_; ///< Capture reference + CaptureBasePtr capture_target_ptr_; ///< Capture target + double normalized_score_; ///< normalized similarity score (0 is bad, 1 is good) +}; + +/** \brief General loop closure processor + * + * This is an abstract class. + * + You must define the following classes : + * - voteFindLoopClosures(CaptureBasePtr) + * - emplaceFeatures(CaptureBasePtr) + * - findLoopClosures(CaptureBasePtr) + * - validateLoop(CaptureBasePtr, CaptureBasePtr) + * - emplaceFactors(CaptureBasePtr, CaptureBasePtr) + * + You can override the following classes : + * - process(CaptureBasePtr) + */ +class ProcessorLoopClosure : public ProcessorBase +{ +protected: + + ParamsProcessorLoopClosurePtr params_loop_closure_; + +public: + + ProcessorLoopClosure(const std::string& _type, int _dim, ParamsProcessorLoopClosurePtr _params_loop_closure); + + ~ProcessorLoopClosure() override = default; + void configure(SensorBasePtr _sensor) override { }; + +protected: + + /** \brief Process a capture (linked to a frame) + * If voteFindLoopClosures() returns true, findLoopClosures() is called. + * emplaceFactors() is called for pairs of current capture and each capture returned by findLoopClosures() + */ + virtual void process(CaptureBasePtr); + + /** \brief Returns if findLoopClosures() has to be called for the given capture + */ + virtual bool voteFindLoopClosures(CaptureBasePtr cap) = 0; + + /** \brief detects and emplaces all features of the given capture + */ + virtual void emplaceFeatures(CaptureBasePtr cap) = 0; + + /** \brief Find captures that correspond to loop closures with the given capture + */ + virtual std::map<double,MatchLoopClosurePtr> findLoopClosures(CaptureBasePtr _capture) = 0; + + /** \brief validates a loop closure + */ + virtual bool validateLoopClosure(MatchLoopClosurePtr) = 0; + + /** \brief emplaces the factor(s) corresponding to a Loop Closure between two captures + */ + virtual void emplaceFactors(MatchLoopClosurePtr) = 0; + + void processCapture(CaptureBasePtr) override; + void processKeyFrame(FrameBasePtr, const double&) override; + + bool triggerInCapture(CaptureBasePtr _cap) const override { return true;}; + bool triggerInKeyFrame(FrameBasePtr _frm, const double& _time_tol) const override { return true;}; + + bool storeKeyFrame(FrameBasePtr _frm) override { return false;}; + bool storeCapture(CaptureBasePtr _cap) override { return false;}; + + bool voteForKeyFrame() const override { return false;}; +}; + +} // namespace wolf + +#endif /* _WOLF_PROCESSOR_LOOP_CLOSURE_BASE_H */ diff --git a/include/core/processor/processor_loopclosure.h b/include/core/processor/processor_loopclosure.h deleted file mode 100644 index 12e1fd8781627154315b4815a86105e1d404e86f..0000000000000000000000000000000000000000 --- a/include/core/processor/processor_loopclosure.h +++ /dev/null @@ -1,183 +0,0 @@ -#ifndef _WOLF_PROCESSOR_LOOPCLOSURE_BASE_H -#define _WOLF_PROCESSOR_LOOPCLOSURE_BASE_H - -// Wolf related headers -#include "core/processor/processor_base.h" - -namespace wolf{ - -WOLF_STRUCT_PTR_TYPEDEFS(ParamsProcessorLoopClosure); - -struct ParamsProcessorLoopClosure : public ParamsProcessorBase -{ - using ParamsProcessorBase::ParamsProcessorBase; - // virtual ~ParamsProcessorLoopClosure() = default; - - // add neccesery parameters for loop closure initialisation here and initialize - // them in constructor -}; - -/** \brief General loop closure processor - * - * This is an abstract class. - * + You must define the following classes : - * - voteComputeFeatures() - * - voteSearchLoopClosure() - * - computeFeatures() - * - findLoopCandidate() - * - createFactors() - * + You can override the following classes : - * - selectPairKC() - * - validateLoop() - * - processLoopClosure() - * - * It establishes factors XXX - * - * Should you need extra functionality for your derived types, - * you can overload these two methods, - * - * - preProcess() { } - * - postProcess() { } - * - * which are called at the beginning and at the end of process() respectively. - */ - -class ProcessorLoopClosure : public ProcessorBase -{ -protected: - - ParamsProcessorLoopClosurePtr params_loop_closure_; - -public: - - ProcessorLoopClosure(const std::string& _type, int _dim, ParamsProcessorLoopClosurePtr _params_loop_closure); - - ~ProcessorLoopClosure() override = default; - void configure(SensorBasePtr _sensor) override { }; - -protected: - /** \brief process an incoming capture - * - * The ProcessorLoopClosure is only triggered in KF (see triggerInCapture()) so this function is not called. - */ - void processCapture(CaptureBasePtr) override {}; - - /** \brief process an incoming key-frame - * - * Each derived processor should implement this function. It will be called if: - * - A new KF arrived and triggerInKF() returned true. - */ - void processKeyFrame(FrameBasePtr _keyframe_ptr, const double& _time_tolerance) override; - - /** \brief trigger in capture - * - * The ProcessorLoopClosure only processes incoming KF, then it returns false. - */ - bool triggerInCapture(CaptureBasePtr) const override {return false;} - - /** \brief trigger in key-frame - * - * Returns true if processKeyFrame() should be called after the provided KF arrived. - */ - bool triggerInKeyFrame(FrameBasePtr _keyframe_ptr, const double& _time_tol_other) const override; - - /** \brief store key frame - * - * Returns true if the key frame should be stored - */ - bool storeKeyFrame(FrameBasePtr) override; - - /** \brief store capture - * - * Returns true if the capture should be stored - */ - bool storeCapture(CaptureBasePtr) override; - - /** \brief Called by process(). Tells if computeFeatures() will be called - */ - virtual bool voteComputeFeatures() = 0; - - /** \brief Called by process(). Tells if findLoopCandidate() and createFactors() will be called - * - * WARNING : A LC can be searched only when voteComputeFeatures() return true - */ - virtual bool voteSearchLoopClosure() = 0; - - /** \brief returns a KeyFrame-Capture pair compatible together (selected from the buffers) - * - * Should clear elements before the ones selected in buffers. - * In the default implementation, we select the KF with the most recent TimeStamp - * and that is compatible with at least a Capture - */ - virtual std::pair<FrameBasePtr,CaptureBasePtr> selectPairKC(void); - - /** \brief add the Capture and all features needed to the corresponding KF - * - * If the loop closure process requires features associated to each capture, - * the computations to create these features must be done here. - * - * Important: All detected features should be emplaced to the capture. - * - * Returns a bool that tells if features were successfully created - */ - virtual bool detectFeatures(CaptureBasePtr cap) = 0; - - /** \brief Find a KF that would be a good candidate to close a loop - * if validateLoop is not overwritten, a loop will be closed with the returned candidate - * if no good candidate is found, return nullptr - */ - virtual CaptureBasePtr findLoopCandidate(CaptureBasePtr _capture) = 0; - - /** \brief validate/discard a loop closure - * - * overwrite it if you want an additional test after findLoopCandidate() - */ - virtual bool validateLoop(CaptureBasePtr _capture_1, CaptureBasePtr _capture_2) {return true;}; - - /** \brief emplace the factor(s) - * - */ - virtual void emplaceFactors(CaptureBasePtr _capture_1, CaptureBasePtr _capture_2) = 0; - - - /** Pre-process incoming Capture - * - * This is called by process() just after assigning incoming_ptr_ to a valid Capture. - * - * Overload this function to prepare stuff on derived classes. - * - * Typical uses of prePrecess() are: - * - casting base types to derived types - * - initializing counters, flags, or any derived variables - * - initializing algorithms needed for processing the derived data - */ - virtual void preProcess() { } - - /** Post-process - * - * This is called by process() after finishing the processing algorithm. - * - * Overload this function to post-process stuff on derived classes. - * - * Typical uses of postPrecess() are: - * - resetting and/or clearing variables and/or algorithms at the end of processing - * - drawing / printing / logging the results of the processing - */ - virtual void postProcess() { } - - /** \brief Vote for KeyFrame generation - * - * If a KeyFrame criterion is validated, this function returns true, - * meaning that it wants to create a KeyFrame at the \b last Capture. - * - * WARNING! This function only votes! It does not create KeyFrames! - */ - bool voteForKeyFrame() const override - { - return false; - }; -}; - -} // namespace wolf - -#endif /* _WOLF_PROCESSOR_LOOPCLOSURE_BASE_H */ diff --git a/src/processor/processor_loop_closure.cpp b/src/processor/processor_loop_closure.cpp new file mode 100644 index 0000000000000000000000000000000000000000..486be875ac6eb306e6e0a7973742b7f3e52241c7 --- /dev/null +++ b/src/processor/processor_loop_closure.cpp @@ -0,0 +1,154 @@ +#include "core/processor/processor_loop_closure.h" + +namespace wolf +{ + +ProcessorLoopClosure::ProcessorLoopClosure(const std::string& _type, + int _dim, + ParamsProcessorLoopClosurePtr _params_loop_closure): + ProcessorBase(_type, _dim, _params_loop_closure), + params_loop_closure_(_params_loop_closure) +{ + // +} + +void ProcessorLoopClosure::processCapture(CaptureBasePtr _capture) +{ + /* This function has 3 scenarios: + * 1. Capture already linked to a frame (in trajectory) -> process + * 2. Capture has a timestamp compatible with any stored frame -> link + process + * 3. Otherwise -> store capture (Note that more than one processor can be emplacing frames, so an older frame can arrive later than this one) + */ + + WOLF_DEBUG("ProcessorLoopClosure::processCapture capture ", _capture->id()); + + // CASE 1: + if (_capture->getFrame() and _capture->getFrame()->getTrajectory()) + { + WOLF_DEBUG("CASE 1"); + + process(_capture); + + // remove the frame and older frames + buffer_pack_kf_.removeUpTo(_capture->getFrame()->getTimeStamp()); + + return; + } + + // Search for any stored frame within time tolerance of capture + auto frame_pack = buffer_pack_kf_.select(_capture->getTimeStamp(), params_->time_tolerance); + + // CASE 2: + if (_capture->getFrame() == nullptr and frame_pack) + { + WOLF_DEBUG("CASE 2"); + + _capture->link(frame_pack->key_frame); + + process(_capture); + + // remove the frame and older frames + buffer_pack_kf_.removeUpTo(frame_pack->key_frame->getTimeStamp()); + + return; + } + // CASE 3: + WOLF_DEBUG("CASE 3"); + buffer_capture_.add(_capture->getTimeStamp(), _capture); +} + +void ProcessorLoopClosure::processKeyFrame(FrameBasePtr _frame, const double& _time_tolerance) +{ + /* This function has 4 scenarios: + * 1. Frame already have a capture of the sensor -> process + * 2. Frame has a timestamp within time tolerances of any stored capture -> link + process + * 3. Frame is more recent than any stored capture -> store frame to be processed later in processCapture + * 4. Otherwise: The frame is not compatible with any stored capture -> discard frame + */ + + WOLF_DEBUG("ProcessorLoopClosure::processKeyFrame frame ", _frame->id()); + + // CASE 1: + auto cap = _frame->getCaptureOf(getSensor()); + if (cap) + { + WOLF_DEBUG("CASE 1"); + + process(cap); + + // remove the capture (if stored) + buffer_capture_.getContainer().erase(cap->getTimeStamp()); + + return; + } + + // Search for any stored capture within time tolerance of frame + auto capture = buffer_capture_.select(_frame->getTimeStamp(), params_->time_tolerance); + + // CASE 2: + if (capture and not capture->getFrame()) + { + WOLF_DEBUG("CASE 2"); + + capture->link(_frame); + + process(capture); + + // remove the capture (if stored) + buffer_capture_.getContainer().erase(capture->getTimeStamp()); + + // remove old captures (10s of old captures are kept in case frames arrives unordered) + buffer_capture_.removeUpTo(_frame->getTimeStamp() - 10); + + return; + } + // CASE 3: + if (buffer_capture_.selectLastAfter(_frame->getTimeStamp(), params_->time_tolerance) == nullptr) + { + WOLF_DEBUG("CASE 3"); + + // store frame + buffer_pack_kf_.add(_frame, _time_tolerance); + + return; + } + // CASE 4: + WOLF_DEBUG("CASE 4"); + // nothing (discard frame) +} + +void ProcessorLoopClosure::process(CaptureBasePtr _capture) +{ + assert(_capture->getFrame() != nullptr && "ProcessorLoopClosure::process _capture not linked to _frame"); + WOLF_DEBUG("ProcessorLoopClosure::process frame ", _capture->getFrame()->id(), " capture ", _capture->id()); + + // Detect and emplace features + WOLF_DEBUG("emplacing features..."); + emplaceFeatures(_capture); + + // Vote for loop closure search + if (voteFindLoopClosures(_capture)) + { + WOLF_DEBUG("finding loop closures..."); + + // Find loop closures + auto match_lc_map = findLoopClosures(_capture); + + WOLF_DEBUG(match_lc_map.size(), " loop closures found"); + + // Emplace factors for each LC if validated + auto n_loops = 0; + for (const auto& match_pair : match_lc_map) + if (validateLoopClosure(match_pair.second)) + { + emplaceFactors(match_pair.second); + n_loops++; + + if (params_loop_closure_->max_loops > 0 and + n_loops >= params_loop_closure_->max_loops) + break; + } + } +} + +}// namespace wolf diff --git a/src/processor/processor_loopclosure.cpp b/src/processor/processor_loopclosure.cpp deleted file mode 100644 index 16e037b1c7634c4d0d7509fea51e075fbc8d0066..0000000000000000000000000000000000000000 --- a/src/processor/processor_loopclosure.cpp +++ /dev/null @@ -1,119 +0,0 @@ -/** - * \file processor_loop_closure.h - * - * Created on: Mai 31, 2019 - * \author: Pierre Guetschel - */ - -#include "core/processor/processor_loopclosure.h" - - -namespace wolf -{ - -ProcessorLoopClosure::ProcessorLoopClosure(const std::string& _type, - int _dim, - ParamsProcessorLoopClosurePtr _params_loop_closure): - ProcessorBase(_type, _dim, _params_loop_closure), - params_loop_closure_(_params_loop_closure) -{ - // -} - -//############################################################################## -void ProcessorLoopClosure::processKeyFrame(FrameBasePtr _keyframe_ptr, const double& _time_tolerance) -{ - // the pre-process, if necessary, is implemented in the derived classes - preProcess(); - - if (voteComputeFeatures()) - { - std::pair<FrameBasePtr,CaptureBasePtr> pairKC = selectPairKC(); - - auto cap_1 = pairKC.second; - auto kf_1 = pairKC.first; - - if (kf_1==nullptr || cap_1==nullptr) return; - bool success_computeFeatures = detectFeatures(cap_1); - - // if succeded - if (success_computeFeatures) - { - // link the capture to the KF (if not already linked) - if (cap_1->getFrame() != kf_1) - { - assert(cap_1->getFrame() == nullptr && "capture already linked to a different frame"); //FIXME - cap_1->link(kf_1); - } - - // search loop closure - if(voteSearchLoopClosure()) - { - auto cap_2 = findLoopCandidate(cap_1); - if (cap_2==nullptr) - return; - if (!validateLoop(cap_1, cap_2)) - return; - if (cap_1->getFrame() == nullptr || cap_2->getFrame() == nullptr) - { - WOLF_WARN("ProcessorLoopClosureBase : tried to close a loop with captures linked to no KF"); - return; - } - if (cap_1->getFrame() == cap_2->getFrame()) - { - WOLF_WARN("ProcessorLoopClosureBase : findLoopCandidate() returned two captures of the same frame"); - return; - } - emplaceFactors(cap_1, cap_2); - } - } - } - - // the post-process, if necessary, is implemented in the derived classes - postProcess(); -} - -bool ProcessorLoopClosure::triggerInKeyFrame(FrameBasePtr _keyframe_ptr, const double& _time_tol_other) const -{ - return true; -} -bool ProcessorLoopClosure::storeKeyFrame(FrameBasePtr _frame_ptr) -{ - return true; -} -bool ProcessorLoopClosure::storeCapture(CaptureBasePtr _cap_ptr) -{ - return true; -} - -/** - * In the default implementation, we select the KF with the most recent TimeStamp - * and that is compatible with at least a Capture - */ -std::pair<FrameBasePtr,CaptureBasePtr> ProcessorLoopClosure::selectPairKC() -{ - auto kf_container = buffer_pack_kf_.getContainer(); - - if (kf_container.empty()) - return std::make_pair(nullptr, nullptr); - - for (auto kf_it=kf_container.begin(); kf_it!=kf_container.end(); ++kf_it) - { - CaptureBasePtr cap_ptr = buffer_capture_.select(kf_it->first, kf_it->second->time_tolerance); - if (cap_ptr != nullptr) - { - // clear the buffers : - buffer_capture_.removeUpTo(cap_ptr->getTimeStamp()); - buffer_pack_kf_.removeUpTo(kf_it->first); - // return the KF-Cap pair : - return std::make_pair(kf_it->second->key_frame, cap_ptr); - } - } - return std::make_pair(nullptr, nullptr); -} - - -//############################################################################## - - -}// namespace wolf diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index be33df04ad6dc395ec55b63b9de14a4ea73ff904..4e03c1a2a284f02b2fe26869ad656e5c959a450a 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -233,9 +233,9 @@ target_link_libraries(gtest_param_prior ${PLUGIN_NAME}) wolf_add_gtest(gtest_processor_diff_drive gtest_processor_diff_drive.cpp) target_link_libraries(gtest_processor_diff_drive ${PLUGIN_NAME}) -# ProcessorLoopClosureBase class test -wolf_add_gtest(gtest_processor_loopclosure gtest_processor_loopclosure.cpp) -target_link_libraries(gtest_processor_loopclosure ${PLUGIN_NAME}) +# ProcessorLoopClosure class test +wolf_add_gtest(gtest_processor_loop_closure gtest_processor_loop_closure.cpp) +target_link_libraries(gtest_processor_loop_closure ${PLUGIN_NAME}) # ProcessorFrameNearestNeighborFilter class test # wolf_add_gtest(gtest_processor_frame_nearest_neighbor_filter_2d gtest_processor_frame_nearest_neighbor_filter_2d.cpp) diff --git a/test/dummy/processor_loop_closure_dummy.h b/test/dummy/processor_loop_closure_dummy.h new file mode 100644 index 0000000000000000000000000000000000000000..a391ad9bd593b164b5bfbb0607295e72763852e4 --- /dev/null +++ b/test/dummy/processor_loop_closure_dummy.h @@ -0,0 +1,93 @@ +#ifndef TEST_DUMMY_PROCESSOR_LOOP_CLOSURE_DUMMY_H_ +#define TEST_DUMMY_PROCESSOR_LOOP_CLOSURE_DUMMY_H_ + +#include "core/processor/processor_loop_closure.h" + +using namespace wolf; +using namespace Eigen; + +WOLF_PTR_TYPEDEFS(ProcessorLoopClosureDummy); + +// dummy class: +class ProcessorLoopClosureDummy : public ProcessorLoopClosure +{ + public: + ProcessorLoopClosureDummy(ParamsProcessorLoopClosurePtr _params) : + ProcessorLoopClosure("ProcessorLoopClosureDummy", 2, _params) + { + } + + protected: + bool voteFindLoopClosures(CaptureBasePtr cap) override { return true;}; + bool validateLoopClosure(MatchLoopClosurePtr match) override { return true;}; + + void emplaceFeatures(CaptureBasePtr cap) override + { + // feature = frame pose + FeatureBase::emplace<FeatureBase>(cap, + "FeatureLoopClosureDummy", + cap->getFrame()->getState().vector("PO"), + MatrixXd::Identity(3,3)); + } + + std::map<double,MatchLoopClosurePtr> findLoopClosures(CaptureBasePtr _capture) override + { + std::map<double,MatchLoopClosurePtr> match_lc_map; + + auto old_frame = _capture->getFrame()->getPreviousFrame(); + while (old_frame) + { + // match if features (frames psoe) are close enough + for (auto cap : old_frame->getCaptureList()) + for (auto feat : cap->getFeatureList()) + if (feat->getType() == "FeatureLoopClosureDummy" and + (feat->getMeasurement() - _capture->getFeatureList().front()->getMeasurement()).norm() < 1e-3) + { + MatchLoopClosurePtr match = std::make_shared<MatchLoopClosure>(); + match->capture_reference_ptr_ = cap; + match->capture_target_ptr_ = _capture; + match->normalized_score_ = 1; + + while (match_lc_map.count(match->normalized_score_)) + match->normalized_score_ -= 1e-9; + + match_lc_map.emplace(match->normalized_score_, match); + } + + old_frame = old_frame->getPreviousFrame(); + } + + return match_lc_map; + } + + void emplaceFactors(MatchLoopClosurePtr match) override + { + FeatureBasePtr feat_2; + for (auto feat : match->capture_target_ptr_->getFeatureList()) + if (feat->getType() == "FeatureLoopClosureDummy") + { + feat_2 = feat; + break; + } + + FactorBase::emplace<FactorRelativePose2d>(feat_2, feat_2, + match->capture_reference_ptr_->getFrame(), + shared_from_this(), + false, + TOP_LOOP); + } + + public: + unsigned int getNStoredFrames() + { + return buffer_pack_kf_.getContainer().size(); + } + + unsigned int getNStoredCaptures() + { + return buffer_capture_.getContainer().size(); + } +}; + + +#endif /* TEST_DUMMY_PROCESSOR_LOOP_CLOSURE_DUMMY_H_ */ diff --git a/test/gtest_processor_loop_closure.cpp b/test/gtest_processor_loop_closure.cpp new file mode 100644 index 0000000000000000000000000000000000000000..aec8092de22eaee6a4577b43d8b7a10f8528582b --- /dev/null +++ b/test/gtest_processor_loop_closure.cpp @@ -0,0 +1,327 @@ + +#include "core/utils/utils_gtest.h" +#include "core/problem/problem.h" +#include "core/capture/capture_base.h" +#include "core/factor/factor_relative_pose_2d.h" + +#include "dummy/processor_loop_closure_dummy.h" + +// STL +#include <iterator> +#include <iostream> + +using namespace wolf; +using namespace Eigen; + +class ProcessorLoopClosureTest : public testing::Test +{ + protected: + // Wolf problem + ProblemPtr problem = Problem::create("PO", 2); + SensorBasePtr sensor; + ProcessorLoopClosureDummyPtr processor; + + virtual void SetUp() + { + // Emplace sensor + sensor = SensorBase::emplace<SensorBase>(problem->getHardware(), + "SensorBase", + std::make_shared<StateBlock>(Vector2d::Zero()), + std::make_shared<StateBlock>(Vector1d::Zero()), + nullptr, + 2); + + // Emplace processor + ParamsProcessorLoopClosurePtr params = std::make_shared<ParamsProcessorLoopClosure>(); + params->time_tolerance = 0.5; + processor = ProcessorBase::emplace<ProcessorLoopClosureDummy>(sensor, + params); + } + + FrameBasePtr emplaceFrame(TimeStamp ts, const Vector3d& x) + { + // new frame + return problem->emplaceFrame(ts, x); + } + + CaptureBasePtr emplaceCapture(FrameBasePtr frame) + { + // new capture + return CaptureBase::emplace<CaptureBase>(frame, + "CaptureBase", + frame->getTimeStamp(), + sensor); + } + + CaptureBasePtr createCapture(TimeStamp ts) + { + // new capture + return std::make_shared<CaptureBase>("CaptureBase", + ts, + sensor); + } +}; + +TEST_F(ProcessorLoopClosureTest, installProcessor) +{ + EXPECT_EQ(processor->getNStoredFrames(), 0); + EXPECT_EQ(processor->getNStoredCaptures(), 0); +} + +TEST_F(ProcessorLoopClosureTest, frame_stored) +{ + // new frame + auto frm1 = emplaceFrame(1, Vector3d::Zero()); + + // keyframecallback + problem->keyFrameCallback(frm1, nullptr, 0.5); + + EXPECT_EQ(processor->getNStoredFrames(), 1); + EXPECT_EQ(processor->getNStoredCaptures(), 0); +} + +TEST_F(ProcessorLoopClosureTest, capture_stored) +{ + // new capture + auto cap1 = createCapture(1); + + // captureCallback + processor->captureCallback(cap1); + + EXPECT_EQ(processor->getNStoredFrames(), 0); + EXPECT_EQ(processor->getNStoredCaptures(), 1); +} + +TEST_F(ProcessorLoopClosureTest, captureCallbackCase1) +{ + // emplace frame and capture + auto frm1 = emplaceFrame(1, Vector3d::Zero()); + auto cap1 = emplaceCapture(frm1); + + // captureCallback + processor->captureCallback(cap1); + + EXPECT_EQ(cap1->getFeatureList().size(), 1); // capture processed by the processor + EXPECT_EQ(processor->getNStoredFrames(), 0); + EXPECT_EQ(processor->getNStoredCaptures(), 0); +} + +TEST_F(ProcessorLoopClosureTest, captureCallbackCase2) +{ + // new frame + auto frm1 = emplaceFrame(1, Vector3d::Zero()); + + // new capture + auto cap1 = createCapture(1); + + // keyframecallback + problem->keyFrameCallback(frm1, nullptr, 0.5); + + // captureCallback + processor->captureCallback(cap1); + + EXPECT_EQ(cap1->getFrame(), frm1); // capture processed by the processor + EXPECT_EQ(cap1->getFeatureList().size(), 1); // capture processed by the processor + EXPECT_EQ(processor->getNStoredFrames(), 0); + EXPECT_EQ(processor->getNStoredCaptures(), 0); +} + +TEST_F(ProcessorLoopClosureTest, captureCallbackCase3) +{ + // new frame + auto frm1 = emplaceFrame(1, Vector3d::Zero()); + // new capture + auto cap1 = createCapture(2); + + // keyframecallback + problem->keyFrameCallback(frm1, nullptr, 0.5); + + // captureCallback + processor->captureCallback(cap1); + + EXPECT_TRUE(cap1->getFrame() == nullptr); + EXPECT_EQ(cap1->getFeatureList().size(), 0); + EXPECT_EQ(processor->getNStoredFrames(), 1); + EXPECT_EQ(processor->getNStoredCaptures(), 1); +} + +TEST_F(ProcessorLoopClosureTest, keyFrameCallbackCase1) +{ + // emplace frame and capture + auto frm1 = emplaceFrame(1, Vector3d::Zero()); + auto cap1 = emplaceCapture(frm1); + + // keyframecallback + problem->keyFrameCallback(frm1, nullptr, 0.5); + + EXPECT_EQ(cap1->getFeatureList().size(), 1); // capture processed by the processor + EXPECT_EQ(processor->getNStoredFrames(), 0); + EXPECT_EQ(processor->getNStoredCaptures(), 0); +} + +TEST_F(ProcessorLoopClosureTest, keyFrameCallbackCase2) +{ + // new frame + auto frm1 = emplaceFrame(1, Vector3d::Zero()); + // new capture + auto cap1 = createCapture(1); + + // captureCallback + processor->captureCallback(cap1); + + // keyframecallback + problem->keyFrameCallback(frm1, nullptr, 0.5); + + EXPECT_EQ(cap1->getFrame(), frm1); // capture processed by the processor + EXPECT_EQ(cap1->getFeatureList().size(), 1); // capture processed by the processor + EXPECT_EQ(processor->getNStoredFrames(), 0); + EXPECT_EQ(processor->getNStoredCaptures(), 0); +} + +TEST_F(ProcessorLoopClosureTest, keyFrameCallbackCase3) +{ + // new frame + auto frm1 = emplaceFrame(2, Vector3d::Zero()); + // new capture + auto cap1 = createCapture(1); + + // captureCallback + processor->captureCallback(cap1); + + // keyframecallback + problem->keyFrameCallback(frm1, nullptr, 0.5); + + EXPECT_TRUE(cap1->getFrame() == nullptr); + EXPECT_EQ(cap1->getFeatureList().size(), 0); + EXPECT_EQ(processor->getNStoredFrames(), 1); + EXPECT_EQ(processor->getNStoredCaptures(), 1); +} + +TEST_F(ProcessorLoopClosureTest, keyFrameCallbackCase4) +{ + // new frame + auto frm1 = emplaceFrame(1, Vector3d::Zero()); + // new capture + auto cap1 = createCapture(2); + + // captureCallback + processor->captureCallback(cap1); + + // keyframecallback + problem->keyFrameCallback(frm1, nullptr, 0.5); + + EXPECT_TRUE(cap1->getFrame() == nullptr); + EXPECT_EQ(cap1->getFeatureList().size(), 0); + EXPECT_EQ(processor->getNStoredFrames(), 0); + EXPECT_EQ(processor->getNStoredCaptures(), 1); +} + +TEST_F(ProcessorLoopClosureTest, captureCallbackMatch) +{ + // new frame + auto frm1 = emplaceFrame(1, Vector3d::Zero()); + auto frm2 = emplaceFrame(2, Vector3d::Zero()); + auto frm3 = emplaceFrame(3, Vector3d::Zero()); + auto frm4 = emplaceFrame(4, Vector3d::Zero()); + auto frm5 = emplaceFrame(5, Vector3d::Zero()); + // new captures + auto cap4 = createCapture(4); + + // keyframecallback + problem->keyFrameCallback(frm1, nullptr, 0.5); + problem->keyFrameCallback(frm2, nullptr, 0.5); + problem->keyFrameCallback(frm3, nullptr, 0.5); + problem->keyFrameCallback(frm4, nullptr, 0.5); + problem->keyFrameCallback(frm5, nullptr, 0.5); + + // captureCallback + processor->captureCallback(cap4); + + EXPECT_EQ(frm1->getCaptureList().size(), 0); + EXPECT_EQ(frm2->getCaptureList().size(), 0); + EXPECT_EQ(frm3->getCaptureList().size(), 0); + EXPECT_EQ(frm4->getCaptureList().size(), 1); + EXPECT_EQ(frm5->getCaptureList().size(), 0); + + EXPECT_TRUE(cap4->getFrame() == frm4); + EXPECT_EQ(cap4->getFeatureList().size(), 1); + + EXPECT_EQ(processor->getNStoredFrames(), 1); // all oldest frames are removed from buffer + EXPECT_EQ(processor->getNStoredCaptures(), 0); +} + +TEST_F(ProcessorLoopClosureTest, keyFrameCallbackMatch) +{ + // new frame + auto frm2 = emplaceFrame(2, Vector3d::Zero()); + // new captures + auto cap1 = createCapture(1); + auto cap2 = createCapture(2); + auto cap3 = createCapture(3); + auto cap4 = createCapture(4); + auto cap5 = createCapture(5); + + // captureCallback + processor->captureCallback(cap1); + processor->captureCallback(cap2); + processor->captureCallback(cap3); + processor->captureCallback(cap4); + processor->captureCallback(cap5); + + // keyframecallback + problem->keyFrameCallback(frm2, nullptr, 0.5); + + EXPECT_TRUE(cap1->getFrame() == nullptr); + EXPECT_TRUE(cap2->getFrame() == frm2); + EXPECT_TRUE(cap3->getFrame() == nullptr); + EXPECT_TRUE(cap4->getFrame() == nullptr); + EXPECT_TRUE(cap5->getFrame() == nullptr); + + EXPECT_EQ(cap1->getFeatureList().size(), 0); + EXPECT_EQ(cap2->getFeatureList().size(), 1); + EXPECT_EQ(cap3->getFeatureList().size(), 0); + EXPECT_EQ(cap4->getFeatureList().size(), 0); + EXPECT_EQ(cap5->getFeatureList().size(), 0); + + EXPECT_EQ(processor->getNStoredFrames(), 0); + EXPECT_EQ(processor->getNStoredCaptures(), 4); +} + +TEST_F(ProcessorLoopClosureTest, emplaceFactors) +{ + // emplace frame and capture + auto cap1 = emplaceCapture(emplaceFrame(1, Vector3d::Zero())); + processor->captureCallback(cap1); + + auto cap2 = emplaceCapture(emplaceFrame(2, Vector3d::Ones())); + processor->captureCallback(cap2); + + auto cap3 = emplaceCapture(emplaceFrame(3, 2*Vector3d::Ones())); + processor->captureCallback(cap3); + + auto cap4 = emplaceCapture(emplaceFrame(4, Vector3d::Zero())); + processor->captureCallback(cap4); + + EXPECT_EQ(cap1->getFrame()->getConstrainedByList().size(), 1); + EXPECT_EQ(cap2->getFrame()->getConstrainedByList().size(), 0); + EXPECT_EQ(cap3->getFrame()->getConstrainedByList().size(), 0); + EXPECT_EQ(cap4->getFrame()->getConstrainedByList().size(), 0); + + EXPECT_EQ(cap1->getFeatureList().size(), 1); + EXPECT_EQ(cap2->getFeatureList().size(), 1); + EXPECT_EQ(cap3->getFeatureList().size(), 1); + EXPECT_EQ(cap4->getFeatureList().size(), 1); + + EXPECT_EQ(cap1->getFeatureList().front()->getFactorList().size(), 0); + EXPECT_EQ(cap2->getFeatureList().front()->getFactorList().size(), 0); + EXPECT_EQ(cap3->getFeatureList().front()->getFactorList().size(), 0); + EXPECT_EQ(cap4->getFeatureList().front()->getFactorList().size(), 1); + + EXPECT_EQ(cap1->getFrame()->getConstrainedByList().front(), cap4->getFeatureList().front()->getFactorList().front()); +} + +int main(int argc, char **argv) +{ + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/test/gtest_processor_loopclosure.cpp b/test/gtest_processor_loopclosure.cpp deleted file mode 100644 index 4e50c870348da1877b3a599c48158730e2df47ab..0000000000000000000000000000000000000000 --- a/test/gtest_processor_loopclosure.cpp +++ /dev/null @@ -1,104 +0,0 @@ - -#include "core/utils/utils_gtest.h" -#include "core/problem/problem.h" -#include "core/capture/capture_void.h" - -#include "core/processor/processor_loopclosure.h" - - -// STL -#include <iterator> -#include <iostream> - -using namespace wolf; -using namespace Eigen; - - -WOLF_PTR_TYPEDEFS(ProcessorLoopClosureDummy); - -// dummy class: -class ProcessorLoopClosureDummy : public ProcessorLoopClosure -{ -private: - bool* factor_created; - -public: - ProcessorLoopClosureDummy(ParamsProcessorLoopClosurePtr _params_loop_closure, bool& factor_created): - ProcessorLoopClosure("LOOP CLOSURE DUMMY", 0, _params_loop_closure), - factor_created(&factor_created){}; - std::pair<FrameBasePtr,CaptureBasePtr> public_selectPairKC(){ return selectPairKC();}; - -protected: - bool voteComputeFeatures() override { return true;}; - bool voteSearchLoopClosure() override { return true;}; - bool detectFeatures(CaptureBasePtr cap) override { return true;}; - CaptureBasePtr findLoopCandidate(CaptureBasePtr _capture) override - { - for (FrameBasePtr kf : *getProblem()->getTrajectory()) - for (CaptureBasePtr cap : kf->getCaptureList()) - if (cap != _capture) - return cap; - return nullptr; - }; - void emplaceFactors(CaptureBasePtr _capture_1, CaptureBasePtr _capture_2) override - { - std::cout << "factor created\n"; - *factor_created = true; - }; -}; - - - -TEST(ProcessorLoopClosure, installProcessor) -{ - using namespace wolf; - using std::shared_ptr; - using std::make_shared; - using std::static_pointer_cast; - using Eigen::Vector2d; - - bool factor_created = false; - - - double dt = 0.01; - - // Wolf problem - ProblemPtr problem = Problem::create("PO", 2); - - // Install tracker (sensor and processor) - auto sens_lc = SensorBase::emplace<SensorBase>(problem->getHardware(), - "SENSOR BASE", - std::make_shared<StateBlock>(Eigen::VectorXd::Zero(2)), - std::make_shared<StateBlock>(Eigen::VectorXd::Zero(1)), - std::make_shared<StateBlock>(Eigen::VectorXd::Zero(2)), 2); - ParamsProcessorLoopClosurePtr params = std::make_shared<ParamsProcessorLoopClosure>(); - auto proc_lc = ProcessorBase::emplace<ProcessorLoopClosureDummy>(sens_lc, params, factor_created); - std::cout << "sensor & processor created and added to wolf problem" << std::endl; - - // initialize - TimeStamp t(0.0); - // Vector3d x(0,0,0); - VectorComposite x(Vector3d(0,0,0), "PO", {2,1}); - // Matrix3d P = Matrix3d::Identity() * 0.1; - VectorComposite P(Vector3d(sqrt(0.1),sqrt(0.1),sqrt(0.1)), "PO", {2,1}); - problem->setPriorFactor(x, P, t, dt/2); // KF1 - - - // new KF - t += dt; - auto kf = problem->emplaceFrame(t, x); //KF2 - // emplace a capture in KF - auto capt_lc = CaptureBase::emplace<CaptureVoid>(kf, t, sens_lc); - proc_lc->captureCallback(capt_lc); - - // callback KF - proc_lc->keyFrameCallback(kf, dt/2); - - ASSERT_TRUE(factor_created); -} - -int main(int argc, char **argv) -{ - testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -}